1
1
mirror of https://github.com/Kozea/WeasyPrint.git synced 2024-11-09 14:05:30 +03:00

Fix "pages" counter in non-root absolute boxes

These boxes were not available through the "descendants" function, because they
are represented by placeholders. They are also not available in the list of
absolute boxes of the page, because they’re not relative to the root page.

The new "placeholders" parameter of "descendants" goes through placeholders and
gets all the absolute children.

Putting "descendants" and "children" in all boxes, not just parents, avoids
useless checks and special cases.

Fix #2029.
This commit is contained in:
Guillaume Ayoub 2024-01-29 08:17:06 +01:00
parent e018bf3fbb
commit c6468e5395
9 changed files with 67 additions and 48 deletions

View File

@ -62,10 +62,9 @@ def test_breaking_linebox():
for child in line.children:
assert child.element_tag in ('em', 'p')
assert child.style['font_size'] == 13
if isinstance(child, boxes.ParentBox):
for child_child in child.children:
assert child.element_tag in ('em', 'strong', 'span')
assert child.style['font_size'] == 13
for child_child in child.children:
assert child.element_tag in ('em', 'strong', 'span')
assert child.style['font_size'] == 13
@assert_no_logs

View File

@ -167,7 +167,7 @@ def test_target_absolute():
content: target-counter('#h', page);
}
div {
position: absolute;
position: absolute;
}
</style>
<div><a id="span">link</a></div>
@ -182,3 +182,32 @@ def test_target_absolute():
text_box, after = inline.children
assert text_box.text == 'link'
assert after.children[0].text == '1'
@assert_no_logs
def test_target_absolute_non_root():
document = FakeHTML(string='''
<style>
a::after {
content: target-counter('#h', page);
}
section {
position: relative;
}
div {
position: absolute;
}
</style>
<section><div><a id="span">link</a></div></section>
<h1 id="h">abc</h1>
''')
page, = document.render().pages
html, = page._page_box.children
body, = html.children
section, h1 = body.children
div, = section.children
line, = div.children
inline, = line.children
text_box, after = inline.children
assert text_box.text == 'link'
assert after.children[0].text == '1'

View File

@ -810,10 +810,9 @@ def draw_outlines(stream, box):
stream, outline_box, 4 * (width,), style,
styled_color(style, color, side))
if isinstance(box, boxes.ParentBox):
for child in box.children:
if isinstance(child, boxes.Box):
draw_outlines(stream, child)
for child in box.children:
if isinstance(child, boxes.Box):
draw_outlines(stream, child)
def draw_table(stream, table):

View File

@ -82,13 +82,23 @@ class Box:
# Default, overriden on some subclasses
def all_children(self):
return ()
return self.children
def descendants(self, placeholders=False):
"""A flat generator for a box, its children and descendants."""
yield self
for child in self.children:
if placeholders or isinstance(child, Box):
yield from child.descendants(placeholders)
else:
yield child
def __init__(self, element_tag, style, element):
self.element_tag = element_tag
self.element = element
self.style = style
self.remove_decoration_sides = set()
self.children = []
def __repr__(self):
return f'<{type(self).__name__} {self.element_tag}>'
@ -332,9 +342,6 @@ class ParentBox(Box):
super().__init__(element_tag, style, element)
self.children = tuple(children)
def all_children(self):
return self.children
def _reset_spacing(self, side):
"""Set to 0 the margin, padding and border of ``side``."""
self.remove_decoration_sides.add(side)
@ -353,7 +360,7 @@ class ParentBox(Box):
def copy_with_children(self, new_children):
"""Create a new equivalent box with given ``new_children``."""
new_box = self.copy()
new_box.children = list(new_children)
new_box.children = new_children
# Clear and reset removed decorations as we don't want to keep the
# previous data, for example when a box is split between two pages.
@ -363,19 +370,9 @@ class ParentBox(Box):
def deepcopy(self):
result = self.copy()
result.children = tuple(child.deepcopy() for child in self.children)
result.children = list(child.deepcopy() for child in self.children)
return result
def descendants(self):
"""A flat generator for a box, its children and descendants."""
yield self
for child in self.children:
if isinstance(child, ParentBox):
for grand_child in child.descendants():
yield grand_child
else:
yield child
def get_wrapped_table(self):
"""Get the table wrapped by the box."""
assert self.is_table_wrapper

