mirror of
https://github.com/Kozea/WeasyPrint.git
synced 2024-10-05 00:21:15 +03:00
Make the parent box children immutable
This commit is contained in:
parent
75220039ce
commit
063c3b0331
@ -67,8 +67,6 @@ See respective docstrings for details.
|
||||
"""
|
||||
|
||||
|
||||
import collections
|
||||
|
||||
from ..css import computed_from_cascaded
|
||||
from ..css.values import get_single_keyword
|
||||
|
||||
@ -296,26 +294,29 @@ class PageBox(Box):
|
||||
|
||||
class ParentBox(Box):
|
||||
"""A box that has children."""
|
||||
def __init__(self, document, element):
|
||||
def __init__(self, document, element, children):
|
||||
super(ParentBox, self).__init__(document, element)
|
||||
self.empty()
|
||||
self.children = tuple(children)
|
||||
# Kidnapping
|
||||
for child in self.children:
|
||||
child.parent = self
|
||||
|
||||
def enumerate_skip(self, skip_num=0):
|
||||
"""
|
||||
Yield ``(child, child_index)`` tuples for each children after skippng
|
||||
``skip_num`` of them.
|
||||
"""Yield ``(child, child_index)`` tuples for each child.
|
||||
|
||||
``skip_num`` children are skipped before iterating over them.
|
||||
|
||||
"""
|
||||
for index in xrange(skip_num, len(self.children)):
|
||||
yield index, self.children[index]
|
||||
|
||||
def empty(self):
|
||||
"""Initialize or empty the children list."""
|
||||
self.children = collections.deque()
|
||||
|
||||
def add_child(self, child):
|
||||
"""Add the new child to the children list and set its parent."""
|
||||
child.parent = self
|
||||
self.children.append(child)
|
||||
def copy_with_children(self, children):
|
||||
"""Create a new equivalent box with given ``children``."""
|
||||
new_box = self.copy()
|
||||
new_box.children = tuple(children)
|
||||
for child in new_box.children:
|
||||
child.parent = new_box
|
||||
return new_box
|
||||
|
||||
def descendants(self):
|
||||
"""A flat generator for a box, its children and descendants."""
|
||||
|
@ -80,37 +80,39 @@ def dom_to_box(document, element):
|
||||
# Specific handling for the element. (eg. replaced element)
|
||||
return result
|
||||
|
||||
if display in ('block', 'list-item'):
|
||||
box = boxes.BlockBox(document, element)
|
||||
if display == 'list-item':
|
||||
add_list_marker(box)
|
||||
elif display == 'inline':
|
||||
box = boxes.InlineBox(document, element)
|
||||
elif display == 'inline-block':
|
||||
box = boxes.InlineBlockBox(document, element)
|
||||
else:
|
||||
raise NotImplementedError('Unsupported display: ' + display)
|
||||
children = []
|
||||
|
||||
assert isinstance(box, boxes.ParentBox)
|
||||
if element.text:
|
||||
box.add_child(boxes.TextBox(document, element, element.text))
|
||||
children.append(boxes.TextBox(document, element, element.text))
|
||||
for child_element in element:
|
||||
# lxml.html already converts HTML entities to text.
|
||||
# Here we ignore comments and XML processing instructions.
|
||||
if isinstance(child_element.tag, basestring):
|
||||
child_box = dom_to_box(document, child_element)
|
||||
if child_box is not None:
|
||||
box.add_child(child_box)
|
||||
children.append(child_box)
|
||||
# else: child_element had `display: None`
|
||||
if child_element.tail:
|
||||
box.add_child(boxes.TextBox(
|
||||
children.append(boxes.TextBox(
|
||||
document, element, child_element.tail))
|
||||
|
||||
if display in ('block', 'list-item'):
|
||||
box = boxes.BlockBox(document, element, children)
|
||||
if display == 'list-item':
|
||||
box = create_box_marker(box)
|
||||
elif display == 'inline':
|
||||
box = boxes.InlineBox(document, element, children)
|
||||
elif display == 'inline-block':
|
||||
box = boxes.InlineBlockBox(document, element, children)
|
||||
else:
|
||||
raise NotImplementedError('Unsupported display: ' + display)
|
||||
assert isinstance(box, boxes.ParentBox)
|
||||
|
||||
return box
|
||||
|
||||
|
||||
def add_list_marker(box):
|
||||
"""Add a list marker to elements with ``display: list-item``.
|
||||
def create_box_marker(box):
|
||||
"""Return a box with a list marker to elements with ``display: list-item``.
|
||||
|
||||
See http://www.w3.org/TR/CSS21/generate.html#lists
|
||||
|
||||
@ -125,7 +127,7 @@ def add_list_marker(box):
|
||||
if surface is None:
|
||||
type_ = get_single_keyword(box.style.list_style_type)
|
||||
if type_ == 'none':
|
||||
return
|
||||
return box
|
||||
marker = GLYPH_LIST_MARKERS[type_]
|
||||
marker_box = boxes.TextBox(box.document, box.element, marker)
|
||||
else:
|
||||
@ -135,14 +137,13 @@ def add_list_marker(box):
|
||||
|
||||
position = get_single_keyword(box.style.list_style_position)
|
||||
if position == 'inside':
|
||||
assert not box.children # Make sure we’re adding at the beggining
|
||||
box.add_child(marker_box)
|
||||
# U+00A0, NO-BREAK SPACE
|
||||
spacer = boxes.TextBox(box.document, box.element, u'\u00a0')
|
||||
box.add_child(spacer)
|
||||
return box.copy_with_children((marker_box, spacer) + box.children)
|
||||
elif position == 'outside':
|
||||
box.outside_list_marker = marker_box
|
||||
marker_box.parent = box
|
||||
return box
|
||||
|
||||
|
||||
def process_whitespace(box):
|
||||
@ -230,41 +231,51 @@ def inline_in_block(box):
|
||||
]
|
||||
|
||||
"""
|
||||
for child_box in getattr(box, 'children', []):
|
||||
inline_in_block(child_box)
|
||||
new_children = []
|
||||
if isinstance(box, boxes.ParentBox):
|
||||
for child_box in getattr(box, 'children', []):
|
||||
new_child = inline_in_block(child_box)
|
||||
new_children.append(new_child)
|
||||
box = box.copy_with_children(new_children)
|
||||
|
||||
if not isinstance(box, boxes.BlockContainerBox):
|
||||
return
|
||||
return box
|
||||
|
||||
line_box = boxes.LineBox(box.document, box.element)
|
||||
children = box.children
|
||||
box.empty()
|
||||
for child_box in children:
|
||||
new_line_children = []
|
||||
new_children = []
|
||||
for child_box in box.children:
|
||||
if isinstance(child_box, boxes.BlockLevelBox):
|
||||
if line_box.children:
|
||||
if new_line_children:
|
||||
# Inlines are consecutive no more: add this line box
|
||||
# and create a new one.
|
||||
anonymous = boxes.AnonymousBlockBox(box.document, box.element)
|
||||
anonymous.add_child(line_box)
|
||||
box.add_child(anonymous)
|
||||
line_box = boxes.LineBox(box.document, box.element)
|
||||
box.add_child(child_box)
|
||||
line_box = boxes.LineBox(
|
||||
box.document, box.element, new_line_children)
|
||||
anonymous = boxes.AnonymousBlockBox(
|
||||
box.document, box.element, [line_box])
|
||||
new_children.append(anonymous)
|
||||
new_line_children = []
|
||||
new_children.append(child_box)
|
||||
elif isinstance(child_box, boxes.LineBox):
|
||||
# Merge the line box we just found with the new one we are making
|
||||
for child in child_box.children:
|
||||
line_box.add_child(child)
|
||||
new_line_children.append(child)
|
||||
else:
|
||||
line_box.add_child(child_box)
|
||||
if line_box.children:
|
||||
new_line_children.append(child_box)
|
||||
if new_line_children:
|
||||
# There were inlines at the end
|
||||
if box.children:
|
||||
anonymous = boxes.AnonymousBlockBox(box.document, box.element)
|
||||
anonymous.add_child(line_box)
|
||||
box.add_child(anonymous)
|
||||
if new_children:
|
||||
line_box = boxes.LineBox(
|
||||
box.document, box.element, new_line_children)
|
||||
anonymous = boxes.AnonymousBlockBox(
|
||||
box.document, box.element, [line_box])
|
||||
new_children.append(anonymous)
|
||||
else:
|
||||
# Only inline-level children: one line box
|
||||
box.add_child(line_box)
|
||||
return box
|
||||
line_box = boxes.LineBox(
|
||||
box.document, box.element, new_line_children)
|
||||
new_children.append(line_box)
|
||||
|
||||
return box.copy_with_children(new_children)
|
||||
|
||||
|
||||
def block_in_inline(box):
|
||||
@ -343,16 +354,16 @@ def block_in_inline(box):
|
||||
new_line, block, stack = _inner_block_in_inline(child, stack)
|
||||
if block is None:
|
||||
break
|
||||
anon = boxes.AnonymousBlockBox(box.document, box.element)
|
||||
anon.add_child(new_line)
|
||||
anon = boxes.AnonymousBlockBox(
|
||||
box.document, box.element, [new_line])
|
||||
new_children.append(anon)
|
||||
new_children.append(block_in_inline(block))
|
||||
# Loop with the same child and the new stack.
|
||||
if new_children:
|
||||
# Some children were already added, this became a block
|
||||
# context.
|
||||
new_child = boxes.AnonymousBlockBox(box.document, box.element)
|
||||
new_child.add_child(new_line)
|
||||
new_child = boxes.AnonymousBlockBox(
|
||||
box.document, box.element, [new_line])
|
||||
else:
|
||||
# Keep the single line box as-is, without anonymous blocks.
|
||||
new_child = new_line
|
||||
@ -365,22 +376,11 @@ def block_in_inline(box):
|
||||
new_children.append(new_child)
|
||||
|
||||
if changed:
|
||||
new_box = box.copy()
|
||||
new_box.empty()
|
||||
for new_child in new_children:
|
||||
new_box.add_child(new_child)
|
||||
return new_box
|
||||
return box.copy_with_children(new_children)
|
||||
else:
|
||||
return box
|
||||
|
||||
|
||||
def _add_anonymous_block(box, child):
|
||||
"""Wrap the child in an AnonymousBlockBox and add it to box."""
|
||||
anon_block = boxes.AnonymousBlockBox(box.document, box.element)
|
||||
anon_block.add_child(child)
|
||||
box.add_child(anon_block)
|
||||
|
||||
|
||||
def _inner_block_in_inline(box, skip_stack=None):
|
||||
"""Find a block-level box in an inline formatting context.
|
||||
|
||||
@ -393,8 +393,7 @@ def _inner_block_in_inline(box, skip_stack=None):
|
||||
``skip_stack``, return ``(new_box, None, None)``
|
||||
|
||||
"""
|
||||
new_box = box.copy()
|
||||
new_box.empty()
|
||||
new_children = []
|
||||
block_level_box = None
|
||||
resume_at = None
|
||||
changed = False
|
||||
@ -425,11 +424,13 @@ def _inner_block_in_inline(box, skip_stack=None):
|
||||
# block_level_box is still None.
|
||||
if new_child is not child:
|
||||
changed = True
|
||||
new_box.add_child(new_child)
|
||||
new_children.append(new_child)
|
||||
if block_level_box is not None:
|
||||
resume_at = (index, resume_at)
|
||||
box = box.copy_with_children(new_children)
|
||||
break
|
||||
else:
|
||||
if not (changed or skip):
|
||||
new_box = box
|
||||
return new_box, block_level_box, resume_at
|
||||
if changed or skip:
|
||||
box = box.copy_with_children(new_children)
|
||||
|
||||
return box, block_level_box, resume_at
|
||||
|
@ -102,9 +102,7 @@ def make_text_box(document, element, text):
|
||||
"""
|
||||
text_box = boxes.TextBox(document, element, text)
|
||||
if is_block_level(document, element):
|
||||
block = boxes.BlockBox(document, element)
|
||||
block.add_child(text_box)
|
||||
return block
|
||||
return boxes.BlockBox(document, element, [text_box])
|
||||
else:
|
||||
return text_box
|
||||
|
||||
|
@ -149,8 +149,7 @@ def block_level_height(box, max_position_y, skip_stack):
|
||||
position_y = box.content_box_y()
|
||||
initial_position_y = position_y
|
||||
|
||||
new_box = box.copy()
|
||||
new_box.empty()
|
||||
new_children = []
|
||||
|
||||
if skip_stack is None:
|
||||
skip = 0
|
||||
@ -169,7 +168,7 @@ def block_level_height(box, max_position_y, skip_stack):
|
||||
child, max_position_y, skip_stack)
|
||||
skip_stack = None
|
||||
for line in lines:
|
||||
new_box.add_child(line)
|
||||
new_children.append(line)
|
||||
position_y += line.height
|
||||
if resume_at is not None:
|
||||
resume_at = (index, resume_at)
|
||||
@ -181,7 +180,7 @@ def block_level_height(box, max_position_y, skip_stack):
|
||||
new_position_y = position_y + new_child.margin_height()
|
||||
# TODO: find a way to break between blocks
|
||||
# if new_position_y <= max_position_y:
|
||||
new_box.add_child(new_child)
|
||||
new_children.append(new_child)
|
||||
position_y = new_position_y
|
||||
# else:
|
||||
# resume_at = (index, None) # or something... XXX
|
||||
@ -191,6 +190,8 @@ def block_level_height(box, max_position_y, skip_stack):
|
||||
else:
|
||||
resume_at = None
|
||||
|
||||
new_box = box.copy_with_children(new_children)
|
||||
|
||||
if new_box.height == 'auto':
|
||||
new_box.height = position_y - initial_position_y
|
||||
|
||||
|
@ -260,17 +260,10 @@ def is_empty_line(linebox):
|
||||
return num_textbox == len(linebox.children)
|
||||
|
||||
|
||||
def get_new_empty_line(linebox):
|
||||
"""Return empty copy of ``linebox``."""
|
||||
new_line = linebox.copy()
|
||||
new_line.empty()
|
||||
return new_line
|
||||
|
||||
|
||||
def layout_next_linebox(linebox, remaining_width, skip_stack):
|
||||
"""Same as split_inline_level."""
|
||||
assert isinstance(linebox, boxes.LineBox)
|
||||
new_line = get_new_empty_line(linebox)
|
||||
children = []
|
||||
|
||||
if skip_stack is None:
|
||||
skip = 0
|
||||
@ -283,14 +276,14 @@ def layout_next_linebox(linebox, remaining_width, skip_stack):
|
||||
skip_stack = None
|
||||
|
||||
margin_width = new_child.margin_width()
|
||||
if margin_width > remaining_width and new_line.children:
|
||||
if margin_width > remaining_width and children:
|
||||
# too wide, and the inline is non-empty:
|
||||
# put child entirely on the next line.
|
||||
resume_at = (index, None)
|
||||
break
|
||||
else:
|
||||
remaining_width -= margin_width
|
||||
new_line.add_child(new_child)
|
||||
children.append(new_child)
|
||||
|
||||
if resume_at is not None:
|
||||
resume_at = (index, resume_at)
|
||||
@ -298,7 +291,7 @@ def layout_next_linebox(linebox, remaining_width, skip_stack):
|
||||
else:
|
||||
resume_at = None
|
||||
|
||||
return new_line, resume_at
|
||||
return linebox.copy_with_children(children), resume_at
|
||||
|
||||
|
||||
def split_inline_level(box, available_width, skip_stack):
|
||||
@ -342,8 +335,8 @@ def split_inline_box(inlinebox, remaining_width, skip_stack):
|
||||
inlinebox.border_right_width)
|
||||
remaining_width -= left_spacing
|
||||
|
||||
new_inlinebox = inlinebox.copy()
|
||||
new_inlinebox.empty()
|
||||
children = []
|
||||
reset_spacing = False
|
||||
|
||||
if skip_stack is None:
|
||||
skip = 0
|
||||
@ -351,7 +344,6 @@ def split_inline_box(inlinebox, remaining_width, skip_stack):
|
||||
skip, skip_stack = skip_stack
|
||||
|
||||
for index, child in inlinebox.enumerate_skip(skip):
|
||||
|
||||
new_child, resume_at = split_inline_level(
|
||||
child, remaining_width, skip_stack)
|
||||
skip_stack = None
|
||||
@ -365,23 +357,27 @@ def split_inline_box(inlinebox, remaining_width, skip_stack):
|
||||
|
||||
margin_width = new_child.margin_width()
|
||||
|
||||
if (margin_width > remaining_width and new_inlinebox.children):
|
||||
if (margin_width > remaining_width and children):
|
||||
# too wide, and the inline is non-empty:
|
||||
# put child entirely on the next line.
|
||||
resume_at = (index, None)
|
||||
break
|
||||
else:
|
||||
remaining_width -= margin_width
|
||||
new_inlinebox.add_child(new_child)
|
||||
children.append(new_child)
|
||||
|
||||
if resume_at is not None:
|
||||
inlinebox.reset_spacing('left')
|
||||
new_inlinebox.reset_spacing('right')
|
||||
reset_spacing = True
|
||||
resume_at = (index, resume_at)
|
||||
break
|
||||
else:
|
||||
resume_at = None
|
||||
|
||||
new_inlinebox = inlinebox.copy_with_children(children)
|
||||
if reset_spacing:
|
||||
inlinebox.reset_spacing('left')
|
||||
new_inlinebox.reset_spacing('right')
|
||||
|
||||
return new_inlinebox, resume_at
|
||||
|
||||
|
||||
|
@ -411,6 +411,7 @@ def test_empty_linebox():
|
||||
def test_breaking_linebox():
|
||||
"""Test lineboxes breaks with a lot of text and deep nesting."""
|
||||
def get_paragraph_linebox(width, font_size):
|
||||
"""Helper returning a paragraph with given style."""
|
||||
page = u'''
|
||||
<style>
|
||||
p { font-size: %(font_size)spx;
|
||||
|
@ -106,6 +106,7 @@ class TextFragment(object):
|
||||
def get_baseline(self):
|
||||
"""Get the baseline of the text."""
|
||||
# TODO: use introspection to get the descent
|
||||
descent = self.layout.get_context().get_metrics(None, None).get_descent()
|
||||
context = self.layout.get_context()
|
||||
descent = context.get_metrics(None, None).get_descent()
|
||||
_width, height = self.get_size()
|
||||
return height - descent / Pango.SCALE
|
||||
|
Loading…
Reference in New Issue
Block a user