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
weasyprint/document.py CHANGED
@@ -10,7 +10,7 @@ from .anchors import gather_anchors, make_page_bookmark_tree
10
10
  from .css import get_all_computed_styles
11
11
  from .css.counters import CounterStyle
12
12
  from .css.targets import TargetCollector
13
- from .draw import draw_page, stacked
13
+ from .draw import draw_page
14
14
  from .formatting_structure.build import build_formatting_structure
15
15
  from .html import get_html_metadata
16
16
  from .images import get_image_from_uri as original_get_image_from_uri
@@ -82,7 +82,7 @@ class Page:
82
82
 
83
83
  def paint(self, stream, scale=1):
84
84
  """Paint the page into the PDF file."""
85
- with stacked(stream):
85
+ with stream.stacked():
86
86
  stream.transform(a=scale, d=scale)
87
87
  draw_page(self._page_box, stream)
88
88
 
@@ -92,7 +92,7 @@ class DocumentMetadata:
92
92
 
93
93
  New attributes may be added in future versions of WeasyPrint.
94
94
  """
95
- def __init__(self, title=None, authors= None, description=None, keywords=None,
95
+ def __init__(self, title=None, authors=None, description=None, keywords=None,
96
96
  generator=None, created=None, modified=None, attachments=None,
97
97
  lang=None, custom=None, generate_rdf_metadata=generate_rdf_metadata):
98
98
  #: The title of the document, as a string or :obj:`None`.
@@ -204,9 +204,11 @@ class Document:
204
204
  """
205
205
 
206
206
  @classmethod
207
- def _build_layout_context(cls, html, font_config, counter_style, options):
207
+ def _build_layout_context(cls, html, font_config, counter_style, color_profiles,
208
+ options):
208
209
  target_collector = TargetCollector()
209
210
  page_rules = []
211
+ layers = []
210
212
  user_stylesheets = []
211
213
  cache = options['cache']
212
214
  if cache is None:
@@ -217,11 +219,12 @@ class Document:
217
219
  if not hasattr(css, 'matcher'):
218
220
  css = CSS(
219
221
  guess=css, media_type=html.media_type,
220
- font_config=font_config, counter_style=counter_style)
222
+ font_config=font_config, counter_style=counter_style,
223
+ color_profiles=color_profiles)
221
224
  user_stylesheets.append(css)
222
225
  style_for = get_all_computed_styles(
223
- html, user_stylesheets, options['presentational_hints'],
224
- font_config, counter_style, page_rules, target_collector,
226
+ html, user_stylesheets, options['presentational_hints'], font_config,
227
+ counter_style, color_profiles, page_rules, layers, target_collector,
225
228
  options['pdf_forms'])
226
229
  get_image_from_uri = functools.partial(
227
230
  original_get_image_from_uri, cache=cache,
@@ -233,15 +236,18 @@ class Document:
233
236
  return context
234
237
 
235
238
  @classmethod
236
- def _render(cls, html, font_config, counter_style, options):
239
+ def _render(cls, html, font_config, counter_style, color_profiles, options):
237
240
  if font_config is None:
238
241
  font_config = FontConfiguration()
239
242
 
240
243
  if counter_style is None:
241
244
  counter_style = CounterStyle()
242
245
 
246
+ if color_profiles is None:
247
+ color_profiles = {}
248
+
243
249
  context = cls._build_layout_context(
244
- html, font_config, counter_style, options)
250
+ html, font_config, counter_style, color_profiles, options)
245
251
 
246
252
  root_box = build_formatting_structure(
247
253
  html.etree_element, context.style_for, context.get_image_from_uri,
@@ -252,11 +258,11 @@ class Document:
252
258
  rendering = cls(
253
259
  [Page(page_box) for page_box in page_boxes],
254
260
  DocumentMetadata(**get_html_metadata(html)),
255
- html.url_fetcher, font_config)
261
+ html.url_fetcher, font_config, color_profiles)
256
262
  rendering._html = html
257
263
  return rendering
258
264
 
259
- def __init__(self, pages, metadata, url_fetcher, font_config):
265
+ def __init__(self, pages, metadata, url_fetcher, font_config, color_profiles):
260
266
  #: A list of :class:`Page` objects.
261
267
  self.pages = pages
262
268
  #: A :class:`DocumentMetadata` object.
@@ -276,13 +282,7 @@ class Document:
276
282
  # fonts that may be used when rendering
277
283
  self.font_config = font_config
