weasyprint 65.0__py3-none-any.whl → 66.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. weasyprint/__init__.py +4 -1
  2. weasyprint/__main__.py +2 -0
  3. weasyprint/css/__init__.py +12 -4
  4. weasyprint/css/computed_values.py +8 -2
  5. weasyprint/css/html5_ua.css +10 -11
  6. weasyprint/css/html5_ua_form.css +1 -1
  7. weasyprint/css/utils.py +1 -1
  8. weasyprint/document.py +2 -10
  9. weasyprint/draw/__init__.py +51 -57
  10. weasyprint/draw/border.py +120 -66
  11. weasyprint/draw/text.py +1 -2
  12. weasyprint/formatting_structure/boxes.py +3 -2
  13. weasyprint/formatting_structure/build.py +32 -42
  14. weasyprint/images.py +8 -15
  15. weasyprint/layout/__init__.py +5 -2
  16. weasyprint/layout/absolute.py +4 -1
  17. weasyprint/layout/block.py +60 -29
  18. weasyprint/layout/column.py +1 -0
  19. weasyprint/layout/flex.py +55 -29
  20. weasyprint/layout/float.py +8 -1
  21. weasyprint/layout/grid.py +1 -1
  22. weasyprint/layout/inline.py +7 -8
  23. weasyprint/layout/page.py +43 -15
  24. weasyprint/layout/preferred.py +59 -32
  25. weasyprint/layout/table.py +8 -4
  26. weasyprint/pdf/__init__.py +13 -6
  27. weasyprint/pdf/anchors.py +2 -2
  28. weasyprint/pdf/pdfua.py +7 -115
  29. weasyprint/pdf/stream.py +40 -49
  30. weasyprint/pdf/tags.py +305 -0
  31. weasyprint/stacking.py +14 -15
  32. weasyprint/svg/__init__.py +22 -11
  33. weasyprint/svg/bounding_box.py +4 -2
  34. weasyprint/svg/defs.py +4 -9
  35. weasyprint/svg/utils.py +9 -5
  36. weasyprint/text/fonts.py +1 -1
  37. weasyprint/text/line_break.py +45 -26
  38. weasyprint/urls.py +21 -10
  39. {weasyprint-65.0.dist-info → weasyprint-66.0.dist-info}/METADATA +1 -1
  40. weasyprint-66.0.dist-info/RECORD +74 -0
  41. {weasyprint-65.0.dist-info → weasyprint-66.0.dist-info}/WHEEL +1 -1
  42. weasyprint/draw/stack.py +0 -13
  43. weasyprint-65.0.dist-info/RECORD +0 -74
  44. {weasyprint-65.0.dist-info → weasyprint-66.0.dist-info}/entry_points.txt +0 -0
  45. {weasyprint-65.0.dist-info → weasyprint-66.0.dist-info}/licenses/LICENSE +0 -0
weasyprint/__init__.py CHANGED
@@ -15,7 +15,7 @@ import cssselect2
15
15
  import tinycss2
16
16
  import tinyhtml5
17
17
 
18
- VERSION = __version__ = '65.0'
18
+ VERSION = __version__ = '66.0'
19
19
 
20
20
  #: Default values for command-line and Python API options. See
21
21
  #: :func:`__main__.main` to learn more about specific options for
@@ -39,6 +39,8 @@ VERSION = __version__ = '65.0'
39
39
  #: A PDF version number.
40
40
  #: :param bool pdf_forms:
41
41
  #: Whether PDF forms have to be included.
42
+ #: :param bool pdf_tags:
43
+ #: Whether PDF should be tagged for accessibility.
42
44
  #: :param bool uncompressed_pdf:
43
45
  #: Whether PDF content should be compressed.
44
46
  #: :param bool custom_metadata:
