2011-05-17 13:29:00 +04:00
|
|
|
|
# coding: utf8
|
2012-03-22 02:19:27 +04:00
|
|
|
|
"""
|
|
|
|
|
weasyprint.formatting_structure.boxes
|
|
|
|
|
-------------------------------------
|
2011-05-17 13:29:00 +04:00
|
|
|
|
|
2012-03-22 02:19:27 +04:00
|
|
|
|
Classes for all types of boxes in the CSS formatting structure / box model.
|
2011-05-17 13:29:00 +04:00
|
|
|
|
|
2012-03-22 02:19:27 +04:00
|
|
|
|
See http://www.w3.org/TR/CSS21/visuren.html
|
2011-05-25 16:59:42 +04:00
|
|
|
|
|
2012-03-22 02:19:27 +04:00
|
|
|
|
Names are the same as in CSS 2.1 with the exception of ``TextBox``. In
|
|
|
|
|
WeasyPrint, any text is in a ``TextBox``. What CSS calls anonymous
|
|
|
|
|
inline boxes are text boxes but not all text boxes are anonymous
|
|
|
|
|
inline boxes.
|
2011-05-25 16:59:42 +04:00
|
|
|
|
|
2012-03-22 02:19:27 +04:00
|
|
|
|
See http://www.w3.org/TR/CSS21/visuren.html#anonymous
|
2011-05-25 16:59:42 +04:00
|
|
|
|
|
2012-03-22 02:19:27 +04:00
|
|
|
|
Abstract classes, should not be instantiated:
|
2011-05-25 16:59:42 +04:00
|
|
|
|
|
2012-03-22 02:19:27 +04:00
|
|
|
|
* Box
|
|
|
|
|
* BlockLevelBox
|
|
|
|
|
* InlineLevelBox
|
|
|
|
|
* BlockContainerBox
|
|
|
|
|
* ReplacedBox
|
|
|
|
|
* ParentBox
|
|
|
|
|
* AtomicInlineLevelBox
|
2011-05-25 16:59:42 +04:00
|
|
|
|
|
2012-03-22 02:19:27 +04:00
|
|
|
|
Concrete classes:
|
2011-05-25 16:59:42 +04:00
|
|
|
|
|
2012-03-22 02:19:27 +04:00
|
|
|
|
* PageBox
|
|
|
|
|
* BlockBox
|
|
|
|
|
* InlineBox
|
|
|
|
|
* InlineBlockBox
|
|
|
|
|
* BlockReplacedBox
|
|
|
|
|
* InlineReplacedBox
|
|
|
|
|
* TextBox
|
|
|
|
|
* LineBox
|
|
|
|
|
* Various table-related Box subclasses
|
2011-05-25 16:59:42 +04:00
|
|
|
|
|
2012-03-22 02:19:27 +04:00
|
|
|
|
All concrete box classes whose name contains "Inline" or "Block" have
|
|
|
|
|
one of the following "outside" behavior:
|
2011-05-25 16:59:42 +04:00
|
|
|
|
|
2012-03-22 02:19:27 +04:00
|
|
|
|
* Block-level (inherits from :class:`BlockLevelBox`)
|
|
|
|
|
* Inline-level (inherits from :class:`InlineLevelBox`)
|
2011-05-25 16:59:42 +04:00
|
|
|
|
|
2012-03-22 02:19:27 +04:00
|
|
|
|
and one of the following "inside" behavior:
|
2011-05-25 16:59:42 +04:00
|
|
|
|
|
2012-03-22 02:19:27 +04:00
|
|
|
|
* Block container (inherits from :class:`BlockContainerBox`)
|
|
|
|
|
* Inline content (InlineBox and :class:`TextBox`)
|
|
|
|
|
* Replaced content (inherits from :class:`ReplacedBox`)
|
2011-05-25 16:59:42 +04:00
|
|
|
|
|
2012-03-22 02:19:27 +04:00
|
|
|
|
... with various combinasions of both.
|
2011-05-25 16:59:42 +04:00
|
|
|
|
|
2012-03-22 02:19:27 +04:00
|
|
|
|
See respective docstrings for details.
|
2011-05-25 16:59:42 +04:00
|
|
|
|
|
2012-03-22 02:19:27 +04:00
|
|
|
|
:copyright: Copyright 2011-2012 Simon Sapin and contributors, see AUTHORS.
|
|
|
|
|
:license: BSD, see LICENSE for details.
|
2011-05-25 16:59:42 +04:00
|
|
|
|
|
2012-03-22 02:19:27 +04:00
|
|
|
|
"""
|
2011-05-17 13:29:00 +04:00
|
|
|
|
|
2012-02-17 21:49:58 +04:00
|
|
|
|
from __future__ import division, unicode_literals
|
|
|
|
|
|
|
|
|
|
from ..compat import xrange
|
2012-04-03 16:59:06 +04:00
|
|
|
|
from ..css.computed_values import ZERO_PIXELS
|
2012-02-17 21:49:58 +04:00
|
|
|
|
|
|
|
|
|
|
2011-08-23 19:57:23 +04:00
|
|
|
|
# The *Box classes have many attributes and methods, but that's the way it is
|
|
|
|
|
# pylint: disable=R0904,R0902
|
|
|
|
|
|
2011-05-19 17:31:34 +04:00
|
|
|
|
class Box(object):
|
2011-08-24 11:41:38 +04:00
|
|
|
|
"""Abstract base class for all boxes."""
|
2011-11-14 17:29:40 +04:00
|
|
|
|
# Definitions for the rules generating anonymous table boxes
|
|
|
|
|
# http://www.w3.org/TR/CSS21/tables.html#anonymous-boxes
|
|
|
|
|
proper_table_child = False
|
|
|
|
|
internal_table_or_caption = False
|
2011-11-16 20:34:02 +04:00
|
|
|
|
tabular_container = False
|
2011-11-14 17:29:40 +04:00
|
|
|
|
|
2011-11-22 16:06:50 +04:00
|
|
|
|
# Default, may be overriden on instances.
|
|
|
|
|
is_table_wrapper = False
|
2012-02-28 17:50:48 +04:00
|
|
|
|
is_for_root_element = False
|
2012-05-15 21:29:54 +04:00
|
|
|
|
bookmark_label = None
|
2012-05-21 16:22:32 +04:00
|
|
|
|
bookmark_level = None
|
2011-11-14 17:29:40 +04:00
|
|
|
|
|
2011-12-05 18:25:42 +04:00
|
|
|
|
def __init__(self, element_tag, sourceline, style):
|
2011-12-02 15:36:20 +04:00
|
|
|
|
self.element_tag = element_tag
|
|
|
|
|
self.sourceline = sourceline # for debugging only
|
2011-12-02 14:31:06 +04:00
|
|
|
|
# Copying might not be needed, but let’s be careful with mutable
|
|
|
|
|
# objects.
|
|
|
|
|
self.style = style.copy()
|
2012-05-21 16:22:32 +04:00
|
|
|
|
bookmark_level = style.bookmark_level
|
|
|
|
|
if bookmark_level != 'none':
|
|
|
|
|
self.bookmark_level = bookmark_level
|
2011-06-29 16:04:42 +04:00
|
|
|
|
|
2011-07-20 13:35:43 +04:00
|
|
|
|
def __repr__(self):
|
2011-12-28 18:34:30 +04:00
|
|
|
|
return '<%s %s %s>' % (
|
2011-12-02 15:36:20 +04:00
|
|
|
|
type(self).__name__, self.element_tag, self.sourceline)
|
2011-07-20 13:35:43 +04:00
|
|
|
|
|
2011-12-02 14:31:06 +04:00
|
|
|
|
@classmethod
|
|
|
|
|
def anonymous_from(cls, parent, *args, **kwargs):
|
|
|
|
|
"""Return an anonymous box that inherits from ``parent``."""
|
2011-12-02 18:31:23 +04:00
|
|
|
|
return cls(parent.element_tag, parent.sourceline,
|
2011-12-02 14:31:06 +04:00
|
|
|
|
parent.style.inherit_from(),
|
|
|
|
|
*args, **kwargs)
|
|
|
|
|
|
2011-11-17 18:39:30 +04:00
|
|
|
|
def copy(self):
|
2011-07-20 13:35:43 +04:00
|
|
|
|
"""Return shallow copy of the box."""
|
|
|
|
|
cls = type(self)
|
|
|
|
|
# Create a new instance without calling __init__: initializing
|
|
|
|
|
# styles may be kinda expensive, no need to do it again.
|
|
|
|
|
new_box = cls.__new__(cls)
|
|
|
|
|
# Copy attributes
|
|
|
|
|
new_box.__dict__.update(self.__dict__)
|
2011-08-17 16:01:06 +04:00
|
|
|
|
new_box.style = self.style.copy()
|
2011-07-20 13:35:43 +04:00
|
|
|
|
return new_box
|
2011-07-15 20:07:49 +04:00
|
|
|
|
|
2011-11-24 20:56:05 +04:00
|
|
|
|
def translate(self, dx=0, dy=0):
|
2011-08-24 11:41:38 +04:00
|
|
|
|
"""Change the box’s position.
|
|
|
|
|
|
|
|
|
|
Also update the children’s positions accordingly.
|
|
|
|
|
|
2011-08-11 14:05:40 +04:00
|
|
|
|
"""
|
2011-08-22 13:38:54 +04:00
|
|
|
|
# Overridden in ParentBox to also translate children, if any.
|
2011-11-24 20:56:05 +04:00
|
|
|
|
self.position_x += dx
|
|
|
|
|
self.position_y += dy
|
2011-08-11 14:05:40 +04:00
|
|
|
|
|
2011-08-24 11:41:38 +04:00
|
|
|
|
# Heights and widths
|
2011-07-22 20:16:31 +04:00
|
|
|
|
|
2011-07-12 19:53:15 +04:00
|
|
|
|
def padding_width(self):
|
2011-07-22 16:53:51 +04:00
|
|
|
|
"""Width of the padding box."""
|
2011-07-12 19:53:15 +04:00
|
|
|
|
return self.width + self.padding_left + self.padding_right
|
2011-05-19 17:31:34 +04:00
|
|
|
|
|
2011-07-12 19:53:15 +04:00
|
|
|
|
def padding_height(self):
|
2011-07-22 16:53:51 +04:00
|
|
|
|
"""Height of the padding box."""
|
2011-07-12 19:53:15 +04:00
|
|
|
|
return self.height + self.padding_top + self.padding_bottom
|
2011-05-25 16:59:42 +04:00
|
|
|
|
|
2011-07-12 19:53:15 +04:00
|
|
|
|
def border_width(self):
|
2011-07-22 16:53:51 +04:00
|
|
|
|
"""Width of the border box."""
|
2011-12-30 20:19:02 +04:00
|
|
|
|
return self.padding_width() + self.border_left_width + \
|
|
|
|
|
self.border_right_width
|
2011-05-25 16:59:42 +04:00
|
|
|
|
|
2011-07-12 19:53:15 +04:00
|
|
|
|
def border_height(self):
|
2011-07-22 16:53:51 +04:00
|
|
|
|
"""Height of the border box."""
|
2011-12-30 20:19:02 +04:00
|
|
|
|
return self.padding_height() + self.border_top_width + \
|
|
|
|
|
self.border_bottom_width
|
2011-07-15 20:07:49 +04:00
|
|
|
|
|
2011-07-22 16:53:51 +04:00
|
|
|
|
def margin_width(self):
|
|
|
|
|
"""Width of the margin box (aka. outer box)."""
|
|
|
|
|
return self.border_width() + self.margin_left + self.margin_right
|
|
|
|
|
|
|
|
|
|
def margin_height(self):
|
|
|
|
|
"""Height of the margin box (aka. outer box)."""
|
|
|
|
|
return self.border_height() + self.margin_top + self.margin_bottom
|
|
|
|
|
|
2011-08-24 11:41:38 +04:00
|
|
|
|
# Corners positions
|
2011-08-09 18:08:58 +04:00
|
|
|
|
|
2011-07-22 16:53:51 +04:00
|
|
|
|
def content_box_x(self):
|
|
|
|
|
"""Absolute horizontal position of the content box."""
|
|
|
|
|
return self.position_x + self.margin_left + self.padding_left + \
|
2011-12-30 20:19:02 +04:00
|
|
|
|
self.border_left_width
|
2011-07-22 16:53:51 +04:00
|
|
|
|
|
|
|
|
|
def content_box_y(self):
|
|
|
|
|
"""Absolute vertical position of the content box."""
|
|
|
|
|
return self.position_y + self.margin_top + self.padding_top + \
|
2011-12-30 20:19:02 +04:00
|
|
|
|
self.border_top_width
|
2011-07-22 16:53:51 +04:00
|
|
|
|
|
2011-08-09 18:08:58 +04:00
|
|
|
|
def padding_box_x(self):
|
|
|
|
|
"""Absolute horizontal position of the padding box."""
|
2011-12-30 20:19:02 +04:00
|
|
|
|
return self.position_x + self.margin_left + self.border_left_width
|
2011-08-09 18:08:58 +04:00
|
|
|
|
|
|
|
|
|
def padding_box_y(self):
|
|
|
|
|
"""Absolute vertical position of the padding box."""
|
2011-12-30 20:19:02 +04:00
|
|
|
|
return self.position_y + self.margin_top + self.border_top_width
|
2011-08-09 18:08:58 +04:00
|
|
|
|
|
|
|
|
|
def border_box_x(self):
|
|
|
|
|
"""Absolute horizontal position of the border box."""
|
|
|
|
|
return self.position_x + self.margin_left
|
|
|
|
|
|
|
|
|
|
def border_box_y(self):
|
|
|
|
|
"""Absolute vertical position of the border box."""
|
|
|
|
|
return self.position_y + self.margin_top
|
|
|
|
|
|
2012-06-15 00:04:48 +04:00
|
|
|
|
def hit_area(self):
|
|
|
|
|
"""Return the (x, y, w, h) rectangle where the box is clickable."""
|
|
|
|
|
# "Border area. That's the area that hit-testing is done on."
|
|
|
|
|
# http://lists.w3.org/Archives/Public/www-style/2012Jun/0318.html
|
|
|
|
|
return (self.border_box_x(), self.border_box_y(),
|
|
|
|
|
self.border_width(), self.border_height())
|
2012-04-10 16:37:54 +04:00
|
|
|
|
|
2011-08-24 11:41:38 +04:00
|
|
|
|
# Positioning schemes
|
2011-07-22 20:16:31 +04:00
|
|
|
|
|
|
|
|
|
def is_floated(self):
|
|
|
|
|
"""Return whether this box is floated."""
|
2011-10-08 16:41:12 +04:00
|
|
|
|
return self.style.float != 'none'
|
2011-07-22 20:16:31 +04:00
|
|
|
|
|
|
|
|
|
def is_absolutely_positioned(self):
|
|
|
|
|
"""Return whether this box is in the absolute positioning scheme."""
|
2011-10-08 16:41:12 +04:00
|
|
|
|
return self.style.position in ('absolute', 'fixed')
|
2011-07-22 20:16:31 +04:00
|
|
|
|
|
|
|
|
|
def is_in_normal_flow(self):
|
|
|
|
|
"""Return whether this box is in normal flow."""
|
|
|
|
|
return not (self.is_floated() or self.is_absolutely_positioned())
|
|
|
|
|
|
2011-05-25 16:59:42 +04:00
|
|
|
|
|
2011-07-11 14:47:00 +04:00
|
|
|
|
class ParentBox(Box):
|
2011-08-24 11:41:38 +04:00
|
|
|
|
"""A box that has children."""
|
2011-12-05 18:25:42 +04:00
|
|
|
|
def __init__(self, element_tag, sourceline, style, children):
|
|
|
|
|
super(ParentBox, self).__init__(element_tag, sourceline, style)
|
2011-10-03 20:57:26 +04:00
|
|
|
|
self.children = tuple(children)
|
2011-07-11 14:47:00 +04:00
|
|
|
|
|
2011-09-29 19:18:10 +04:00
|
|
|
|
def enumerate_skip(self, skip_num=0):
|
2011-10-03 20:57:26 +04:00
|
|
|
|
"""Yield ``(child, child_index)`` tuples for each child.
|
|
|
|
|
|
|
|
|
|
``skip_num`` children are skipped before iterating over them.
|
|
|
|
|
|
2011-09-29 19:18:10 +04:00
|
|
|
|
"""
|
|
|
|
|
for index in xrange(skip_num, len(self.children)):
|
|
|
|
|
yield index, self.children[index]
|
|
|
|
|
|
2012-05-21 16:22:32 +04:00
|
|
|
|
def _reset_spacing(self, side):
|
|
|
|
|
"""Set to 0 the margin, padding and border of ``side``."""
|
|
|
|
|
setattr(self, 'margin_%s' % side, 0)
|
|
|
|
|
setattr(self, 'padding_%s' % side, 0)
|
|
|
|
|
setattr(self, 'border_%s_width' % side, 0)
|
|
|
|
|
|
|
|
|
|
self.style['margin_%s' % side] = ZERO_PIXELS
|
|
|
|
|
self.style['padding_%s' % side] = ZERO_PIXELS
|
|
|
|
|
self.style['border_%s_width' % side] = 0
|
|
|
|
|
|
|
|
|
|
def _remove_decoration(self, start, end):
|
|
|
|
|
if start:
|
|
|
|
|
self._reset_spacing('top')
|
|
|
|
|
if end:
|
|
|
|
|
self._reset_spacing('bottom')
|
|
|
|
|
|
|
|
|
|
def copy_with_children(self, children, is_start=True, is_end=True):
|
2011-10-03 20:57:26 +04:00
|
|
|
|
"""Create a new equivalent box with given ``children``."""
|
2011-11-17 18:39:30 +04:00
|
|
|
|
new_box = self.copy()
|
2011-10-03 20:57:26 +04:00
|
|
|
|
new_box.children = tuple(children)
|
2012-05-21 16:22:32 +04:00
|
|
|
|
if not is_start:
|
|
|
|
|
new_box.outside_list_marker = None
|
|
|
|
|
if new_box.bookmark_level:
|
|
|
|
|
del new_box.bookmark_level
|
|
|
|
|
new_box._remove_decoration(not is_start, not is_end)
|
2011-10-03 20:57:26 +04:00
|
|
|
|
return new_box
|
2011-07-11 14:47:00 +04:00
|
|
|
|
|
|
|
|
|
def descendants(self):
|
|
|
|
|
"""A flat generator for a box, its children and descendants."""
|
|
|
|
|
yield self
|
2011-10-08 16:41:12 +04:00
|
|
|
|
for child in self.children:
|
2011-07-11 14:47:00 +04:00
|
|
|
|
if hasattr(child, 'descendants'):
|
|
|
|
|
for grand_child in child.descendants():
|
|
|
|
|
yield grand_child
|
|
|
|
|
else:
|
|
|
|
|
yield child
|
|
|
|
|
|
2011-11-24 20:56:05 +04:00
|
|
|
|
def translate(self, dx=0, dy=0):
|
2011-08-24 11:41:38 +04:00
|
|
|
|
"""Change the position of the box.
|
|
|
|
|
|
|
|
|
|
Also update the children’s positions accordingly.
|
|
|
|
|
|
2011-08-11 14:05:40 +04:00
|
|
|
|
"""
|
2011-11-24 20:56:05 +04:00
|
|
|
|
super(ParentBox, self).translate(dx, dy)
|
2011-08-11 14:05:40 +04:00
|
|
|
|
for child in self.children:
|
2011-11-24 20:56:05 +04:00
|
|
|
|
child.translate(dx, dy)
|
2011-08-11 14:05:40 +04:00
|
|
|
|
|
2012-06-15 00:04:48 +04:00
|
|
|
|
def get_wrapped_table(self):
|
|
|
|
|
"""Get the table wrapped by the box."""
|
|
|
|
|
if self.is_table_wrapper:
|
|
|
|
|
for child in self.children:
|
|
|
|
|
if isinstance(child, TableBox):
|
|
|
|
|
return child
|
|
|
|
|
else: # pragma: no cover
|
|
|
|
|
raise ValueError('Table wrapper without a table')
|
|
|
|
|
|
2011-07-11 14:47:00 +04:00
|
|
|
|
|
|
|
|
|
class BlockLevelBox(Box):
|
2011-08-24 11:41:38 +04:00
|
|
|
|
"""A box that participates in an block formatting context.
|
|
|
|
|
|
|
|
|
|
An element with a ``display`` value of ``block``, ``list-item`` or
|
|
|
|
|
``table`` generates a block-level box.
|
2011-07-11 14:47:00 +04:00
|
|
|
|
|
|
|
|
|
"""
|
2012-06-19 16:52:30 +04:00
|
|
|
|
clearance = None
|
2011-07-11 14:47:00 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BlockContainerBox(ParentBox):
|
2011-08-24 11:41:38 +04:00
|
|
|
|
"""A box that contains only block-level boxes or only line boxes.
|
|
|
|
|
|
2011-07-11 14:47:00 +04:00
|
|
|
|
A box that either contains only block-level boxes or establishes an inline
|
|
|
|
|
formatting context and thus contains only line boxes.
|
|
|
|
|
|
2011-08-24 11:41:38 +04:00
|
|
|
|
A non-replaced element with a ``display`` value of ``block``,
|
|
|
|
|
``list-item``, ``inline-block`` or 'table-cell' generates a block container
|
|
|
|
|
box.
|
|
|
|
|
|
2011-07-11 14:47:00 +04:00
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
2011-05-25 16:59:42 +04:00
|
|
|
|
class BlockBox(BlockContainerBox, BlockLevelBox):
|
2011-08-24 11:41:38 +04:00
|
|
|
|
"""A block-level box that is also a block container.
|
2011-06-29 16:04:42 +04:00
|
|
|
|
|
2011-08-24 11:41:38 +04:00
|
|
|
|
A non-replaced element with a ``display`` value of ``block``, ``list-item``
|
2011-05-25 16:59:42 +04:00
|
|
|
|
generates a block box.
|
2011-08-24 11:41:38 +04:00
|
|
|
|
|
2011-05-23 16:31:14 +04:00
|
|
|
|
"""
|
2012-02-23 22:30:31 +04:00
|
|
|
|
# TODO: remove this when outside list marker are absolute children
|
|
|
|
|
def translate(self, dx=0, dy=0):
|
|
|
|
|
"""Change the position of the box.
|
|
|
|
|
|
|
|
|
|
Also update the children’s positions accordingly.
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
super(BlockBox, self).translate(dx, dy)
|
|
|
|
|
marker = getattr(self, 'outside_list_marker', None)
|
|
|
|
|
if marker:
|
|
|
|
|
marker.translate(dx, dy)
|
2011-05-19 17:31:34 +04:00
|
|
|
|
|
|
|
|
|
|
2011-11-08 18:24:31 +04:00
|
|
|
|
class LineBox(ParentBox):
|
2011-08-24 11:41:38 +04:00
|
|
|
|
"""A box that represents a line in an inline formatting context.
|
|
|
|
|
|
|
|
|
|
Can only contain inline-level boxes.
|
2011-06-29 16:04:42 +04:00
|
|
|
|
|
2011-05-23 16:31:14 +04:00
|
|
|
|
In early stages of building the box tree a single line box contains many
|
2011-07-01 20:20:30 +04:00
|
|
|
|
consecutive inline boxes. Later, during layout phase, each line boxes will
|
|
|
|
|
be split into multiple line boxes, one for each actual line.
|
2011-08-24 11:41:38 +04:00
|
|
|
|
|
2011-05-23 16:31:14 +04:00
|
|
|
|
"""
|
2011-12-05 18:25:42 +04:00
|
|
|
|
def __init__(self, element_tag, sourceline, style, children):
|
|
|
|
|
assert style.anonymous
|
|
|
|
|
super(LineBox, self).__init__(element_tag, sourceline, style, children)
|
2011-05-19 17:31:34 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class InlineLevelBox(Box):
|
2011-08-24 11:41:38 +04:00
|
|
|
|
"""A box that participates in an inline formatting context.
|
|
|
|
|
|
|
|
|
|
An inline-level box that is not an inline box is said to be "atomic". Such
|
|
|
|
|
boxes are inline blocks, replaced elements and inline tables.
|
2011-06-29 16:04:42 +04:00
|
|
|
|
|
2011-08-24 11:41:38 +04:00
|
|
|
|
An element with a ``display`` value of ``inline``, ``inline-table``, or
|
|
|
|
|
``inline-block`` generates an inline-level box.
|
2011-05-25 16:59:42 +04:00
|
|
|
|
|
2011-05-23 16:31:14 +04:00
|
|
|
|
"""
|
2012-05-21 16:22:32 +04:00
|
|
|
|
def _remove_decoration(self, start, end):
|
|
|
|
|
ltr = self.style.direction == 'ltr'
|
|
|
|
|
if start:
|
|
|
|
|
self._reset_spacing('left' if ltr else 'right')
|
|
|
|
|
if end:
|
|
|
|
|
self._reset_spacing('right' if ltr else 'left')
|
2011-05-19 17:31:34 +04:00
|
|
|
|
|
|
|
|
|
|
2011-07-11 14:47:00 +04:00
|
|
|
|
class InlineBox(InlineLevelBox, ParentBox):
|
2011-08-24 11:41:38 +04:00
|
|
|
|
"""An inline box with inline children.
|
|
|
|
|
|
|
|
|
|
A box that participates in an inline formatting context and whose content
|
2011-05-25 16:59:42 +04:00
|
|
|
|
also participates in that inline formatting context.
|
2011-06-29 16:04:42 +04:00
|
|
|
|
|
2011-08-24 11:41:38 +04:00
|
|
|
|
A non-replaced element with a ``display`` value of ``inline`` generates an
|
2011-05-25 16:59:42 +04:00
|
|
|
|
inline box.
|
2011-08-24 11:41:38 +04:00
|
|
|
|
|
2011-05-25 16:59:42 +04:00
|
|
|
|
"""
|
2012-06-15 00:04:48 +04:00
|
|
|
|
def hit_area(self):
|
|
|
|
|
"""Return the (x, y, w, h) rectangle where the box is clickable."""
|
|
|
|
|
# Use line-height (margin_height) rather than border_height
|
|
|
|
|
return (self.border_box_x(), self.position_y,
|
|
|
|
|
self.border_width(), self.margin_height())
|
2011-05-25 16:59:42 +04:00
|
|
|
|
|
|
|
|
|
|
2011-11-08 18:24:31 +04:00
|
|
|
|
class TextBox(InlineLevelBox):
|
2011-08-24 11:41:38 +04:00
|
|
|
|
"""A box that contains only text and has no box children.
|
2011-06-29 16:04:42 +04:00
|
|
|
|
|
2011-05-25 16:59:42 +04:00
|
|
|
|
Any text in the document ends up in a text box. What CSS calls "anonymous
|
|
|
|
|
inline boxes" are also text boxes.
|
2011-08-24 11:41:38 +04:00
|
|
|
|
|
2011-05-23 16:31:14 +04:00
|
|
|
|
"""
|
2011-12-05 18:25:42 +04:00
|
|
|
|
def __init__(self, element_tag, sourceline, style, text):
|
|
|
|
|
assert style.anonymous
|
2011-09-30 19:18:53 +04:00
|
|
|
|
assert text
|
2011-12-05 21:03:25 +04:00
|
|
|
|
super(TextBox, self).__init__(element_tag, sourceline, style)
|
|
|
|
|
text_transform = style.text_transform
|
|
|
|
|
if text_transform != 'none':
|
|
|
|
|
text = {
|
|
|
|
|
'uppercase': lambda t: t.upper(),
|
|
|
|
|
'lowercase': lambda t: t.lower(),
|
|
|
|
|
# Python’s unicode.captitalize is not the same.
|
|
|
|
|
'capitalize': lambda t: t.title(),
|
|
|
|
|
}[text_transform](text)
|
2011-11-08 16:09:19 +04:00
|
|
|
|
self.text = text
|
2011-05-19 17:31:34 +04:00
|
|
|
|
|
2011-11-08 16:09:19 +04:00
|
|
|
|
def copy_with_text(self, text):
|
2011-12-26 15:47:26 +04:00
|
|
|
|
"""Return a new TextBox identical to this one except for the text."""
|
2011-11-08 16:09:19 +04:00
|
|
|
|
assert text
|
2011-11-17 18:39:30 +04:00
|
|
|
|
new_box = self.copy()
|
2011-11-08 16:09:19 +04:00
|
|
|
|
new_box.text = text
|
2011-10-04 16:56:23 +04:00
|
|
|
|
return new_box
|
|
|
|
|
|
2011-05-19 17:31:34 +04:00
|
|
|
|
|
2011-07-20 15:29:38 +04:00
|
|
|
|
class AtomicInlineLevelBox(InlineLevelBox):
|
2011-08-24 11:41:38 +04:00
|
|
|
|
"""An atomic box in an inline formatting context.
|
|
|
|
|
|
|
|
|
|
This inline-level box cannot be split for line breaks.
|
|
|
|
|
|
2011-07-20 15:29:38 +04:00
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class InlineBlockBox(AtomicInlineLevelBox, BlockContainerBox):
|
2011-08-24 11:41:38 +04:00
|
|
|
|
"""A box that is both inline-level and a block container.
|
|
|
|
|
|
|
|
|
|
It behaves as inline on the outside and as a block on the inside.
|
2011-05-25 16:59:42 +04:00
|
|
|
|
|
2011-07-20 15:29:38 +04:00
|
|
|
|
A non-replaced element with a 'display' value of 'inline-block' generates
|
|
|
|
|
an inline-block box.
|
2011-08-24 11:41:38 +04:00
|
|
|
|
|
2011-05-25 16:59:42 +04:00
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ReplacedBox(Box):
|
2011-08-24 11:41:38 +04:00
|
|
|
|
"""A box whose content is replaced.
|
|
|
|
|
|
|
|
|
|
For example, ``<img>`` are replaced: their content is rendered externally
|
|
|
|
|
and is opaque from CSS’s point of view.
|
|
|
|
|
|
2011-05-25 16:59:42 +04:00
|
|
|
|
"""
|
2011-12-05 18:25:42 +04:00
|
|
|
|
def __init__(self, element_tag, sourceline, style, replacement):
|
|
|
|
|
super(ReplacedBox, self).__init__(element_tag, sourceline, style)
|
2011-05-25 17:54:46 +04:00
|
|
|
|
self.replacement = replacement
|
2011-05-25 16:59:42 +04:00
|
|
|
|
|
|
|
|
|
|
2011-12-05 17:24:43 +04:00
|
|
|
|
class BlockReplacedBox(ReplacedBox, BlockLevelBox):
|
2011-08-24 11:41:38 +04:00
|
|
|
|
"""A box that is both replaced and block-level.
|
|
|
|
|
|
|
|
|
|
A replaced element with a ``display`` value of ``block``, ``liste-item`` or
|
|
|
|
|
``table`` generates a block-level replaced box.
|
2011-05-25 16:59:42 +04:00
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
2011-12-05 17:24:43 +04:00
|
|
|
|
class InlineReplacedBox(ReplacedBox, AtomicInlineLevelBox):
|
2011-08-24 11:41:38 +04:00
|
|
|
|
"""A box that is both replaced and inline-level.
|
|
|
|
|
|
|
|
|
|
A replaced element with a ``display`` value of ``inline``,
|
|
|
|
|
``inline-table``, or ``inline-block`` generates an inline-level replaced
|
|
|
|
|
box.
|
2011-05-25 16:59:42 +04:00
|
|
|
|
|
|
|
|
|
"""
|
2011-11-14 17:29:40 +04:00
|
|
|
|
|
|
|
|
|
|
2011-11-22 16:06:50 +04:00
|
|
|
|
class TableBox(BlockLevelBox, ParentBox):
|
2011-11-14 17:29:40 +04:00
|
|
|
|
"""Box for elements with ``display: table``"""
|
|
|
|
|
# Definitions for the rules generating anonymous table boxes
|
|
|
|
|
# http://www.w3.org/TR/CSS21/tables.html#anonymous-boxes
|
|
|
|
|
tabular_container = True
|
|
|
|
|
|
2012-02-23 20:49:33 +04:00
|
|
|
|
def translate(self, dx=0, dy=0):
|
|
|
|
|
"""Change the position of the box.
|
|
|
|
|
|
|
|
|
|
Also update the children’s positions accordingly.
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
super(TableBox, self).translate(dx, dy)
|
2012-05-11 16:10:11 +04:00
|
|
|
|
for child in self.column_groups:
|
2012-02-23 20:49:33 +04:00
|
|
|
|
# TODO: why did we not put these in .children again?
|
|
|
|
|
child.translate(dx, dy)
|
|
|
|
|
|
2011-11-14 17:29:40 +04:00
|
|
|
|
|
2011-11-15 21:22:00 +04:00
|
|
|
|
class InlineTableBox(TableBox):
|
2011-11-14 17:29:40 +04:00
|
|
|
|
"""Box for elements with ``display: inline-table``"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TableRowGroupBox(ParentBox):
|
|
|
|
|
"""Box for elements with ``display: table-row-group``"""
|
|
|
|
|
proper_table_child = True
|
|
|
|
|
internal_table_or_caption = True
|
|
|
|
|
tabular_container = True
|
|
|
|
|
proper_parents = (TableBox, InlineTableBox)
|
|
|
|
|
|
2011-11-15 21:22:00 +04:00
|
|
|
|
# Default values. May be overriden on instances.
|
2012-05-04 19:05:16 +04:00
|
|
|
|
is_header = False
|
|
|
|
|
is_footer = False
|
2011-11-15 21:22:00 +04:00
|
|
|
|
|
2011-11-14 17:29:40 +04:00
|
|
|
|
|
|
|
|
|
class TableRowBox(ParentBox):
|
|
|
|
|
"""Box for elements with ``display: table-row``"""
|
|
|
|
|
proper_table_child = True
|
|
|
|
|
internal_table_or_caption = True
|
|
|
|
|
tabular_container = True
|
|
|
|
|
proper_parents = (TableBox, InlineTableBox, TableRowGroupBox)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TableColumnGroupBox(ParentBox):
|
|
|
|
|
"""Box for elements with ``display: table-column-group``"""
|
|
|
|
|
proper_table_child = True
|
|
|
|
|
internal_table_or_caption = True
|
|
|
|
|
proper_parents = (TableBox, InlineTableBox)
|
|
|
|
|
|
2011-11-15 14:44:28 +04:00
|
|
|
|
# Default value. May be overriden on instances.
|
|
|
|
|
span = 1
|
|
|
|
|
|
2011-11-29 15:43:42 +04:00
|
|
|
|
# Columns groups never have margins or paddings
|
|
|
|
|
margin_top = 0
|
|
|
|
|
margin_bottom = 0
|
|
|
|
|
margin_left = 0
|
|
|
|
|
margin_right = 0
|
|
|
|
|
|
|
|
|
|
padding_top = 0
|
|
|
|
|
padding_bottom = 0
|
|
|
|
|
padding_left = 0
|
|
|
|
|
padding_right = 0
|
|
|
|
|
|
2011-11-14 17:29:40 +04:00
|
|
|
|
|
2012-07-11 18:20:43 +04:00
|
|
|
|
# Not really a parent box, but pretending to be removes some corner cases.
|
2011-12-05 17:21:02 +04:00
|
|
|
|
class TableColumnBox(ParentBox):
|
2011-12-26 15:47:26 +04:00
|
|
|
|
"""Box for elements with ``display: table-column``"""
|
2011-11-14 17:29:40 +04:00
|
|
|
|
proper_table_child = True
|
|
|
|
|
internal_table_or_caption = True
|
|
|
|
|
proper_parents = (TableBox, InlineTableBox, TableColumnGroupBox)
|
|
|
|
|
|
2011-11-15 14:44:28 +04:00
|
|
|
|
# Default value. May be overriden on instances.
|
|
|
|
|
span = 1
|
|
|
|
|
|
2011-11-29 15:43:42 +04:00
|
|
|
|
# Columns never have margins or paddings
|
|
|
|
|
margin_top = 0
|
|
|
|
|
margin_bottom = 0
|
|
|
|
|
margin_left = 0
|
|
|
|
|
margin_right = 0
|
|
|
|
|
|
|
|
|
|
padding_top = 0
|
|
|
|
|
padding_bottom = 0
|
|
|
|
|
padding_left = 0
|
|
|
|
|
padding_right = 0
|
|
|
|
|
|
2011-11-14 17:29:40 +04:00
|
|
|
|
|
|
|
|
|
class TableCellBox(BlockContainerBox):
|
|
|
|
|
"""Box for elements with ``display: table-cell``"""
|
|
|
|
|
internal_table_or_caption = True
|
|
|
|
|
|
2011-11-15 14:44:28 +04:00
|
|
|
|
# Default values. May be overriden on instances.
|
|
|
|
|
colspan = 1
|
|
|
|
|
rowspan = 1
|
|
|
|
|
|
2011-11-14 17:29:40 +04:00
|
|
|
|
|
|
|
|
|
class TableCaptionBox(BlockBox):
|
|
|
|
|
"""Box for elements with ``display: table-caption``"""
|
|
|
|
|
proper_table_child = True
|
2011-11-15 21:22:00 +04:00
|
|
|
|
internal_table_or_caption = True
|
2011-11-14 17:29:40 +04:00
|
|
|
|
proper_parents = (TableBox, InlineTableBox)
|
2011-12-28 18:34:30 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PageBox(ParentBox):
|
|
|
|
|
"""Box for a page.
|
|
|
|
|
|
|
|
|
|
Initially the whole document will be in the box for the root element.
|
|
|
|
|
During layout a new page box is created after every page break.
|
|
|
|
|
|
|
|
|
|
"""
|
2012-01-27 14:07:49 +04:00
|
|
|
|
def __init__(self, page_type, style):
|
2012-01-17 18:40:26 +04:00
|
|
|
|
self.page_type = page_type
|
2011-12-28 18:34:30 +04:00
|
|
|
|
# Page boxes are not linked to any element.
|
2012-06-06 14:04:09 +04:00
|
|
|
|
self.fixed_boxes = []
|
2011-12-28 18:34:30 +04:00
|
|
|
|
super(PageBox, self).__init__(
|
|
|
|
|
element_tag=None, sourceline=None, style=style, children=[])
|
|
|
|
|
|
|
|
|
|
def __repr__(self):
|
2012-01-17 18:40:26 +04:00
|
|
|
|
return '<%s %s>' % (type(self).__name__, self.page_type)
|
2011-12-28 18:34:30 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MarginBox(BlockContainerBox):
|
|
|
|
|
"""Box in page margins, as defined in CSS3 Paged Media"""
|
|
|
|
|
def __init__(self, at_keyword, style, children=[]):
|
|
|
|
|
self.at_keyword = at_keyword
|
|
|
|
|
# Margin boxes are not linked to any element.
|
|
|
|
|
super(MarginBox, self).__init__(
|
|
|
|
|
element_tag=None, sourceline=None, style=style, children=children)
|
|
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
|
return '<%s %s>' % (type(self).__name__, self.at_keyword)
|