278
284
 
279
- def build_element_structure(self, structure, etree_element=None):
280
- if etree_element is None:
281
- etree_element = self._html.etree_element
282
- structure[etree_element] = {'parent': None}
283
- for child in etree_element:
284
- structure[child] = {'parent': etree_element}
285
- self.build_element_structure(structure, child)
285
+ self.color_profiles = color_profiles
286
286
 
287
287
  def copy(self, pages='all'):
288
288
  """Take a subset of the pages.
@@ -314,7 +314,8 @@ class Document:
314
314
  elif not isinstance(pages, list):
315
315
  pages = list(pages)
316
316
  return type(self)(
317
- pages, self.metadata, self.url_fetcher, self.font_config)
317
+ pages, self.metadata, self.url_fetcher, self.font_config,
318
+ self.color_profiles)
318
319
 
319
320
  def make_bookmark_tree(self, scale=1, transform_pages=False):
320
321
  """Make a tree of all bookmarks in the document.
@@ -12,7 +12,6 @@ from ..matrix import Matrix
12
12
  from ..stacking import StackingContext
13
13
  from .border import draw_border, draw_line, draw_outline, rounded_box, set_mask_border
14
14
  from .color import styled_color
15
- from .stack import stacked
16
15
  from .text import draw_text
17
16
 
18
17
 
@@ -32,11 +31,9 @@ def draw_page(page, stream):
32
31
  def draw_stacking_context(stream, stacking_context):
33
32
  """Draw a ``stacking_context`` on ``stream``."""
34
33
  # See https://www.w3.org/TR/CSS2/zindex.html.
35
- with stacked(stream):
34
+ with stream.stacked():
36
35
  box = stacking_context.box
37
36
 
38
- stream.begin_marked_content(box, mcid=True)
39
-
40
37
  # Apply the viewport_overflow to the html box, see #35.
41
38
  if box.is_for_root_element and (
42
39
  stacking_context.page.style['overflow'] != 'visible'):
@@ -68,21 +65,20 @@ def draw_stacking_context(stream, stacking_context):
68
65
  if box.transformation_matrix.determinant:
69
66
  stream.transform(*box.transformation_matrix.values)
70
67
  else:
71
- stream.end_marked_content()
72
68
  return
73
69
 
74
70
  # Point 1 is done in draw_page.
75
71
 
76
72
  # Point 2.
77
- if isinstance(box, (boxes.BlockBox, boxes.MarginBox,
78
- boxes.InlineBlockBox, boxes.TableCellBox,
79
- boxes.FlexContainerBox, boxes.ReplacedBox)):
73
+ if isinstance(box, (boxes.BlockBox, boxes.MarginBox, boxes.InlineBlockBox,
74
+ boxes.TableCellBox, boxes.FlexContainerBox,
75
+ boxes.GridContainerBox, boxes.ReplacedBox)):
80
76
  set_mask_border(stream, box)
81
- # The canvas background was removed by layout_backgrounds
77
+ # The canvas background was removed by layout_backgrounds.
82
78
  draw_background(stream, box.background)
83
79
  draw_border(stream, box)
84
80
 
85
- with stacked(stream):
81
+ with stream.stacked():
86
82
  # Dont clip the page box, see #35.