@@ -71,6 +73,7 @@ DEFAULT_OPTIONS = {
71
73
  'pdf_variant': None,
72
74
  'pdf_version': None,
73
75
  'pdf_forms': None,
76
+ 'pdf_tags': False,
74
77
  'uncompressed_pdf': False,
75
78
  'custom_metadata': False,
76
79
  'presentational_hints': False,
weasyprint/__main__.py CHANGED
@@ -89,6 +89,8 @@ PARSER.add_argument(
89
89
  PARSER.add_argument('--pdf-version', help='PDF version number')
90
90
  PARSER.add_argument(
91
91
  '--pdf-forms', action='store_true', help='include PDF forms')
92
+ PARSER.add_argument(
93
+ '--pdf-tags', action='store_true', help='tag PDF for accessibility')
92
94
  PARSER.add_argument(
93
95
  '--uncompressed-pdf', action='store_true',
94
96
  help='do not compress PDF content, mainly for debugging purpose')
@@ -573,28 +573,36 @@ def declaration_precedence(origin, importance):
573
573
  return 5
574
574
 
575
575
 
576
- def resolve_var(computed, token, parent_style):
576
+ def resolve_var(computed, token, parent_style, known_variables=None):
577
577
  """Return token with resolved CSS variables."""
578
578
  if not check_var_function(token):
579
579
  return
580
580
 
581
+ if known_variables is None:
582
+ known_variables = set()
583
+
581
584
  if token.lower_name != 'var':
582
585
  arguments = []
583
586
  for i, argument in enumerate(token.arguments):
584
587
  if argument.type == 'function':
585
- arguments.extend(resolve_var(computed, argument, parent_style))
588
+ arguments.extend(resolve_var(
589
+ computed, argument, parent_style, known_variables))
586
590
  else:
587
591
  arguments.append(argument)
588
592
  token = tinycss2.ast.FunctionBlock(
589
593
  token.source_line, token.source_column, token.name, arguments)
590
- return resolve_var(computed, token, parent_style) or (token,)
594
+ return resolve_var(computed, token, parent_style, known_variables) or (token,)
591
595
 
592
596
  args = parse_function(token)[1]
593
597
  variable_name = args.pop(0).value.replace('-', '_') # first arg is name
598
+ if variable_name in known_variables:
599
+ return [] # endless recursion, returned value is nothing
600
+ else:
601
+ known_variables.add(variable_name)
594
602
  default = args # next args are default value
595
603
  computed_value = []
596
604
  for value in (computed[variable_name] or default):
597
- resolved = resolve_var(computed, value, parent_style)
605
+ resolved = resolve_var(computed, value, parent_style, known_variables)
598
606
  computed_value.extend((value,) if resolved is None else resolved)
599
607
  return computed_value
600
608
 
@@ -282,7 +282,7 @@ def length(style, name, value, font_size=None, pixels_only=False):
282
282
  elif unit in LENGTHS_TO_PIXELS:
283
283
  # Convert absolute lengths to pixels
284
284
  result = value.value * LENGTHS_TO_PIXELS[unit]
285
- elif unit in ('em', 'ex', 'ch', 'rem'):
285
+ elif unit in ('em', 'ex', 'ch', 'rem', 'lh', 'rlh'):
286
286
  if font_size is None:
287
287
  font_size = style['font_size']
288
288
  if unit == 'ex':
@@ -296,6 +296,12 @@ def length(style, name, value, font_size=None, pixels_only=False):
296
296
  result = value.value * font_size
297
297
  elif unit == 'rem':
298
298
  result = value.value * style.root_style['font_size']
299
+ elif unit == 'lh':
300
+ line_height, _ = strut_layout(style)
301
+ result = value.value * line_height
302
+ elif unit == 'rlh':
303
+ line_height, _ = strut_layout(style.root_style)
304
+ result = value.value * line_height
299
305
  else:
300
306
  # A percentage or 'auto': no conversion needed.
301
307
  return value
@@ -478,7 +484,7 @@ def _content_list(style, values):
478
484
  if attr is None:
479
485
  computed_value = None
480
486
  else:
481
- computed_value = (value[0], ((attr,) + value[1][1:]))
487
+ computed_value = (value[0], (attr, *value[1][1:]))
482
488
  else:
483
489
  computed_value = value
484
490
  if computed_value is None:
@@ -20,7 +20,7 @@ a[href] { -weasy-link: attr(href) }
20
20
  /* Display and visibility */
21
21
 
22
22
  [hidden], area, base, basefont, command, datalist, head, input[type=hidden i], link, menu[type=context i], meta, noembed, noframes, param, rp, script, source, style, template, title, track { display: none }
23
- address, article, aside, blockquote, body, center, dd, details, dir, div, dl, dt, frame, frameset, fieldset, figure, figcaption, footer, form, h1, h2, h3, h4, h5, h6, header, hgroup, hr, html, legend, listing, menu, nav, ol, p, plaintext, pre, section, summary, ul, xmp { display: block }
23
+ address, article, aside, blockquote, body, center, dd, details, dir, div, dl, dt, frame, frameset, fieldset, figure, figcaption, footer, form, h1, h2, h3, h4, h5, h6, header, hgroup, hr, html, legend, listing, main, menu, nav, ol, p, plaintext, pre, section, summary, ul, xmp { display: block }
24
24
  button, input, keygen, select, textarea { display: inline-block }
25
25
  li { display: list-item }
26
26
  table { display: table }
@@ -47,11 +47,6 @@ h3 { margin-top: 1em; margin-bottom: 1em }
47
47
  h4 { margin-top: 1.33em; margin-bottom: 1.33em }
48
48
  h5 { margin-top: 1.67em; margin-bottom: 1.67em }
49
49
  h6 { margin-top: 2.33em; margin-bottom: 2.33em }
50
- :is(article, aside, nav, section) h1 { font-size: 1.5em; margin-bottom: .83em; margin-top: .83em }
51
- :is(article, aside, nav, section) :is(article, aside, nav, section) h1 { font-size: 1.17em; margin-bottom: 1em; margin-top: 1em }
52
- :is(article, aside, nav, section) :is(article, aside, nav, section) :is(article, aside, nav, section) h1 { font-size: 1em; margin-bottom: 1.33em; margin-top: 1.33em }
53
- :is(article, aside, nav, section) :is(article, aside, nav, section) :is(article, aside, nav, section) :is(article, aside, nav, section) h1 { font-size: .83em; margin-bottom: 1.67em; margin-top: 1.67em }
54
- :is(article, aside, nav, section) :is(article, aside, nav, section) :is(article, aside, nav, section) :is(article, aside, nav, section) :is(article, aside, nav, section) h1 { font-size: .67em; margin-bottom: 2.33em; margin-top: 2.33em }
55
50
 
56
51
  blockquote, figure { margin-left: 40px; margin-right: 40px }
57
52
 
@@ -62,14 +57,18 @@ dd { margin-left: 40px }
62
57
  [dir] [dir=rtl i] dd { margin-left: 40px; margin-right: 0 }
63
58
  [dir] [dir] [dir=ltr i] dd { margin-left: 0; margin-right: 40px }
64
59
  [dir] [dir] [dir=rtl i] dd { margin-left: 40px; margin-right: 0 }
60
+ dd[dir=ltr i][dir][dir] { margin-left: 0; margin-right: 40px }
61
+ dd[dir=rtl i][dir][dir] { margin-left: 40px; margin-right: 0 }
65
62
 
66
63
  dir, menu, ol, ul { padding-left: 40px }
67
- [dir=rtl i] :is(dir, menu, ol, ul) { padding-left: 0; padding-right: 40px }
68
64
  [dir=ltr i] :is(dir, menu, ol, ul) { padding-left: 40px; padding-right: 0 }
69
- [dir] [dir=rtl i] :is(dir, menu, ol, ul) { padding-left: 0; padding-right: 40px }
65
+ [dir=rtl i] :is(dir, menu, ol, ul) { padding-left: 0; padding-right: 40px }
70
66
  [dir] [dir=ltr i] :is(dir, menu, ol, ul) { padding-left: 40px; padding-right: 0 }
71
- [dir] [dir] [dir=rtl i] :is(dir, menu, ol, ul) { padding-left: 0; padding-right: 40px }
67
+ [dir] [dir=rtl i] :is(dir, menu, ol, ul) { padding-left: 0; padding-right: 40px }
72
68
  [dir] [dir] [dir=ltr i] :is(dir, menu, ol, ul) { padding-left: 40px; padding-right: 0 }
69
+ [dir] [dir] [dir=rtl i] :is(dir, menu, ol, ul) { padding-left: 0; padding-right: 40px }
70
+ :is(dir, menu, ol, ul)[dir=ltr i][dir][dir] { padding-left: 40px; padding-right: 0 }
71
+ :is(dir, menu, ol, ul)[dir=rtl i][dir][dir] { padding-left: 0; padding-right: 40px }
73
72
 
74
73
  table { border-spacing: 2px; border-collapse: separate }
75
74
  td, th { padding: 1px }
@@ -170,9 +169,9 @@ input:is([type=button i], [type=reset i], [type=submit i])[value], button[value]
170
169
  input[type=submit i]:not([value])::before { content: "Submit" }
171
170
  input[type=reset i]:not([value])::before { content: "Reset" }
172
171
  input:is([type=checkbox i], [type=radio i]) { height: .7em; vertical-align: -.2em; width: .7em }
173
- input:is([type=checkbox i], [type=radio i])[checked]::before { background: black; content: ""; height: 100% }
172
+ input:is([type=checkbox i], [type=radio i])[checked]::before { background: black; content: ""; display: block; height: 100% }
174
173
  input[type=radio i], input[type=radio][checked]:before { border-radius: 50% }
175
- input[value]::before { content: attr(value); display: block; overflow: hidden }
174
+ input[value]:not([type=checkbox i], [type=radio i])::before { content: attr(value); display: block; overflow: hidden }
176
175
  :is(input, input[value=""], input[type=checkbox i], input[type=radio i]) { content: ""; display: block }
177
176
  select { background: lightgrey; border-radius: .25em .25em; position: relative; white-space: normal }
178
177
  select[multiple] { height: 3.6em }
@@ -1,5 +1,5 @@
1
1
  /* Default stylesheet for PDF forms */
2
2
 
3
3
  button, input, select, textarea { appearance: auto }
4
- select option, select:not([multiple])::before, input:not([type="submit"])::before {visibility: hidden }
4
+ select option, select:not([multiple])::before, input:not([type="submit"])::before { visibility: hidden }
5
5
  textarea { text-indent: 10000% } /* Hide text but don’t change color used by PDF form */
weasyprint/css/utils.py CHANGED
@@ -40,7 +40,7 @@ RESOLUTION_TO_DPPX = {
40
40
  }
41
41
 
42
42
  # Sets of possible length units
43
- LENGTH_UNITS = set(LENGTHS_TO_PIXELS) | set(['ex', 'em', 'ch', 'rem'])
43
+ LENGTH_UNITS = set(LENGTHS_TO_PIXELS) | set(['ex', 'em', 'ch', 'rem', 'lh', 'rlh'])
44
44
 
45
45
  # Constants about background positions
46
46
  ZERO_PERCENT = Dimension(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
 
@@ -276,14 +276,6 @@ class Document:
276
276
  # fonts that may be used when rendering
277
277
  self.font_config = font_config
278
278
 
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)
286
-
287
279
  def copy(self, pages='all'):
288
280
  """Take a subset of the pages.
289
281
 
@@ -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)
@@ -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, image_rendering)
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):
310
+ with stream.artifact(), stream.stacked():
326
311
  layer.image.draw(group, image_width, image_height, image_rendering)
327
- pattern.draw_x_object(group.id)
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,10 +477,10 @@ 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
486
  box.replacement.draw(
@@ -513,15 +499,10 @@ def draw_inline_level(stream, page, box, offset_x=0, text_overflow='clip',
513
499
  draw_background(stream, box.background)
514
500
  draw_border(stream, box)
515
501
  if isinstance(box, (boxes.InlineBox, boxes.LineBox)):
516
- link_annotation = None
517
502
  if isinstance(box, boxes.LineBox):
518
503
  text_overflow = box.text_overflow
519
504
  block_ellipsis = box.block_ellipsis
520
- else:
521
- link_annotation = box.link_annotation
522
505
  ellipsis = 'none'
523
- if link_annotation:
524
- stream.begin_marked_content(box, mcid=True, tag='Link')
525
506
  for i, child in enumerate(box.children):
526
507
  if i == len(box.children) - 1:
527
508
  # Last child
@@ -531,15 +512,28 @@ def draw_inline_level(stream, page, box, offset_x=0, text_overflow='clip',
531
512
  else:
532
513
  child_offset_x = offset_x + child.position_x - box.position_x
533
514
  if isinstance(child, boxes.TextBox):
534
- draw_text(stream, child, child_offset_x, text_overflow, ellipsis)
515
+ with stream.marked(child, 'Span'):
516
+ draw_text(
517
+ stream, child, child_offset_x, text_overflow, ellipsis)
535
518
  else:
536
519
  draw_inline_level(
537
520
  stream, page, child, child_offset_x, text_overflow, ellipsis)
538
- if link_annotation:
539
- stream.end_marked_content()
540
521
  elif isinstance(box, boxes.InlineReplacedBox):
541
- draw_replacedbox(stream, box)
522
+ with stream.marked(box, 'Figure'):
523
+ draw_replacedbox(stream, box)
542
524
  else:
543
525
  assert isinstance(box, boxes.TextBox)
544
526
  # Should only happen for list markers.
545
527
  draw_text(stream, box, offset_x, text_overflow)
528
+
529
+
530
+ def draw_block_level(page, stream, blocks_and_cells):
531
+ for block, blocks_and_cells in blocks_and_cells.items():
532
+ if isinstance(block, boxes.ReplacedBox):
533
+ with stream.marked(block, 'Figure'):
534
+ draw_replacedbox(stream, block)
535
+ elif block.children:
536
+ if isinstance(block.children[-1], boxes.LineBox):
537
+ for child in block.children:
538
+ draw_inline_level(stream, page, child)
539
+ draw_block_level(page, stream, blocks_and_cells)