1
1
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:
Guillaume Ayoub 2011-10-03 18:57:26 +02:00
parent 75220039ce
commit 063c3b0331
7 changed files with 104 additions and 105 deletions

View File

@ -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."""

View File

@ -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 were 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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