87
83
  clip = (
88
84
  box.style['overflow'] != 'visible' and
@@ -118,17 +114,8 @@ def draw_stacking_context(stream, stacking_context):
118
114
  draw_inline_level(stream, stacking_context.page, box)
119
115
 
120
116
  # Point 7.
121
- for block in (box, *stacking_context.blocks_and_cells):
122
- if isinstance(block, boxes.ReplacedBox):
123
- draw_replacedbox(stream, block)
124
- elif block.children:
125
- if block != box:
126
- stream.begin_marked_content(block, mcid=True)
127
- if isinstance(block.children[-1], boxes.LineBox):
128
- for child in block.children:
129
- draw_inline_level(stream, stacking_context.page, child)
130
- if block != box:
131
- stream.end_marked_content()
117
+ draw_block_level(
118
+ stacking_context.page, stream, {box: stacking_context.blocks_and_cells})
132
119
 
133
120
  # Point 8.
134
121
  for child_context in stacking_context.zero_z_contexts:
@@ -144,12 +131,10 @@ def draw_stacking_context(stream, stacking_context):
144
131
  if box.style['opacity'] < 1:
145
132
  group_id = stream.id
146
133
  stream = original_stream
147
- with stacked(stream):
134
+ with stream.stacked():
148
135
  stream.set_alpha(box.style['opacity'], stroke=True, fill=True)
149
136
  stream.draw_x_object(group_id)
150
137
 
151
- stream.end_marked_content()
152
-
153
138
 
154
139
  def draw_background(stream, bg, clip_box=True, bleed=None, marks=()):
155
140
  """Draw the background color and image to a ``pdf.stream.Stream``.
@@ -161,7 +146,7 @@ def draw_background(stream, bg, clip_box=True, bleed=None, marks=()):
161
146
  if bg is None:
162
147
  return
163
148
 
164
- with stacked(stream):
149
+ with stream.stacked():
165
150
  if clip_box:
166
151
  for box in bg.layers[-1].clipped_boxes:
167
152
  rounded_box(stream, box)
@@ -170,7 +155,7 @@ def draw_background(stream, bg, clip_box=True, bleed=None, marks=()):
170
155
 
171
156
  # Draw background color.
172
157
  if bg.color.alpha > 0:
173
- with stacked(stream):
158
+ with stream.artifact(), stream.stacked():
174
159
  stream.set_color(bg.color)
175
160
  painting_area = bg.layers[-1].painting_area
176
161
  stream.rectangle(*painting_area)
@@ -232,7 +217,7 @@ def draw_background(stream, bg, clip_box=True, bleed=None, marks=()):
232
217
  '''
233
218
  svg += '</svg>'
234
219
  tree = ElementTree.fromstring(svg)
235
- image = SVGImage(tree, None, None, stream)
220
+ image = SVGImage(tree, None, None, None)
236
221
  # Painting area is the PDF media box
237
222
  size = (width, height)
238
223
  position = (x, y)
@@ -247,10 +232,10 @@ def draw_background(stream, bg, clip_box=True, bleed=None, marks=()):
247
232
  bg.layers.insert(0, layer)
248
233
  # Paint in reversed order: first layer is "closest" to the viewer.
249
234
  for layer in reversed(bg.layers):
250
- draw_background_image(stream, layer, bg.image_rendering)
235
+ draw_background_image(stream, layer, bg.style)
251
236
 
252
237
 
253
- def draw_background_image(stream, layer, image_rendering):
238
+ def draw_background_image(stream, layer, style):
254
239
  if layer.image is None or 0 in layer.size:
255
240
  return
256
241
 
@@ -262,21 +247,21 @@ def draw_background_image(stream, layer, image_rendering):
262
247
  image_width, image_height = layer.size
263
248
 
264
249
  if repeat_x == 'no-repeat' and repeat_y == 'no-repeat':
265
- # We don't use a pattern when we don't need to because some viewers
266
- # (e.g., Preview on Mac) introduce unnecessary pixelation when vector
267
- # images are used in patterns.
268
- if not layer.unbounded:
269
- stream.rectangle(painting_x, painting_y, painting_width,
270
- painting_height)
271
- stream.clip()
272
- stream.end()
273
- # Put the image in a group so that masking outside the image and
274
- # masking within the image don't conflict.
275
- group = stream.add_group(*stream.page_rectangle)
276
- group.transform(e=position_x + positioning_x,
277
- f=position_y + positioning_y)
278
- layer.image.draw(group, image_width, image_height, image_rendering)
279
- stream.draw_x_object(group.id)
250
+ with stream.artifact():
251
+ # We don't use a pattern when we don't need to because some viewers
252
+ # (e.g., Preview on Mac) introduce unnecessary pixelation when vector
253
+ # images are used in patterns.
254
+ if not layer.unbounded:
255
+ stream.rectangle(
256
+ painting_x, painting_y, painting_width, painting_height)
257
+ stream.clip()
258
+ stream.end()
259
+ # Put the image in a group so that masking outside the image and
260
+ # masking within the image don't conflict.
261
+ group = stream.add_group(*stream.page_rectangle)
262
+ group.transform(e=position_x + positioning_x, f=position_y + positioning_y)
263
+ layer.image.draw(group, image_width, image_height, style)
264
+ stream.draw_x_object(group.id)
280
265
  return
281
266
 
282
267
  if repeat_x == 'no-repeat':
@@ -322,9 +307,10 @@ def draw_background_image(stream, layer, image_rendering):
322
307
  0, 0, image_width, image_height, repeat_width, repeat_height, matrix)
323
308
  group = pattern.add_group(0, 0, repeat_width, repeat_height)
324
309
 
325
- with stacked(stream):
326
- layer.image.draw(group, image_width, image_height, image_rendering)
327
- pattern.draw_x_object(group.id)
310
+ with stream.artifact(), stream.stacked():
311
+ layer.image.draw(group, image_width, image_height, style)
312
+ with pattern.artifact():
313
+ pattern.draw_x_object(group.id)
328
314
  stream.set_color_space('Pattern')
329
315
  stream.set_color_special(pattern.id)
330
316
  if layer.unbounded:
@@ -476,9 +462,9 @@ def draw_collapsed_borders(stream, table):
476
462
 
477
463
  for segment in segments:
478
464
  _, style, width, color, side, border_box = segment
479
- with stacked(stream):
480
- bx, by, bw, bh = border_box
481
- color = styled_color(style, color, side)
465
+ bx, by, bw, bh = border_box
466
+ color = styled_color(style, color, side)
467
+ with stream.artifact(), stream.stacked():
482
468
  draw_line(stream, bx, by, bx + bw, by + bh, width, style, color)
483
469
 
484
470
 
@@ -491,14 +477,13 @@ def draw_replacedbox(stream, box):
491
477
  if draw_width <= 0 or draw_height <= 0:
492
478
  return
493
479
 
494
- with stacked(stream):
480
+ with stream.stacked():
495
481
  stream.set_alpha(1)
496
482
  stream.transform(e=draw_x, f=draw_y)
497
- with stacked(stream):
483
+ with stream.stacked():
498
484
  # TODO: Use the real intrinsic size here, not affected by
499
485
  # 'image-resolution'?
500
- box.replacement.draw(
501
- stream, draw_width, draw_height, box.style['image_rendering'])
486
+ box.replacement.draw(stream, draw_width, draw_height, box.style)
502
487
 
503
488
 
504
489
  def draw_inline_level(stream, page, box, offset_x=0, text_overflow='clip',
@@ -513,15 +498,10 @@ def draw_inline_level(stream, page, box, offset_x=0, text_overflow='clip',
513
498
  draw_background(stream, box.background)
514
499
  draw_border(stream, box)
515
500
  if isinstance(box, (boxes.InlineBox, boxes.LineBox)):
516
- link_annotation = None
517
501
  if isinstance(box, boxes.LineBox):
518
502
  text_overflow = box.text_overflow
519
503
  block_ellipsis = box.block_ellipsis
520
- else:
521
- link_annotation = box.link_annotation
522
504
  ellipsis = 'none'
523
- if link_annotation:
524
- stream.begin_marked_content(box, mcid=True, tag='Link')
525
505
  for i, child in enumerate(box.children):
526
506
  if i == len(box.children) - 1:
527
507
  # Last child
@@ -531,15 +511,28 @@ def draw_inline_level(stream, page, box, offset_x=0, text_overflow='clip',
531
511
  else:
532
512
  child_offset_x = offset_x + child.position_x - box.position_x
533
513
  if isinstance(child, boxes.TextBox):
534
- draw_text(stream, child, child_offset_x, text_overflow, ellipsis)
514
+ with stream.marked(child, 'Span'):
515
+ draw_text(
516
+ stream, child, child_offset_x, text_overflow, ellipsis)
535
517
  else:
536
518
  draw_inline_level(
537
519
  stream, page, child, child_offset_x, text_overflow, ellipsis)
538
- if link_annotation:
539
- stream.end_marked_content()
540
520
  elif isinstance(box, boxes.InlineReplacedBox):
541
- draw_replacedbox(stream, box)
521
+ with stream.marked(box, 'Figure'):
522
+ draw_replacedbox(stream, box)
542
523
  else:
543
524
  assert isinstance(box, boxes.TextBox)
544
525
  # Should only happen for list markers.
545
526
  draw_text(stream, box, offset_x, text_overflow)
527
+
528
+
529
+ def draw_block_level(page, stream, blocks_and_cells):
530
+ for block, blocks_and_cells in blocks_and_cells.items():
531
+ if isinstance(block, boxes.ReplacedBox):
532
+ with stream.marked(block, 'Figure'):
533
+ draw_replacedbox(stream, block)
534
+ elif block.children:
535
+ if isinstance(block.children[-1], boxes.LineBox):
536
+ for child in block.children:
537
+ draw_inline_level(stream, page, child)
538
+ draw_block_level(page, stream, blocks_and_cells)