Skip to content

Commit

Permalink
Fix "pages" counter in non-root absolute boxes
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
liZe committed Jan 29, 2024
1 parent e018bf3 commit c6468e5
Show file tree
Hide file tree
Showing 9 changed files with 67 additions and 48 deletions.
7 changes: 3 additions & 4 deletions tests/layout/test_inline.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
31 changes: 30 additions & 1 deletion tests/test_target.py
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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'
7 changes: 3 additions & 4 deletions weasyprint/draw.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
29 changes: 13 additions & 16 deletions weasyprint/formatting_structure/boxes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}>'
Expand Down Expand Up @@ -332,9 +342,6 @@ def __init__(self, element_tag, style, element, children):
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)
Expand All @@ -353,7 +360,7 @@ def remove_decoration(self, start, end):
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.
Expand All @@ -363,19 +370,9 @@ def copy_with_children(self, new_children):

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
Expand Down
8 changes: 4 additions & 4 deletions weasyprint/formatting_structure/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 = []
Expand Down
7 changes: 3 additions & 4 deletions weasyprint/layout/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,10 +316,9 @@ def get_string_or_element_for(self, store, page, name, keyword):
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
Expand Down
4 changes: 2 additions & 2 deletions weasyprint/layout/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -917,15 +917,15 @@ 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

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]

Expand Down
6 changes: 1 addition & 5 deletions weasyprint/layout/page.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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:
Expand Down
16 changes: 8 additions & 8 deletions weasyprint/layout/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit c6468e5

Please sign in to comment.