View File

@ -1231,7 +1231,7 @@ def process_whitespace(box, following_collapsible_space=False):
box.text = text
elif isinstance(box, boxes.ParentBox):
else:
for child in box.children:
if isinstance(child, (boxes.TextBox, boxes.InlineBox)):
child_collapsible_space = process_whitespace(
@ -1257,7 +1257,7 @@ def process_text_transform(box):
if box.style['hyphens'] == 'none':
box.text = box.text.replace('\u00AD', '') # U+00AD is soft hyphen
elif isinstance(box, boxes.ParentBox) and not box.is_running():
elif not box.is_running():
for child in box.children:
if isinstance(child, (boxes.TextBox, boxes.InlineBox)):
process_text_transform(child)
@ -1316,7 +1316,7 @@ def inline_in_block(box):
]
"""
if not isinstance(box, boxes.ParentBox) or box.is_running():
if not box.children or box.is_running():
return box
box_children = list(box.children)
@ -1451,7 +1451,7 @@ def block_in_inline(box):
]
"""
if not isinstance(box, boxes.ParentBox) or box.is_running():
if not box.children or box.is_running():
return box
new_children = []

View File

@ -316,10 +316,9 @@ class LayoutContext:
for (string_name, _) in element.style['string_set']:
if string_name == name:
return first_string
if isinstance(element, boxes.ParentBox):
if element.children:
element = element.children[0]
continue
if element.children:
element = element.children[0]
continue
break
elif keyword == 'last':
return last_string

View File

@ -917,7 +917,7 @@ def block_level_page_break(sibling_before, sibling_after):
box = sibling_before
while isinstance(box, block_parallel_box_types):
values.append(box.style['break_after'])
if not (isinstance(box, boxes.ParentBox) and box.children):
if not box.children:
break
box = box.children[-1]
values.reverse() # Have them in tree order
@ -925,7 +925,7 @@ def block_level_page_break(sibling_before, sibling_after):
box = sibling_after
while isinstance(box, block_parallel_box_types):
values.append(box.style['break_before'])
if not (isinstance(box, boxes.ParentBox) and box.children):
if not box.children:
break
box = box.children[0]

View File

@ -1,7 +1,6 @@
"""Layout for pages and CSS3 margin boxes."""
import copy
from itertools import chain
from math import inf
from ..css import PageType, computed_from_cascaded
@ -666,9 +665,6 @@ def make_page(context, root_box, page_type, resume_at, page_number,
context.finish_block_formatting_context(root_box)
page.children = [root_box, footnote_area]
descendants = chain(page.descendants(), *(
child.descendants() if hasattr(child, 'descendants') else (child,)
for child in positioned_boxes))
# Update page counter values
_standardize_page_based_counters(style, None)
@ -692,7 +688,7 @@ def make_page(context, root_box, page_type, resume_at, page_number,
cached_anchors.extend(x_remake_state.get('anchors', []))
cached_lookups.extend(x_remake_state.get('content_lookups', []))
for child in descendants:
for child in page.descendants(placeholders=True):
# Cache target's page counters
anchor = child.style['anchor']
if anchor and anchor not in cached_anchors:

View File

@ -845,14 +845,14 @@ def find_in_flow_baseline(box, last=False, baseline_types=(boxes.LineBox,)):
# See https://www.w3.org/TR/css-align-3/#synthesize-baseline
if isinstance(box, baseline_types):
return box.position_y + box.baseline
if isinstance(box, boxes.ParentBox) and not isinstance(
box, boxes.TableCaptionBox):
children = reversed(box.children) if last else box.children
for child in children:
if child.is_in_normal_flow():
result = find_in_flow_baseline(child, last, baseline_types)
if result is not None:
return result
elif isinstance(box, boxes.TableCaptionBox):
return
children = reversed(box.children) if last else box.children
for child in children:
if child.is_in_normal_flow():
result = find_in_flow_baseline(child, last, baseline_types)
if result is not None:
return result
def distribute_excess_width(context, grid, excess_width, column_widths,