diff --git a/weasyprint/__init__.py b/weasyprint/__init__.py
index 527ae360a..7f384b2fb 100644
--- a/weasyprint/__init__.py
+++ b/weasyprint/__init__.py
@@ -332,7 +332,6 @@ def __init__(self, guess=None, filename=None, url=None, file_obj=None,
self.base_url = base_url
self.matcher = matcher or cssselect2.Matcher()
self.page_rules = [] if page_rules is None else page_rules
- # TODO: fonts are stored here and should be cleaned after rendering
self.fonts = []
preprocess_stylesheet(
media_type, base_url, stylesheet, url_fetcher, self.matcher,
diff --git a/weasyprint/css/__init__.py b/weasyprint/css/__init__.py
index ff7863fa9..6984bd46c 100644
--- a/weasyprint/css/__init__.py
+++ b/weasyprint/css/__init__.py
@@ -34,7 +34,8 @@
from .validation.descriptors import preprocess_descriptors
# Reject anything not in here:
-PSEUDO_ELEMENTS = (None, 'before', 'after', 'first-line', 'first-letter')
+PSEUDO_ELEMENTS = (
+ None, 'before', 'after', 'marker', 'first-line', 'first-letter')
PageType = namedtuple('PageType', ['side', 'blank', 'first', 'index', 'name'])
@@ -67,11 +68,14 @@ def __init__(self, html, sheets, presentational_hints, target_collector):
for specificity, attributes in find_style_attributes(
html.etree_element, presentational_hints, html.base_url):
element, declarations, base_url = attributes
+ style = cascaded_styles.setdefault((element, None), {})
for name, values, importance in preprocess_declarations(
base_url, declarations):
precedence = declaration_precedence('author', importance)
weight = (precedence, specificity)
- add_declaration(cascaded_styles, name, values, weight, element)
+ old_weight = style.get(name, (None, None))[1]
+ if old_weight is None or old_weight <= weight:
+ style[name] = values, weight
# First, add declarations and set computed styles for "real" elements
# *in tree order*. Tree order is important so that parents have
@@ -84,12 +88,14 @@ def __init__(self, html, sheets, presentational_hints, target_collector):
for selector in sheet.matcher.match(element):
specificity, order, pseudo_type, declarations = selector
specificity = sheet_specificity or specificity
+ style = cascaded_styles.setdefault(
+ (element.etree_element, pseudo_type), {})
for name, values, importance in declarations:
precedence = declaration_precedence(origin, importance)
weight = (precedence, specificity)
- add_declaration(
- cascaded_styles, name, values, weight,
- element.etree_element, pseudo_type)
+ old_weight = style.get(name, (None, None))[1]
+ if old_weight is None or old_weight <= weight:
+ style[name] = values, weight
parent = element.parent.etree_element if element.parent else None
self.set_computed_styles(
element.etree_element, root=html.etree_element, parent=parent,
@@ -165,13 +171,15 @@ def add_page_declarations(self, page_type):
specificity, pseudo_type, selector_page_type = selector
if self._page_type_match(selector_page_type, page_type):
specificity = sheet_specificity or specificity
+ style = self._cascaded_styles.setdefault(
+ (page_type, pseudo_type), {})
for name, values, importance in declarations:
precedence = declaration_precedence(
origin, importance)
weight = (precedence, specificity)
- add_declaration(
- self._cascaded_styles, name, values, weight,
- page_type, pseudo_type)
+ old_weight = style.get(name, (None, None))[1]
+ if old_weight is None or old_weight <= weight:
+ style[name] = values, weight
def get_cascaded_styles(self):
return self._cascaded_styles
@@ -573,19 +581,6 @@ def declaration_precedence(origin, importance):
return 5
-def add_declaration(cascaded_styles, prop_name, prop_values, weight, element,
- pseudo_type=None):
- """Set the value for a property on a given element.
-
- The value is only set if there is no value of greater weight defined yet.
-
- """
- style = cascaded_styles.setdefault((element, pseudo_type), {})
- _values, previous_weight = style.get(prop_name, (None, None))
- if previous_weight is None or previous_weight <= weight:
- style[prop_name] = prop_values, weight
-
-
def computed_from_cascaded(element, cascaded, parent_style, pseudo_type=None,
root_style=None, base_url=None,
target_collector=None):
@@ -602,8 +597,10 @@ def computed_from_cascaded(element, cascaded, parent_style, pseudo_type=None,
# border-*-style is none, so border-width computes to zero.
# Other than that, properties that would need computing are
# border-*-color, but they do not apply.
- for side in ('top', 'bottom', 'left', 'right'):
- computed['border_%s_width' % side] = 0
+ computed['border_top_width'] = 0
+ computed['border_bottom_width'] = 0
+ computed['border_left_width'] = 0
+ computed['border_right_width'] = 0
computed['outline_width'] = 0
return computed
@@ -614,10 +611,10 @@ def computed_from_cascaded(element, cascaded, parent_style, pseudo_type=None,
if parent_style:
for name in parent_style:
if name.startswith('__'):
- specified[name] = parent_style[name]
+ computed[name] = specified[name] = parent_style[name]
for name in cascaded:
if name.startswith('__'):
- specified[name] = cascaded[name][0]
+ computed[name] = specified[name] = cascaded[name][0]
for name, initial in INITIAL_VALUES.items():
if name in cascaded:
@@ -761,7 +758,7 @@ def parse_page_selectors(rule):
types['index'] = (*nth_values, group)
# TODO: specificity is not specified yet
- # https://github.com/w3c/csswg-drafts/issues/3791
+ # https://github.com/w3c/csswg-drafts/issues/3524
types['specificity'][1] += 1
continue
diff --git a/weasyprint/css/computed_values.py b/weasyprint/css/computed_values.py
index 87c83ed44..9a6a9cb5e 100644
--- a/weasyprint/css/computed_values.py
+++ b/weasyprint/css/computed_values.py
@@ -197,29 +197,20 @@ def compute(element, pseudo_type, specified, computed, parent_style,
"""
from .validation.properties import PROPERTIES
- def computer():
- """Dummy object that holds attributes."""
- return 0
-
- computer.is_root_element = parent_style is None
- if parent_style is None:
- parent_style = INITIAL_VALUES
-
- computer.element = element
- computer.pseudo_type = pseudo_type
- computer.specified = specified
- computer.computed = computed
- computer.parent_style = parent_style
- computer.root_style = root_style
- computer.base_url = base_url
- computer.target_collector = target_collector
+ computer = {
+ 'is_root_element': parent_style is None,
+ 'element': element,
+ 'pseudo_type': pseudo_type,
+ 'specified': specified,
+ 'computed': computed,
+ 'parent_style': parent_style or INITIAL_VALUES,
+ 'root_style': root_style,
+ 'base_url': base_url,
+ 'target_collector': target_collector,
+ }
getter = COMPUTER_FUNCTIONS.get
- for name in specified:
- if name.startswith('__'):
- computed[name] = specified[name]
-
for name in COMPUTING_ORDER:
if name in computed:
# Already computed
@@ -354,24 +345,24 @@ def length(computer, name, value, font_size=None, pixels_only=False):
result = value.value * LENGTHS_TO_PIXELS[unit]
elif unit in ('em', 'ex', 'ch', 'rem'):
if font_size is None:
- font_size = computer.computed['font_size']
+ font_size = computer['computed']['font_size']
if unit == 'ex':
# TODO: cache
- result = value.value * font_size * ex_ratio(computer.computed)
+ result = value.value * font_size * ex_ratio(computer['computed'])
elif unit == 'ch':
# TODO: cache
# TODO: use context to use @font-face fonts
layout = text.Layout(
context=None, font_size=font_size,
- style=computer.computed)
+ style=computer['computed'])
layout.set_text('0')
line, _ = layout.get_first_line()
- logical_width, _ = text.get_size(line, computer.computed)
+ logical_width, _ = text.get_size(line, computer['computed'])
result = value.value * logical_width
elif unit == 'em':
result = value.value * font_size
elif unit == 'rem':
- result = value.value * computer.root_style['font_size']
+ result = value.value * computer['root_style']['font_size']
else:
# A percentage or 'auto': no conversion needed.
return value
@@ -385,7 +376,7 @@ def length(computer, name, value, font_size=None, pixels_only=False):
@register_computer('bleed-bottom')
def bleed(computer, name, value):
if value == 'auto':
- if 'crop' in computer.computed['marks']:
+ if 'crop' in computer['computed']['marks']:
return Dimension(8, 'px') # 6pt
else:
return Dimension(0, 'px')
@@ -418,7 +409,7 @@ def background_size(computer, name, values):
@register_computer('outline-width')
def border_width(computer, name, value):
"""Compute the ``border-*-width`` properties."""
- style = computer.computed[name.replace('width', 'style')]
+ style = computer['computed'][name.replace('width', 'style')]
if style in ('none', 'hidden'):
return 0
@@ -461,11 +452,11 @@ def compute_attr_function(computer, values):
func_name, value = values
assert func_name == 'attr()'
attr_name, type_or_unit, fallback = value
- # computer.element sometimes is None
- # computer.element sometimes is a 'PageType' object without .get()
+ # computer['element'] sometimes is None
+ # computer['element'] sometimes is a 'PageType' object without .get()
# so wrapt the .get() into try and return None instead of crashing
try:
- attr_value = computer.element.get(attr_name, fallback)
+ attr_value = computer['element'].get(attr_name, fallback)
if type_or_unit == 'string':
pass # Keep the string
elif type_or_unit == 'url':
@@ -473,7 +464,7 @@ def compute_attr_function(computer, values):
attr_value = ('internal', unquote(attr_value[1:]))
else:
attr_value = (
- 'external', safe_urljoin(computer.base_url, attr_value))
+ 'external', safe_urljoin(computer['base_url'], attr_value))
elif type_or_unit == 'color':
attr_value = parse_color(attr_value.strip())
elif type_or_unit == 'integer':
@@ -519,12 +510,12 @@ def _content_list(computer, values):
(attr,) + value[1][1:]))
else:
computed_value = value
- if computer.target_collector and computed_value:
- computer.target_collector.collect_computed_target(
+ if computer['target_collector'] and computed_value:
+ computer['target_collector'].collect_computed_target(
computed_value[1][0])
if computed_value is None:
LOGGER.warning('Unable to compute %s\'s value for content: %s' % (
- computer.element, ', '.join(str(item) for item in value)))
+ computer['element'], ', '.join(str(item) for item in value)))
else:
computed_values.append(computed_value)
@@ -552,7 +543,7 @@ def content(computer, name, values):
if len(values) == 1:
value, = values
if value == 'normal':
- return 'inhibit' if computer.pseudo_type else 'contents'
+ return 'inhibit' if computer['pseudo_type'] else 'contents'
elif value == 'none':
return 'inhibit'
return _content_list(computer, values)
@@ -565,10 +556,10 @@ def display(computer, name, value):
See http://www.w3.org/TR/CSS21/visuren.html#dis-pos-flo
"""
- float_ = computer.specified['float']
- position = computer.specified['position']
+ float_ = computer['specified']['float']
+ position = computer['specified']['position']
if position in ('absolute', 'fixed') or float_ != 'none' or \
- computer.is_root_element:
+ computer['is_root_element']:
if value == 'inline-table':
return'table'
elif value in ('inline', 'table-row-group', 'table-column',
@@ -586,7 +577,7 @@ def compute_float(computer, name, value):
See http://www.w3.org/TR/CSS21/visuren.html#dis-pos-flo
"""
- if computer.specified['position'] in ('absolute', 'fixed'):
+ if computer['specified']['position'] in ('absolute', 'fixed'):
return 'none'
else:
return value
@@ -599,7 +590,7 @@ def font_size(computer, name, value):
return FONT_SIZE_KEYWORDS[value]
# TODO: support 'larger' and 'smaller'
- parent_font_size = computer.parent_style['font_size']
+ parent_font_size = computer['parent_style']['font_size']
if value.unit == '%':
return value.value * parent_font_size / 100.
else:
@@ -615,7 +606,7 @@ def font_weight(computer, name, value):
elif value == 'bold':
return 700
elif value in ('bolder', 'lighter'):
- parent_value = computer.parent_style['font_weight']
+ parent_value = computer['parent_style']['font_weight']
return FONT_WEIGHT_RELATIVE[value][parent_value]
else:
return value
@@ -630,7 +621,7 @@ def line_height(computer, name, value):
return ('NUMBER', value.value)
elif value.unit == '%':
factor = value.value / 100.
- font_size_value = computer.computed['font_size']
+ font_size_value = computer['computed']['font_size']
pixels = factor * font_size_value
else:
pixels = length(computer, name, value, pixels_only=True)
@@ -642,8 +633,8 @@ def anchor(computer, name, values):
"""Compute the ``anchor`` property."""
if values != 'none':
_, key = values
- anchor_name = computer.element.get(key) or None
- computer.target_collector.collect_anchor(anchor_name)
+ anchor_name = computer['element'].get(key) or None
+ computer['target_collector'].collect_anchor(anchor_name)
return anchor_name
@@ -656,7 +647,7 @@ def link(computer, name, values):
type_, value = values
if type_ == 'attr()':
return get_link_attribute(
- computer.element, value, computer.base_url)
+ computer['element'], value, computer['base_url'])
else:
return values
@@ -669,7 +660,7 @@ def lang(computer, name, values):
else:
type_, key = values
if type_ == 'attr()':
- return computer.element.get(key) or None
+ return computer['element'].get(key) or None
elif type_ == 'string':
return key
@@ -703,11 +694,11 @@ def vertical_align(computer, name, value):
'top', 'bottom'):
return value
elif value == 'super':
- return computer.computed['font_size'] * 0.5
+ return computer['computed']['font_size'] * 0.5
elif value == 'sub':
- return computer.computed['font_size'] * -0.5
+ return computer['computed']['font_size'] * -0.5
elif value.unit == '%':
- height, _ = strut_layout(computer.computed)
+ height, _ = strut_layout(computer['computed'])
return height * value.value / 100.
else:
return length(computer, name, value, pixels_only=True)
diff --git a/weasyprint/css/html5_ua.css b/weasyprint/css/html5_ua.css
index 80a441b66..0b38a54a1 100644
--- a/weasyprint/css/html5_ua.css
+++ b/weasyprint/css/html5_ua.css
@@ -473,6 +473,7 @@ track { display: none; }
tt { font-family: monospace; }
u { text-decoration: underline; }
+::marker { unicode-bidi: isolate; font-variant-numeric: tabular-nums; }
ul { display: block; list-style-type: disc; margin-bottom: 1em; margin-top: 1em; padding-left: 40px; unicode-bidi: isolate; }
*[dir=ltr] ul { padding-left: 40px; padding-right: 0; }
diff --git a/weasyprint/css/tests_ua.css b/weasyprint/css/tests_ua.css
index 91c2ee4f8..3b81c4692 100644
--- a/weasyprint/css/tests_ua.css
+++ b/weasyprint/css/tests_ua.css
@@ -32,3 +32,5 @@ h3 { bookmark-level: 3; bookmark-label: content(text); }
h4 { bookmark-level: 4; bookmark-label: content(text); }
h5 { bookmark-level: 5; bookmark-label: content(text); }
h6 { bookmark-level: 6; bookmark-label: content(text); }
+
+::marker { unicode-bidi: isolate; font-variant-numeric: tabular-nums; }
diff --git a/weasyprint/css/validation/expanders.py b/weasyprint/css/validation/expanders.py
index 4f60653f8..cabd56032 100644
--- a/weasyprint/css/validation/expanders.py
+++ b/weasyprint/css/validation/expanders.py
@@ -456,14 +456,13 @@ def expand_font(name, tokens):
# Make `tokens` a stack
tokens = list(reversed(tokens))
- # Values for font-style font-variant and font-weight can come in any
- # order and are all optional.
- while tokens:
+ # Values for font-style, font-variant-caps, font-weight and font-stretch
+ # can come in any order and are all optional.
+ for _ in range(4):
token = tokens.pop()
if get_keyword(token) == 'normal':
# Just ignore 'normal' keywords. Unspecified properties will get
- # their initial token, which is 'normal' for all three here.
- # TODO: fail if there is too many 'normal' values?
+ # their initial token, which is 'normal' for all four here.
continue
if font_style([token]) is not None:
@@ -475,10 +474,15 @@ def expand_font(name, tokens):
elif font_stretch([token]) is not None:
suffix = '-stretch'
else:
- # We’re done with these three, continue with font-size
+ # We’re done with these four, continue with font-size
break
yield suffix, [token]
+ if not tokens:
+ raise InvalidValues
+ else:
+ token = tokens.pop()
+
# Then font-size is mandatory
# Latest `token` from the loop.
if font_size([token]) is None:
diff --git a/weasyprint/css/validation/properties.py b/weasyprint/css/validation/properties.py
index 810939d7a..c606bc7b1 100644
--- a/weasyprint/css/validation/properties.py
+++ b/weasyprint/css/validation/properties.py
@@ -31,8 +31,6 @@
# returning a value or None for invalid.
# For properties that take a single value, that value is returned by itself
# instead of a list.
-# TODO: fix this: Also transform values: keyword and URLs are returned as
-# strings.
PROPERTIES = {}
@@ -192,7 +190,10 @@ def list_style_image(token, base_url):
@property()
def transform_origin(tokens):
- # TODO: parse (and ignore) a third value for Z.
+ """``transform-origin`` property validation."""
+ if len(tokens) == 3:
+ # Ignore third parameter as 3D transforms are ignored.
+ tokens = tokens[:2]
return parse_2d_position(tokens)
diff --git a/weasyprint/document.py b/weasyprint/document.py
index aa10c7e3c..91d3dc18d 100644
--- a/weasyprint/document.py
+++ b/weasyprint/document.py
@@ -607,8 +607,9 @@ def write_pdf(self, target=None, zoom=1, attachments=None):
"""
# 0.75 = 72 PDF point (cairo units) per inch / 96 CSS pixel per inch
scale = zoom * 0.75
- # Use an in-memory buffer. We will need to seek for metadata
- # TODO: avoid this if target can seek? Benchmark first.
+ # Use an in-memory buffer, as we will need to seek for
+ # metadata. Directly using the target when possible doesn't
+ # significantly save time and memory use.
file_obj = io.BytesIO()
# (1, 1) is overridden by .set_size() below.
surface = cairo.PDFSurface(file_obj, 1, 1)
diff --git a/weasyprint/draw.py b/weasyprint/draw.py
index 3ea4441c3..9ddf08277 100644
--- a/weasyprint/draw.py
+++ b/weasyprint/draw.py
@@ -253,17 +253,11 @@ def draw_stacking_context(context, stacking_context, enable_hinting):
# Point 7
for block in [box] + stacking_context.blocks_and_cells:
- if block.outside_list_marker:
- draw_inline_level(
- context, stacking_context.page,
- block.outside_list_marker, enable_hinting)
-
if isinstance(block, boxes.ReplacedBox):
draw_replacedbox(context, block)
else:
for child in block.children:
if isinstance(child, boxes.LineBox):
- # TODO: draw inline tables
draw_inline_level(
context, stacking_context.page, child,
enable_hinting)
diff --git a/weasyprint/fonts.py b/weasyprint/fonts.py
index 377716c18..d67d59cd2 100644
--- a/weasyprint/fonts.py
+++ b/weasyprint/fonts.py
@@ -428,7 +428,7 @@ def add_font_face(self, rule_descriptors, url_fetcher):
# too as explained in Behdad's blog entry.
# TODO: What about pango_fc_font_map_config_changed()
# as suggested in Behdad's blog entry?
- # Though it seems to work without...
+ # Though it seems to work without…
return filename
else:
LOGGER.debug('Failed to load font at "%s"', url)
diff --git a/weasyprint/formatting_structure/boxes.py b/weasyprint/formatting_structure/boxes.py
index cdf766105..b2c57e9d2 100644
--- a/weasyprint/formatting_structure/boxes.py
+++ b/weasyprint/formatting_structure/boxes.py
@@ -84,7 +84,6 @@ class Box(object):
transformation_matrix = None
bookmark_label = None
string_set = None
- outside_list_marker = None
# Default, overriden on some subclasses
def all_children(self):
@@ -107,12 +106,11 @@ def anonymous_from(cls, parent, *args, **kwargs):
def copy(self):
"""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.
+ # Create a new instance without calling __init__: parameters are
+ # different depending on the class.
new_box = cls.__new__(cls)
# Copy attributes
new_box.__dict__.update(self.__dict__)
- new_box.style = self.style
return new_box
def translate(self, dx=0, dy=0, ignore_floats=False):
@@ -296,15 +294,6 @@ def __init__(self, element_tag, style, children):
def all_children(self):
return self.children
- def enumerate_skip(self, skip_num=0):
- """Yield ``(child, child_index)`` tuples for each child.
-
- ``skip_num`` children are skipped before iterating over them.
-
- """
- for index in range(skip_num, len(self.children)):
- yield index, self.children[index]
-
def _reset_spacing(self, side):
"""Set to 0 the margin, padding and border of ``side``."""
self.style['margin_%s' % side] = Dimension(0, 'px')
@@ -326,18 +315,20 @@ def _reset_spacing(self, side):
def _remove_decoration(self, start, end):
if start or end:
+ old_style = self.style
self.style = self.style.copy()
if start:
self._reset_spacing('top')
if end:
self._reset_spacing('bottom')
+ if (start or end) and old_style == self.style:
+ # Don't copy style if there's no need to, save some memory
+ self.style = old_style
def copy_with_children(self, new_children, is_start=True, is_end=True):
"""Create a new equivalent box with given ``new_children``."""
new_box = self.copy()
new_box.children = tuple(new_children)
- if not is_start:
- new_box.outside_list_marker = None
if self.style['box_decoration_break'] == 'slice':
new_box._remove_decoration(not is_start, not is_end)
return new_box
@@ -400,10 +391,6 @@ class BlockBox(BlockContainerBox, BlockLevelBox):
generates a block box.
"""
- # TODO: remove this when outside list marker are absolute children
- def all_children(self):
- return (itertools.chain(self.children, [self.outside_list_marker])
- if self.outside_list_marker else self.children)
class LineBox(ParentBox):
@@ -438,12 +425,16 @@ class InlineLevelBox(Box):
"""
def _remove_decoration(self, start, end):
if start or end:
+ old_style = self.style
self.style = self.style.copy()
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')
+ if (start or end) and old_style == self.style:
+ # Don't copy style if there's no need to, save some memory
+ self.style = old_style
class InlineBox(InlineLevelBox, ParentBox):
diff --git a/weasyprint/formatting_structure/build.py b/weasyprint/formatting_structure/build.py
index 55fd60b2e..e0005bbf5 100644
--- a/weasyprint/formatting_structure/build.py
+++ b/weasyprint/formatting_structure/build.py
@@ -20,7 +20,7 @@
import tinycss2.color3
from .. import html
-from ..css import properties
+from ..css import computed_values, properties
from ..logger import LOGGER
from . import boxes, counters
@@ -58,8 +58,7 @@ def build_formatting_structure(element_tree, style_for, get_image_from_uri,
def root_style_for(element, pseudo_type=None):
style = style_for(element, pseudo_type)
if style:
- # TODO: we should check that the element has a parent instead.
- if element.tag == 'html':
+ if element == element_tree:
style['display'] = 'block'
else:
style['display'] = 'none'
@@ -81,7 +80,7 @@ def root_style_for(element, pseudo_type=None):
return box
-def make_box(element_tag, style, content, get_image_from_uri):
+def make_box(element_tag, style, content):
return BOX_TYPE_FROM_DISPLAY[style['display']](
element_tag, style, content)
@@ -123,7 +122,7 @@ def element_to_box(element, style_for, get_image_from_uri, base_url,
if display == 'none':
return []
- box = make_box(element.tag, style, [], get_image_from_uri)
+ box = make_box(element.tag, style, [])
if state is None:
# use a list to have a shared mutable object
@@ -137,10 +136,8 @@ def element_to_box(element, style_for, get_image_from_uri, base_url,
update_counters(state, style)
+ outside_markers = []
children = []
- if display == 'list-item':
- children.extend(add_box_marker(
- box, counter_values, get_image_from_uri))
# If this element’s direct children create new scopes, the counter
# names will be in this new list
@@ -149,12 +146,22 @@ def element_to_box(element, style_for, get_image_from_uri, base_url,
box.first_letter_style = style_for(element, 'first-letter')
box.first_line_style = style_for(element, 'first-line')
+ if style['display'] == 'list-item':
+ marker_boxes = marker_to_box(
+ element, state, style, style_for, get_image_from_uri,
+ target_collector)
+ if marker_boxes:
+ if style['list_style_position'] == 'outside':
+ outside_markers.extend(marker_boxes)
+ else:
+ children.extend(marker_boxes)
+
children.extend(before_after_to_box(
element, 'before', state, style_for, get_image_from_uri,
target_collector))
# collect anchor's counter_values, maybe it's a target.
- # to get the spec-conform counter_valuse we must do it here,
+ # to get the spec-conform counter_values we must do it here,
# after the ::before is parsed and befor the ::after is
if style['anchor']:
target_collector.store_target(style['anchor'], counter_values, box)
@@ -188,18 +195,33 @@ def element_to_box(element, style_for, get_image_from_uri, base_url,
# calculate string-set and bookmark-label
set_content_lists(element, box, style, counter_values, target_collector)
+ if outside_markers and not box.children:
+ # See https://www.w3.org/TR/css-lists-3/#list-style-position-outside
+ #
+ # "The size or contents of the marker box may affect the height of the
+ # principal block box and/or the height of its first line box, and in
+ # some cases may cause the creation of a new line box; this
+ # interaction is also not defined."
+ #
+ # We decide here to add a zero-width space to have a minimum
+ # height. Adding text boxes is not the best idea, but it's not a good
+ # moment to add an empty line box, and the specification lets us do
+ # almost what we want, so…
+ box.children = [boxes.TextBox.anonymous_from(box, '')]
+
# Specific handling for the element. (eg. replaced element)
- return html.handle_element(element, box, get_image_from_uri, base_url)
+ return outside_markers + html.handle_element(
+ element, box, get_image_from_uri, base_url)
def before_after_to_box(element, pseudo_type, state, style_for,
get_image_from_uri, target_collector):
- """Yield the box for ::before or ::after pseudo-element if there is one."""
+ """Return the boxes for ::before or ::after pseudo-element."""
style = style_for(element, pseudo_type)
if pseudo_type and style is None:
# Pseudo-elements with no style at all do not get a style dict.
# Their initial content property computes to 'none'.
- return
+ return []
# TODO: should be the computed value. When does the used value for
# `display` differ from the computer value? It's at least wrong for
@@ -207,24 +229,97 @@ def before_after_to_box(element, pseudo_type, state, style_for,
display = style['display']
content = style['content']
if 'none' in (display, content) or content in ('normal', 'inhibit'):
- return
+ return []
- box = make_box(
- '%s::%s' % (element.tag, pseudo_type), style, [], get_image_from_uri)
+ box = make_box('%s::%s' % (element.tag, pseudo_type), style, [])
quote_depth, counter_values, _counter_scopes = state
update_counters(state, style)
children = []
+
+ outside_markers = []
if display == 'list-item':
- children.extend(add_box_marker(
- box, counter_values, get_image_from_uri))
+ marker_boxes = marker_to_box(
+ element, state, style, style_for, get_image_from_uri,
+ target_collector)
+ if marker_boxes:
+ if style['list_style_position'] == 'outside':
+ outside_markers.extend(marker_boxes)
+ else:
+ children.extend(marker_boxes)
+
children.extend(content_to_boxes(
style, box, quote_depth, counter_values, get_image_from_uri,
target_collector))
box.children = children
- yield box
+ return outside_markers + [box]
+
+
+def marker_to_box(element, state, parent_style, style_for, get_image_from_uri,
+ target_collector):
+ """Yield the box for ::marker pseudo-element if there is one.
+
+ https://drafts.csswg.org/css-lists-3/#marker-pseudo
+
+ """
+ style = style_for(element, 'marker')
+
+ children = []
+
+ # TODO: should be the computed value. When does the used value for
+ # `display` differ from the computer value? It's at least wrong for
+ # `content` where 'normal' computes as 'inhibit' for pseudo elements.
+ quote_depth, counter_values, _counter_scopes = state
+
+ box = make_box('%s::marker' % element.tag, style, children)
+
+ if style['display'] == 'none':
+ return
+
+ image_type, image = style['list_style_image']
+
+ if style['content'] not in ('normal', 'inhibit'):
+ children.extend(content_to_boxes(
+ style, box, quote_depth, counter_values, get_image_from_uri,
+ target_collector))
+
+ else:
+ if image_type == 'url':
+ # image may be None here too, in case the image is not available.
+ image = get_image_from_uri(image)
+ if image is not None:
+ box = boxes.InlineReplacedBox.anonymous_from(box, image)
+ children.append(box)
+
+ if not children and style['list_style_type'] != 'none':
+ counter_value = counter_values.get('list-item', [0])[-1]
+ # TODO: rtl numbered list has the dot on the left
+ marker_text = counters.format_list_marker(
+ counter_value, style['list_style_type'])
+ box = boxes.TextBox.anonymous_from(box, marker_text)
+ box.style['white_space'] = 'pre-wrap'
+ children.append(box)
+
+ if not children:
+ return
+
+ if parent_style['list_style_position'] == 'outside':
+ marker_box = boxes.BlockBox.anonymous_from(box, children)
+ # We can safely edit everything that can't be changed by user style
+ # See https://drafts.csswg.org/css-pseudo-4/#marker-pseudo
+ marker_box.style['position'] = 'absolute'
+ if parent_style['direction'] == 'ltr':
+ translate_x = properties.Dimension(-100, '%')
+ else:
+ translate_x = properties.Dimension(100, '%')
+ translate_y = computed_values.ZERO_PIXELS
+ marker_box.style['transform'] = (
+ ('translate', (translate_x, translate_y)),)
+ else:
+ marker_box = boxes.InlineBox.anonymous_from(box, children)
+ yield marker_box
def _collect_missing_counter(counter_name, counter_values, missing_counters):
@@ -433,13 +528,11 @@ def parse_again(mixin_pagebased_counters=None):
local_counters.update(parent_box.cached_counter_values)
local_children = []
- if style['display'] == 'list-item':
- local_children.extend(add_box_marker(
- parent_box, local_counters, get_image_from_uri))
local_children.extend(content_to_boxes(
style, parent_box, orig_quote_depth, local_counters,
get_image_from_uri, target_collector))
+ # TODO: do we need to add markers here?
# TODO: redo the formatting structure of the parent instead of hacking
# the already formatted structure. Find why inline_in_blocks has
# sometimes already been called, and sometimes not.
@@ -584,51 +677,6 @@ def update_counters(state, style):
values[-1] += value
-def add_box_marker(box, counter_values, get_image_from_uri):
- """Add a list marker to boxes for elements with ``display: list-item``,
- and yield children to add a the start of the box.
-
- See http://www.w3.org/TR/CSS21/generate.html#lists
-
- """
- style = box.style
- image_type, image = style['list_style_image']
- if image_type == 'url':
- # surface may be None here too, in case the image is not available.
- image = get_image_from_uri(image)
-
- if image is None:
- type_ = style['list_style_type']
- if type_ == 'none':
- return
- counter_value = counter_values.get('list-item', [0])[-1]
- # TODO: rtl numbered list has the dot on the left
- marker_text = counters.format_list_marker(counter_value, type_)
- marker_box = boxes.TextBox.anonymous_from(box, marker_text)
- else:
- marker_box = boxes.InlineReplacedBox.anonymous_from(box, image)
- marker_box.is_list_marker = True
- marker_box.element_tag += '::marker'
-
- position = style['list_style_position']
- direction = box.style['direction']
- # Apply a margin of 0.5em. The margin to use depends on list-style-position
- # and direction.
- half_em = 0.5 * box.style['font_size']
- propvalue = properties.Dimension(half_em, 'px')
- marker_box.style = marker_box.style.copy()
- if position == 'inside' or direction == 'ltr':
- marker_box.style['margin_right'] = propvalue
- else:
- marker_box.style['margin_left'] = propvalue
-
- if position == 'inside':
- # TODO: rtl markers must be the rightmost box of the first line
- yield marker_box
- elif position == 'outside':
- box.outside_list_marker = marker_box
-
-
def is_whitespace(box, _has_non_whitespace=re.compile('\\S').search):
"""Return True if ``box`` is a TextBox with only whitespace."""
return isinstance(box, boxes.TextBox) and not _has_non_whitespace(box.text)
@@ -1407,7 +1455,8 @@ def _inner_block_in_inline(box, skip_stack=None):
else:
skip, skip_stack = skip_stack
- for index, child in box.enumerate_skip(skip):
+ for i, child in enumerate(box.children[skip:]):
+ index = i + skip
if isinstance(child, boxes.BlockLevelBox) and \
child.is_in_normal_flow():
assert skip_stack is None # Should not skip here
diff --git a/weasyprint/formatting_structure/counters.py b/weasyprint/formatting_structure/counters.py
index 10769fef2..39187fe67 100644
--- a/weasyprint/formatting_structure/counters.py
+++ b/weasyprint/formatting_structure/counters.py
@@ -21,7 +21,7 @@
INITIAL_VALUES = dict(
negative=('-', ''),
prefix='',
- suffix='.',
+ suffix='. ',
range=(float('-inf'), float('inf')),
fallback='decimal',
# type and symbols ommited here.
@@ -227,13 +227,13 @@ def additive(symbols, negative, value):
'disc',
type='repeating',
symbols=['•'], # U+2022, BULLET
- suffix='',
+ suffix=' ',
)
register_style(
'circle',
type='repeating',
symbols=['◦'], # U+25E6 WHITE BULLET
- suffix='',
+ suffix=' ',
)
register_style(
'square',
@@ -241,7 +241,7 @@ def additive(symbols, negative, value):
# CSS Lists 3 suggests U+25FE BLACK MEDIUM SMALL SQUARE
# But I think this one looks better.
symbols=['▪'], # U+25AA BLACK SMALL SQUARE
- suffix='',
+ suffix=' ',
)
register_style(
'lower-latin',
diff --git a/weasyprint/layout/absolute.py b/weasyprint/layout/absolute.py
index 42b3fa5c7..51076f495 100644
--- a/weasyprint/layout/absolute.py
+++ b/weasyprint/layout/absolute.py
@@ -206,12 +206,10 @@ def absolute_block(context, box, containing_block, fixed_boxes):
# avoid a circular import
from .blocks import block_container_layout
- # TODO: remove device_size everywhere else
new_box, _, _, _, _ = block_container_layout(
context, box, max_position_y=float('inf'), skip_stack=None,
- device_size=None, page_is_empty=False,
- absolute_boxes=absolute_boxes, fixed_boxes=fixed_boxes,
- adjoining_margins=None)
+ page_is_empty=False, absolute_boxes=absolute_boxes,
+ fixed_boxes=fixed_boxes, adjoining_margins=None)
for child_placeholder in absolute_boxes:
absolute_layout(context, child_placeholder, new_box, fixed_boxes)
@@ -247,12 +245,10 @@ def absolute_flex(context, box, containing_block_sizes, fixed_boxes,
if box.is_table_wrapper:
table_wrapper_width(context, box, (cb_width, cb_height))
- # TODO: remove device_size everywhere else
new_box, _, _, _, _ = flex_layout(
context, box, max_position_y=float('inf'), skip_stack=None,
- containing_block=containing_block, device_size=None,
- page_is_empty=False, absolute_boxes=absolute_boxes,
- fixed_boxes=fixed_boxes)
+ containing_block=containing_block, page_is_empty=False,
+ absolute_boxes=absolute_boxes, fixed_boxes=fixed_boxes)
for child_placeholder in absolute_boxes:
absolute_layout(context, child_placeholder, new_box, fixed_boxes)
@@ -311,7 +307,7 @@ def absolute_box_layout(context, box, containing_block, fixed_boxes):
def absolute_replaced(context, box, containing_block):
# avoid a circular import
from .inlines import inline_replaced_box_width_height
- inline_replaced_box_width_height(box, device_size=None)
+ inline_replaced_box_width_height(box, containing_block)
cb_x, cb_y, cb_width, cb_height = containing_block
ltr = box.style['direction'] == 'ltr'
diff --git a/weasyprint/layout/blocks.py b/weasyprint/layout/blocks.py
index e577f753c..e7ea66eb7 100644
--- a/weasyprint/layout/blocks.py
+++ b/weasyprint/layout/blocks.py
@@ -17,15 +17,14 @@
from .inlines import (
iter_line_boxes, min_max_auto_replaced, replaced_box_height,
replaced_box_width)
-from .markers import list_marker_layout
from .min_max import handle_min_max_width
from .percentages import resolve_percentages, resolve_position_percentages
from .tables import table_layout, table_wrapper_width
def block_level_layout(context, box, max_position_y, skip_stack,
- containing_block, device_size, page_is_empty,
- absolute_boxes, fixed_boxes, adjoining_margins):
+ containing_block, page_is_empty, absolute_boxes,
+ fixed_boxes, adjoining_margins):
"""Lay out the block-level ``box``.
:param max_position_y: the absolute vertical position (as in
@@ -61,26 +60,23 @@ def block_level_layout(context, box, max_position_y, skip_stack,
return block_level_layout_switch(
context, box, max_position_y, skip_stack, containing_block,
- device_size, page_is_empty, absolute_boxes, fixed_boxes,
- adjoining_margins)
+ page_is_empty, absolute_boxes, fixed_boxes, adjoining_margins)
def block_level_layout_switch(context, box, max_position_y, skip_stack,
- containing_block, device_size, page_is_empty,
- absolute_boxes, fixed_boxes,
- adjoining_margins):
+ containing_block, page_is_empty, absolute_boxes,
+ fixed_boxes, adjoining_margins):
"""Call the layout function corresponding to the ``box`` type."""
if isinstance(box, boxes.TableBox):
return table_layout(
context, box, max_position_y, skip_stack, containing_block,
- device_size, page_is_empty, absolute_boxes, fixed_boxes)
+ page_is_empty, absolute_boxes, fixed_boxes)
elif isinstance(box, boxes.BlockBox):
return block_box_layout(
context, box, max_position_y, skip_stack, containing_block,
- device_size, page_is_empty, absolute_boxes, fixed_boxes,
- adjoining_margins)
+ page_is_empty, absolute_boxes, fixed_boxes, adjoining_margins)
elif isinstance(box, boxes.BlockReplacedBox):
- box = block_replaced_box_layout(box, containing_block, device_size)
+ box = block_replaced_box_layout(box, containing_block)
# Don't collide with floats
# http://www.w3.org/TR/CSS21/visuren.html#floats
box.position_x, box.position_y, _ = avoid_collisions(
@@ -93,21 +89,20 @@ def block_level_layout_switch(context, box, max_position_y, skip_stack,
elif isinstance(box, boxes.FlexBox):
return flex_layout(
context, box, max_position_y, skip_stack, containing_block,
- device_size, page_is_empty, absolute_boxes, fixed_boxes)
+ page_is_empty, absolute_boxes, fixed_boxes)
else: # pragma: no cover
raise TypeError('Layout for %s not handled yet' % type(box).__name__)
def block_box_layout(context, box, max_position_y, skip_stack,
- containing_block, device_size, page_is_empty,
- absolute_boxes, fixed_boxes, adjoining_margins):
+ containing_block, page_is_empty, absolute_boxes,
+ fixed_boxes, adjoining_margins):
"""Lay out the block ``box``."""
if (box.style['column_width'] != 'auto' or
box.style['column_count'] != 'auto'):
result = columns_layout(
context, box, max_position_y, skip_stack, containing_block,
- device_size, page_is_empty, absolute_boxes, fixed_boxes,
- adjoining_margins)
+ page_is_empty, absolute_boxes, fixed_boxes, adjoining_margins)
resume_at = result[1]
# TODO: this condition and the whole relayout are probably wrong
@@ -120,8 +115,8 @@ def block_box_layout(context, box, max_position_y, skip_stack,
max_position_y -= bottom_spacing
result = columns_layout(
context, box, max_position_y, skip_stack,
- containing_block, device_size, page_is_empty,
- absolute_boxes, fixed_boxes, adjoining_margins)
+ containing_block, page_is_empty, absolute_boxes,
+ fixed_boxes, adjoining_margins)
return result
elif box.is_table_wrapper:
@@ -131,8 +126,8 @@ def block_box_layout(context, box, max_position_y, skip_stack,
new_box, resume_at, next_page, adjoining_margins, collapsing_through = \
block_container_layout(
- context, box, max_position_y, skip_stack, device_size,
- page_is_empty, absolute_boxes, fixed_boxes, adjoining_margins)
+ context, box, max_position_y, skip_stack, page_is_empty,
+ absolute_boxes, fixed_boxes, adjoining_margins)
if new_box and new_box.is_table_wrapper:
# Don't collide with floats
# http://www.w3.org/TR/CSS21/visuren.html#floats
@@ -144,26 +139,26 @@ def block_box_layout(context, box, max_position_y, skip_stack,
@handle_min_max_width
-def block_replaced_width(box, containing_block, device_size):
+def block_replaced_width(box, containing_block):
# http://www.w3.org/TR/CSS21/visudet.html#block-replaced-width
- replaced_box_width.without_min_max(box, device_size)
+ replaced_box_width.without_min_max(box, containing_block)
block_level_width.without_min_max(box, containing_block)
-def block_replaced_box_layout(box, containing_block, device_size):
+def block_replaced_box_layout(box, containing_block):
"""Lay out the block :class:`boxes.ReplacedBox` ``box``."""
box = box.copy()
if box.style['width'] == 'auto' and box.style['height'] == 'auto':
computed_margins = box.margin_left, box.margin_right
block_replaced_width.without_min_max(
- box, containing_block, device_size)
- replaced_box_height.without_min_max(box, device_size)
+ box, containing_block)
+ replaced_box_height.without_min_max(box)
min_max_auto_replaced(box)
box.margin_left, box.margin_right = computed_margins
block_level_width.without_min_max(box, containing_block)
else:
- block_replaced_width(box, containing_block, device_size)
- replaced_box_height(box, device_size)
+ block_replaced_width(box, containing_block)
+ replaced_box_height(box)
return box
@@ -257,8 +252,8 @@ def relative_positioning(box, containing_block):
def block_container_layout(context, box, max_position_y, skip_stack,
- device_size, page_is_empty, absolute_boxes,
- fixed_boxes, adjoining_margins=None):
+ page_is_empty, absolute_boxes, fixed_boxes,
+ adjoining_margins=None):
"""Set the ``box`` height."""
# TODO: boxes.FlexBox is allowed here because flex_layout calls
# block_container_layout, there's probably a better solution.
@@ -324,7 +319,8 @@ def block_container_layout(context, box, max_position_y, skip_stack,
else:
skip, skip_stack = skip_stack
first_letter_style = None
- for index, child in box.enumerate_skip(skip):
+ for i, child in enumerate(box.children[skip:]):
+ index = i + skip
child.position_x = position_x
# XXX does not count margins in adjoining_margins:
child.position_y = position_y
@@ -341,8 +337,7 @@ def block_container_layout(context, box, max_position_y, skip_stack,
fixed_boxes.append(placeholder)
elif child.is_floated():
new_child = float_layout(
- context, child, box, device_size, absolute_boxes,
- fixed_boxes)
+ context, child, box, absolute_boxes, fixed_boxes)
# New page if overflow
if (page_is_empty and not new_children) or not (
new_child.position_y + new_child.height >
@@ -357,7 +352,6 @@ def block_container_layout(context, box, max_position_y, skip_stack,
page_break = block_level_page_break(
last_in_flow_child, child)
if new_children and page_break in ('avoid', 'avoid-page'):
- # TODO: fill the blank space at the bottom of the page
result = find_earlier_page_break(
new_children, absolute_boxes, fixed_boxes)
if result:
@@ -376,7 +370,7 @@ def block_container_layout(context, box, max_position_y, skip_stack,
new_containing_block = box
lines_iterator = iter_line_boxes(
context, child, position_y, skip_stack,
- new_containing_block, device_size, absolute_boxes, fixed_boxes,
+ new_containing_block, absolute_boxes, fixed_boxes,
first_letter_style)
is_page_break = False
for line, resume_at in lines_iterator:
@@ -466,7 +460,6 @@ def block_container_layout(context, box, max_position_y, skip_stack,
new_containing_block = box
if not new_containing_block.is_table_wrapper:
- # TODO: there's no collapsing margins inside tables, right?
resolve_percentages(child, new_containing_block)
if (child.is_in_normal_flow() and
last_in_flow_child is None and
@@ -498,7 +491,7 @@ def block_container_layout(context, box, max_position_y, skip_stack,
adjoining_margins = []
position_y = box.content_box_y()
- if adjoining_margins and isinstance(child, boxes.TableBox):
+ if adjoining_margins and box.is_table_wrapper:
collapsed_margin = collapse_margin(adjoining_margins)
child.position_y += collapsed_margin
position_y += collapsed_margin
@@ -513,10 +506,8 @@ def block_container_layout(context, box, max_position_y, skip_stack,
(new_child, resume_at, next_page, next_adjoining_margins,
collapsing_through) = block_level_layout(
context, child, max_position_y, skip_stack,
- new_containing_block, device_size,
- page_is_empty_with_no_children,
- absolute_boxes, fixed_boxes,
- adjoining_margins)
+ new_containing_block, page_is_empty_with_no_children,
+ absolute_boxes, fixed_boxes, adjoining_margins)
skip_stack = None
if new_child is not None:
@@ -632,8 +623,11 @@ def block_container_layout(context, box, max_position_y, skip_stack,
# not adjoining. (position_y is not used afterwards.)
adjoining_margins = []
- if box.border_bottom_width or box.padding_bottom or (
- establishes_formatting_context(box) or box.is_for_root_element):
+ if (box.border_bottom_width or
+ box.padding_bottom or
+ establishes_formatting_context(box) or
+ box.is_for_root_element or
+ box.is_table_wrapper):
position_y += collapse_margin(adjoining_margins)
adjoining_margins = []
@@ -677,24 +671,6 @@ def block_container_layout(context, box, max_position_y, skip_stack,
if next_page['page'] is None:
next_page['page'] = new_box.page_values()[1]
- list_marker_layout(context, new_box)
- if new_box.outside_list_marker:
- # See https://www.w3.org/TR/css-lists-3/#list-style-position-outside
- #
- # "The size or contents of the marker box may affect the height of the
- # principal block box and/or the height of its first line box, and in
- # some cases may cause the creation of a new line box; this
- # interaction is also not defined."
- #
- # We decide here to set the list item box heigth to be at least the
- # height of the marker. Adding an empty line would theoretically be a
- # cleaner solution, but formatting the formatting structure is tricky
- # and we don't need another workaround there.
- collapsing_through = False
- adjoining_margins = []
- new_box.height = max(
- new_box.height, new_box.outside_list_marker.height)
-
return new_box, resume_at, next_page, adjoining_margins, collapsing_through
diff --git a/weasyprint/layout/columns.py b/weasyprint/layout/columns.py
index bc28ae867..7061f3386 100644
--- a/weasyprint/layout/columns.py
+++ b/weasyprint/layout/columns.py
@@ -16,7 +16,7 @@
def columns_layout(context, box, max_position_y, skip_stack, containing_block,
- device_size, page_is_empty, absolute_boxes, fixed_boxes,
+ page_is_empty, absolute_boxes, fixed_boxes,
adjoining_margins):
"""Lay out a multi-column ``box``."""
# Avoid circular imports
@@ -125,8 +125,8 @@ def create_column_box(children):
block.position_y = current_position_y
new_child, _, _, adjoining_margins, _ = block_level_layout(
context, block, original_max_position_y, skip_stack,
- containing_block, device_size, page_is_empty, absolute_boxes,
- fixed_boxes, adjoining_margins)
+ containing_block, page_is_empty, absolute_boxes, fixed_boxes,
+ adjoining_margins)
new_children.append(new_child)
current_position_y = (
new_child.border_height() + new_child.border_box_y())
@@ -142,7 +142,7 @@ def create_column_box(children):
column_box = create_column_box(column_children)
new_child, _, _, _, _ = block_box_layout(
context, column_box, float('inf'), skip_stack, containing_block,
- device_size, page_is_empty, [], [], [])
+ page_is_empty, [], [], [])
height = new_child.margin_height()
if style['column_fill'] == 'balance':
height /= count
@@ -156,8 +156,8 @@ def create_column_box(children):
# Render the column
new_box, resume_at, next_page, _, _ = block_box_layout(
context, column_box, box.content_box_y() + height,
- column_skip_stack, containing_block, device_size,
- page_is_empty, [], [], [])
+ column_skip_stack, containing_block, page_is_empty,
+ [], [], [])
column_skip_stack = resume_at
# Get the empty space at the bottom of the column box
@@ -168,8 +168,7 @@ def create_column_box(children):
# Get the minimum size needed to render the next box
next_box, _, _, _, _ = block_box_layout(
context, column_box, box.content_box_y(),
- column_skip_stack, containing_block, device_size, True,
- [], [], [])
+ column_skip_stack, containing_block, True, [], [], [])
next_box_size = next_box.children[0].margin_height()
# Append the size needed to render the next box in this
@@ -220,8 +219,8 @@ def create_column_box(children):
new_child, column_skip_stack, column_next_page, _, _ = (
block_box_layout(
context, column_box, max_position_y, skip_stack,
- containing_block, device_size, page_is_empty,
- absolute_boxes, fixed_boxes, None))
+ containing_block, page_is_empty, absolute_boxes,
+ fixed_boxes, None))
if new_child is None:
break
next_page = column_next_page
diff --git a/weasyprint/layout/flex.py b/weasyprint/layout/flex.py
index 3f0dc2e4e..dd6651244 100644
--- a/weasyprint/layout/flex.py
+++ b/weasyprint/layout/flex.py
@@ -24,7 +24,7 @@ class FlexLine(list):
def flex_layout(context, box, max_position_y, skip_stack, containing_block,
- device_size, page_is_empty, absolute_boxes, fixed_boxes):
+ page_is_empty, absolute_boxes, fixed_boxes):
# Avoid a circular import
from . import blocks, preferred
@@ -152,8 +152,7 @@ def flex_layout(context, box, max_position_y, skip_stack, containing_block,
new_child.style['max_height'] = Dimension(float('inf'), 'px')
new_child = blocks.block_level_layout(
context, new_child, float('inf'), child_skip_stack,
- parent_box, device_size, page_is_empty, absolute_boxes=[],
- fixed_boxes=[], adjoining_margins=[])[0]
+ parent_box, page_is_empty, [], [], [])[0]
content_size = new_child.height
child.min_height = min(specified_size, content_size)
@@ -212,8 +211,8 @@ def flex_layout(context, box, max_position_y, skip_stack, containing_block,
new_child.width = float('inf')
new_child = blocks.block_level_layout(
context, new_child, float('inf'), child_skip_stack,
- parent_box, device_size, page_is_empty, absolute_boxes,
- fixed_boxes, adjoining_margins=[])[0]
+ parent_box, page_is_empty, absolute_boxes, fixed_boxes,
+ adjoining_margins=[])[0]
child.flex_base_size = new_child.margin_height()
elif child.style[axis] == 'min-content':
child.style[axis] = 'auto'
@@ -227,8 +226,8 @@ def flex_layout(context, box, max_position_y, skip_stack, containing_block,
new_child.width = 0
new_child = blocks.block_level_layout(
context, new_child, float('inf'), child_skip_stack,
- parent_box, device_size, page_is_empty, absolute_boxes,
- fixed_boxes, adjoining_margins=[])[0]
+ parent_box, page_is_empty, absolute_boxes, fixed_boxes,
+ adjoining_margins=[])[0]
child.flex_base_size = new_child.margin_height()
else:
assert child.style[axis].unit == 'px'
@@ -461,8 +460,8 @@ def flex_layout(context, box, max_position_y, skip_stack, containing_block,
new_child, _, _, adjoining_margins, _ = (
blocks.block_level_layout_switch(
context, child_copy, float('inf'), child_skip_stack,
- parent_box, device_size, page_is_empty, absolute_boxes,
- fixed_boxes, adjoining_margins=[]))
+ parent_box, page_is_empty, absolute_boxes, fixed_boxes,
+ adjoining_margins=[]))
child._baseline = find_in_flow_baseline(new_child) or 0
if cross == 'height':
@@ -835,7 +834,7 @@ def flex_layout(context, box, max_position_y, skip_stack, containing_block,
if child.is_flex_item:
new_child, child_resume_at = blocks.block_level_layout_switch(
context, child, max_position_y, child_skip_stack, box,
- device_size, page_is_empty, absolute_boxes, fixed_boxes,
+ page_is_empty, absolute_boxes, fixed_boxes,
adjoining_margins=[])[:2]
if new_child is None:
if resume_at and resume_at[0]:
diff --git a/weasyprint/layout/float.py b/weasyprint/layout/float.py
index 1bca1e765..c521f4cde 100644
--- a/weasyprint/layout/float.py
+++ b/weasyprint/layout/float.py
@@ -24,8 +24,7 @@ def float_width(box, context, containing_block):
box.width = shrink_to_fit(context, box, containing_block.width)
-def float_layout(context, box, containing_block, device_size, absolute_boxes,
- fixed_boxes):
+def float_layout(context, box, containing_block, absolute_boxes, fixed_boxes):
"""Set the width and position of floating ``box``."""
# Avoid circular imports
from .blocks import block_container_layout
@@ -57,7 +56,7 @@ def float_layout(context, box, containing_block, device_size, absolute_boxes,
box.position_y += clearance
if isinstance(box, boxes.BlockReplacedBox):
- inline_replaced_box_width_height(box, device_size=None)
+ inline_replaced_box_width_height(box, containing_block)
elif box.width == 'auto':
float_width(box, context, containing_block)
@@ -68,7 +67,7 @@ def float_layout(context, box, containing_block, device_size, absolute_boxes,
context.create_block_formatting_context()
box, _, _, _, _ = block_container_layout(
context, box, max_position_y=float('inf'),
- skip_stack=None, device_size=device_size, page_is_empty=False,
+ skip_stack=None, page_is_empty=False,
absolute_boxes=absolute_boxes, fixed_boxes=fixed_boxes,
adjoining_margins=None)
context.finish_block_formatting_context(box)
@@ -76,8 +75,8 @@ def float_layout(context, box, containing_block, device_size, absolute_boxes,
box, _, _, _, _ = flex_layout(
context, box, max_position_y=float('inf'),
skip_stack=None, containing_block=containing_block,
- device_size=device_size, page_is_empty=False,
- absolute_boxes=absolute_boxes, fixed_boxes=fixed_boxes)
+ page_is_empty=False, absolute_boxes=absolute_boxes,
+ fixed_boxes=fixed_boxes)
else:
assert isinstance(box, boxes.BlockReplacedBox)
diff --git a/weasyprint/layout/inlines.py b/weasyprint/layout/inlines.py
index 21878229c..c42c38ca6 100644
--- a/weasyprint/layout/inlines.py
+++ b/weasyprint/layout/inlines.py
@@ -14,7 +14,7 @@
from ..css import computed_from_cascaded
from ..css.computed_values import ex_ratio, strut_layout
from ..formatting_structure import boxes
-from ..text import can_break_text, split_first_line
+from ..text import can_break_text, create_layout, split_first_line
from .absolute import AbsolutePlaceholder, absolute_layout
from .flex import flex_layout
from .float import avoid_collisions, float_layout
@@ -22,13 +22,11 @@
from .percentages import resolve_one_percentage, resolve_percentages
from .preferred import (
inline_min_content_width, shrink_to_fit, trailing_whitespace_size)
-from .replaced import image_marker_layout
from .tables import find_in_flow_baseline, table_wrapper_width
def iter_line_boxes(context, box, position_y, skip_stack, containing_block,
- device_size, absolute_boxes, fixed_boxes,
- first_letter_style):
+ absolute_boxes, fixed_boxes, first_letter_style):
"""Return an iterator of ``(line, resume_at)``.
``line`` is a laid-out LineBox with as much content as possible that
@@ -41,7 +39,6 @@ def iter_line_boxes(context, box, position_y, skip_stack, containing_block,
already laid-out line.
:param containing_block: Containing block of the line box:
a :class:`BlockContainerBox`
- :param device_size: ``(width, height)`` of the current page.
"""
resolve_percentages(box, containing_block)
@@ -53,7 +50,7 @@ def iter_line_boxes(context, box, position_y, skip_stack, containing_block,
while 1:
line, resume_at = get_next_linebox(
context, box, position_y, skip_stack, containing_block,
- device_size, absolute_boxes, fixed_boxes, first_letter_style)
+ absolute_boxes, fixed_boxes, first_letter_style)
if line:
position_y = line.position_y + line.height
if line is None:
@@ -67,8 +64,8 @@ def iter_line_boxes(context, box, position_y, skip_stack, containing_block,
def get_next_linebox(context, linebox, position_y, skip_stack,
- containing_block, device_size, absolute_boxes,
- fixed_boxes, first_letter_style):
+ containing_block, absolute_boxes, fixed_boxes,
+ first_letter_style):
"""Return ``(line, resume_at)``."""
skip_stack = skip_first_whitespace(linebox, skip_stack)
if skip_stack == 'continue':
@@ -76,13 +73,19 @@ def get_next_linebox(context, linebox, position_y, skip_stack,
skip_stack = first_letter_to_box(linebox, skip_stack, first_letter_style)
- linebox.width = inline_min_content_width(
- context, linebox, skip_stack=skip_stack, first_line=True)
-
- linebox.height, _ = strut_layout(linebox.style, context)
linebox.position_y = position_y
+
+ if context.excluded_shapes:
+ # Width and height must be calculated to avoid floats
+ linebox.width = inline_min_content_width(
+ context, linebox, skip_stack=skip_stack, first_line=True)
+ linebox.height, _ = strut_layout(linebox.style, context)
+ else:
+ # No float, width and height will be set by the lines
+ linebox.width = linebox.height = 0
position_x, position_y, available_width = avoid_collisions(
context, linebox, containing_block, outer=False)
+
candidate_height = linebox.height
excluded_shapes = context.excluded_shapes[:]
@@ -101,8 +104,9 @@ def get_next_linebox(context, linebox, position_y, skip_stack,
(line, resume_at, preserved_line_break, first_letter,
last_letter, float_width) = split_inline_box(
context, linebox, position_x, max_x, skip_stack, containing_block,
- device_size, line_absolutes, line_fixed, line_placeholders,
- waiting_floats, line_children=[])
+ line_absolutes, line_fixed, line_placeholders, waiting_floats,
+ line_children=[])
+ linebox.width, linebox.height = line.width, line.height
if is_phantom_linebox(line) and not preserved_line_break:
line.height = 0
@@ -167,8 +171,8 @@ def get_next_linebox(context, linebox, position_y, skip_stack,
for waiting_float in waiting_floats:
waiting_float.position_y = waiting_floats_y
waiting_float = float_layout(
- context, waiting_float, containing_block, device_size,
- absolute_boxes, fixed_boxes)
+ context, waiting_float, containing_block, absolute_boxes,
+ fixed_boxes)
float_children.append(waiting_float)
if float_children:
line.children += tuple(float_children)
@@ -332,10 +336,12 @@ def first_letter_to_box(box, skip_stack, first_letter_style):
@handle_min_max_width
-def replaced_box_width(box, device_size):
+def replaced_box_width(box, containing_block):
"""
Compute and set the used width for replaced boxes (inline- or block-level)
"""
+ from .blocks import block_level_width
+
intrinsic_width, intrinsic_height = box.replacement.get_intrinsic_size(
box.style['image_resolution'], box.style['font_size'])
@@ -351,15 +357,7 @@ def replaced_box_width(box, device_size):
box.width = intrinsic_height * box.replacement.intrinsic_ratio
else:
# Point #3
- # " It is suggested that, if the containing block's width does
- # not itself depend on the replaced element's width, then the
- # used value of 'width' is calculated from the constraint
- # equation used for block-level, non-replaced elements in
- # normal flow. "
- # Whaaaaat? Let's not do this and use a value that may work
- # well at least with inline blocks.
- box.width = (
- box.style['font_size'] * box.replacement.intrinsic_ratio)
+ block_level_width(box, containing_block)
if box.width == 'auto':
if box.replacement.intrinsic_ratio is not None:
@@ -370,12 +368,12 @@ def replaced_box_width(box, device_size):
box.width = intrinsic_width
else:
# Point #5
- device_width, _device_height = device_size
- box.width = min(300, device_width)
+ # It's pretty useless to rely on device size to set width.
+ box.width = 300
@handle_min_max_height
-def replaced_box_height(box, device_size):
+def replaced_box_height(box):
"""
Compute and set the used height for replaced boxes (inline- or block-level)
"""
@@ -398,26 +396,26 @@ def replaced_box_height(box, device_size):
elif box.height == 'auto' and intrinsic_height is not None:
box.height = intrinsic_height
elif box.height == 'auto':
- device_width, _device_height = device_size
- box.height = min(150, device_width / 2)
+ # It's pretty useless to rely on device size to set width.
+ box.height = 150
-def inline_replaced_box_layout(box, device_size):
+def inline_replaced_box_layout(box, containing_block):
"""Lay out an inline :class:`boxes.ReplacedBox` ``box``."""
for side in ['top', 'right', 'bottom', 'left']:
if getattr(box, 'margin_' + side) == 'auto':
setattr(box, 'margin_' + side, 0)
- inline_replaced_box_width_height(box, device_size)
+ inline_replaced_box_width_height(box, containing_block)
-def inline_replaced_box_width_height(box, device_size):
+def inline_replaced_box_width_height(box, containing_block):
if box.style['width'] == 'auto' and box.style['height'] == 'auto':
- replaced_box_width.without_min_max(box, device_size)
- replaced_box_height.without_min_max(box, device_size)
+ replaced_box_width.without_min_max(box, containing_block)
+ replaced_box_height.without_min_max(box)
min_max_auto_replaced(box)
else:
- replaced_box_width(box, device_size)
- replaced_box_height(box, device_size)
+ replaced_box_width(box, containing_block)
+ replaced_box_height(box)
def min_max_auto_replaced(box):
@@ -479,14 +477,11 @@ def min_max_auto_replaced(box):
def atomic_box(context, box, position_x, skip_stack, containing_block,
- device_size, absolute_boxes, fixed_boxes):
+ absolute_boxes, fixed_boxes):
"""Compute the width and the height of the atomic ``box``."""
if isinstance(box, boxes.ReplacedBox):
box = box.copy()
- if getattr(box, 'is_list_marker', False):
- image_marker_layout(box)
- else:
- inline_replaced_box_layout(box, device_size)
+ inline_replaced_box_layout(box, containing_block)
box.baseline = box.margin_height()
elif isinstance(box, boxes.InlineBlockBox):
if box.is_table_wrapper:
@@ -495,15 +490,14 @@ def atomic_box(context, box, position_x, skip_stack, containing_block,
(containing_block.width, containing_block.height))
box = inline_block_box_layout(
context, box, position_x, skip_stack, containing_block,
- device_size, absolute_boxes, fixed_boxes)
+ absolute_boxes, fixed_boxes)
else: # pragma: no cover
raise TypeError('Layout for %s not handled yet' % type(box).__name__)
return box
def inline_block_box_layout(context, box, position_x, skip_stack,
- containing_block, device_size, absolute_boxes,
- fixed_boxes):
+ containing_block, absolute_boxes, fixed_boxes):
# Avoid a circular import
from .blocks import block_container_layout
@@ -526,8 +520,8 @@ def inline_block_box_layout(context, box, position_x, skip_stack,
box.position_y = 0
box, _, _, _, _ = block_container_layout(
context, box, max_position_y=float('inf'), skip_stack=skip_stack,
- device_size=device_size, page_is_empty=True,
- absolute_boxes=absolute_boxes, fixed_boxes=fixed_boxes)
+ page_is_empty=True, absolute_boxes=absolute_boxes,
+ fixed_boxes=fixed_boxes)
box.baseline = inline_block_baseline(box)
return box
@@ -561,9 +555,8 @@ def inline_block_width(box, context, containing_block):
def split_inline_level(context, box, position_x, max_x, skip_stack,
- containing_block, device_size, absolute_boxes,
- fixed_boxes, line_placeholders, waiting_floats,
- line_children):
+ containing_block, absolute_boxes, fixed_boxes,
+ line_placeholders, waiting_floats, line_children):
"""Fit as much content as possible from an inline-level box in a width.
Return ``(new_box, resume_at, preserved_line_break, first_letter,
@@ -610,12 +603,12 @@ def split_inline_level(context, box, position_x, max_x, skip_stack,
(new_box, resume_at, preserved_line_break, first_letter,
last_letter, float_widths) = split_inline_box(
context, box, position_x, max_x, skip_stack, containing_block,
- device_size, absolute_boxes, fixed_boxes, line_placeholders,
- waiting_floats, line_children)
+ absolute_boxes, fixed_boxes, line_placeholders, waiting_floats,
+ line_children)
elif isinstance(box, boxes.AtomicInlineLevelBox):
new_box = atomic_box(
context, box, position_x, skip_stack, containing_block,
- device_size, absolute_boxes, fixed_boxes)
+ absolute_boxes, fixed_boxes)
new_box.position_x = position_x
resume_at = None
preserved_line_break = False
@@ -631,7 +624,7 @@ def split_inline_level(context, box, position_x, max_x, skip_stack,
setattr(box, 'margin_' + side, 0)
new_box, resume_at, _, _, _ = flex_layout(
context, box, float('inf'), skip_stack, containing_block,
- device_size, False, absolute_boxes, fixed_boxes)
+ False, absolute_boxes, fixed_boxes)
preserved_line_break = False
first_letter = '\u2e80'
last_letter = '\u2e80'
@@ -643,9 +636,8 @@ def split_inline_level(context, box, position_x, max_x, skip_stack,
def split_inline_box(context, box, position_x, max_x, skip_stack,
- containing_block, device_size, absolute_boxes,
- fixed_boxes, line_placeholders, waiting_floats,
- line_children):
+ containing_block, absolute_boxes, fixed_boxes,
+ line_placeholders, waiting_floats, line_children):
"""Same behavior as split_inline_level."""
# In some cases (shrink-to-fit result being the preferred width)
@@ -681,8 +673,8 @@ def split_inline_box(context, box, position_x, max_x, skip_stack,
else:
skip, skip_stack = skip_stack
- box_children = list(box.enumerate_skip(skip))
- for i, (index, child) in enumerate(box_children):
+ for i, child in enumerate(box.children[skip:]):
+ index = i + skip
child.position_y = box.position_y
if child.is_absolutely_positioned():
child.position_x = position_x
@@ -696,8 +688,7 @@ def split_inline_box(context, box, position_x, max_x, skip_stack,
continue
elif child.is_floated():
child.position_x = position_x
- float_width = shrink_to_fit(
- context, child, containing_block.width)
+ float_width = shrink_to_fit(context, child, containing_block.width)
# To retrieve the real available space for floats, we must remove
# the trailing whitespaces from the line
@@ -714,8 +705,8 @@ def split_inline_box(context, box, position_x, max_x, skip_stack,
waiting_floats.append(child)
else:
child = float_layout(
- context, child, containing_block, device_size,
- absolute_boxes, fixed_boxes)
+ context, child, containing_block, absolute_boxes,
+ fixed_boxes)
waiting_children.append((index, child))
# Translate previous line children
@@ -743,13 +734,13 @@ def split_inline_box(context, box, position_x, max_x, skip_stack,
float_resume_at = index + 1
continue
- last_child = (i == len(box_children) - 1)
+ last_child = (index == len(box.children) - 1)
available_width = max_x
child_waiting_floats = []
new_child, resume_at, preserved, first, last, new_float_widths = (
split_inline_level(
context, child, position_x, available_width, skip_stack,
- containing_block, device_size, absolute_boxes, fixed_boxes,
+ containing_block, absolute_boxes, fixed_boxes,
line_placeholders, child_waiting_floats, line_children))
if last_child and right_spacing and resume_at is None:
# TODO: we should take care of children added into absolute_boxes,
@@ -761,7 +752,7 @@ def split_inline_box(context, box, position_x, max_x, skip_stack,
new_child, resume_at, preserved, first, last, new_float_widths = (
split_inline_level(
context, child, position_x, available_width, skip_stack,
- containing_block, device_size, absolute_boxes, fixed_boxes,
+ containing_block, absolute_boxes, fixed_boxes,
line_placeholders, child_waiting_floats, line_children))
if box.style['direction'] == 'rtl':
@@ -835,8 +826,7 @@ def split_inline_box(context, box, position_x, max_x, skip_stack,
child_new_child, child_resume_at, _, _, _, _ = (
split_inline_level(
context, child, child.position_x, max_x,
- None, box, device_size,
- absolute_boxes, fixed_boxes,
+ None, box, absolute_boxes, fixed_boxes,
line_placeholders, waiting_floats,
line_children))
@@ -1130,7 +1120,6 @@ def inline_box_verticality(box, top_bottom_subtrees, baseline_y):
one_ex = box.style['font_size'] * ex_ratio(box.style)
top = baseline_y - (one_ex + child.margin_height()) / 2.
child_baseline_y = top + child.baseline
- # TODO: actually implement vertical-align: top and bottom
elif vertical_align == 'text-top':
# align top with the top of the parent’s content area
top = (baseline_y - box.baseline + box.margin_top +
@@ -1142,6 +1131,7 @@ def inline_box_verticality(box, top_bottom_subtrees, baseline_y):
box.border_top_width + box.padding_top + box.height)
child_baseline_y = bottom - child.margin_height() + child.baseline
elif vertical_align in ('top', 'bottom'):
+ # TODO: actually implement vertical-align: top and bottom
# Later, we will assume for this subtree that its baseline
# is at y=0.
child_baseline_y = 0
@@ -1243,10 +1233,10 @@ def add_word_spacing(context, box, justification_spacing, x_advance):
box.position_x += x_advance
nb_spaces = count_spaces(box)
if nb_spaces > 0:
- layout, _, resume_at, _, _, _ = split_first_line(
+ layout = create_layout(
box.text, box.style, context, float('inf'),
box.justification_spacing)
- assert resume_at is None
+ layout.deactivate()
extra_space = justification_spacing * nb_spaces
x_advance += extra_space
box.width += extra_space
diff --git a/weasyprint/layout/markers.py b/weasyprint/layout/markers.py
deleted file mode 100644
index c7d9388f1..000000000
--- a/weasyprint/layout/markers.py
+++ /dev/null
@@ -1,66 +0,0 @@
-"""
- weasyprint.layout.markers
- -------------------------
-
- Layout for list markers (for ``display: list-item``).
-
- :copyright: Copyright 2011-2019 Simon Sapin and contributors, see AUTHORS.
- :license: BSD, see LICENSE for details.
-
-"""
-
-from ..formatting_structure import boxes
-from ..text import split_first_line
-from .percentages import resolve_percentages
-from .replaced import image_marker_layout
-from .tables import find_in_flow_baseline
-
-
-def list_marker_layout(context, box):
- """Lay out the list markers of ``box``."""
- # List markers can be either 'inside' or 'outside'.
- # Inside markers are layed out just like normal inline content, but
- # outside markers need specific layout.
- # TODO: implement outside markers in terms of absolute positioning,
- # see CSS3 lists.
- marker = box.outside_list_marker
- if marker:
- # Make a copy to ensure unique markers when the marker's layout has
- # already been done.
- # TODO: this should be done in layout_fixed_boxes
- if hasattr(marker, 'position_x'):
- marker = box.outside_list_marker = marker.copy()
-
- resolve_percentages(marker, containing_block=box)
- if isinstance(marker, boxes.TextBox):
- (marker.pango_layout, _, _, marker.width, marker.height,
- marker.baseline) = split_first_line(
- marker.text, marker.style, context, max_width=None,
- justification_spacing=marker.justification_spacing)
- baseline = find_in_flow_baseline(box)
- else:
- # Image marker
- image_marker_layout(marker)
-
- if isinstance(marker, boxes.TextBox) and baseline:
- # Align the baseline of the marker box with the baseline of the
- # first line of its list-item’s content-box.
- marker.position_y = baseline - marker.baseline
- else:
- # Align the top of the marker box with the top of its list-item’s
- # content-box.
- marker.position_y = box.content_box_y()
-
- # ... and its right with the left of its list-item’s padding box.
- # (Swap left and right for right-to-left text.)
- marker.position_x = box.border_box_x()
-
- direction = box.style['direction']
- if direction == 'ltr':
- marker.position_x -= marker.margin_width()
- else:
- # Move to the right margin.
- marker.position_x += box.border_width()
- if isinstance(marker, boxes.TextBox):
- # Take margin/padding into account.
- marker.position_x += marker.margin_width() - (marker.width)
diff --git a/weasyprint/layout/pages.py b/weasyprint/layout/pages.py
index 65a5492a4..60a0800e4 100644
--- a/weasyprint/layout/pages.py
+++ b/weasyprint/layout/pages.py
@@ -439,8 +439,7 @@ def margin_box_content_layout(context, page, box):
box, resume_at, next_page, _, _ = block_container_layout(
context, box,
max_position_y=float('inf'), skip_stack=None,
- device_size=page.style['size'], page_is_empty=True,
- absolute_boxes=[], fixed_boxes=[])
+ page_is_empty=True, absolute_boxes=[], fixed_boxes=[])
assert resume_at is None
vertical_align = box.style['vertical_align']
@@ -550,8 +549,8 @@ def make_page(context, root_box, page_type, resume_at, page_number,
positioned_boxes = [] # Mixed absolute and fixed
root_box, resume_at, next_page, _, _ = block_level_layout(
context, root_box, page_content_bottom, resume_at,
- initial_containing_block, device_size, page_is_empty,
- positioned_boxes, positioned_boxes, adjoining_margins)
+ initial_containing_block, page_is_empty, positioned_boxes,
+ positioned_boxes, adjoining_margins)
assert root_box
page.fixed_boxes = [
diff --git a/weasyprint/layout/preferred.py b/weasyprint/layout/preferred.py
index a43ac1afb..4e3d28ae4 100644
--- a/weasyprint/layout/preferred.py
+++ b/weasyprint/layout/preferred.py
@@ -257,7 +257,7 @@ def inline_line_widths(context, box, outer, is_line_start, minimum,
skip = 0
else:
skip, skip_stack = skip_stack
- for index, child in box.enumerate_skip(skip):
+ for child in box.children[skip:]:
if child.is_absolutely_positioned():
continue # Skip
diff --git a/weasyprint/layout/replaced.py b/weasyprint/layout/replaced.py
index beeabe0a8..f989fa3be 100644
--- a/weasyprint/layout/replaced.py
+++ b/weasyprint/layout/replaced.py
@@ -13,22 +13,6 @@
from .percentages import percentage
-def image_marker_layout(box):
- """Layout the :class:`boxes.ImageMarkerBox` ``box``.
-
- :class:`boxes.ImageMarkerBox` objects are :class:`boxes.ReplacedBox`
- objects, but their used size is computed differently.
-
- """
- image = box.replacement
- one_em = box.style['font_size']
- iwidth, iheight = image.get_intrinsic_size(
- box.style['image_resolution'], one_em)
- box.width, box.height = default_image_sizing(
- iwidth, iheight, image.intrinsic_ratio, box.width, box.height,
- default_width=one_em, default_height=one_em)
-
-
def default_image_sizing(intrinsic_width, intrinsic_height, intrinsic_ratio,
specified_width, specified_height,
default_width, default_height):
diff --git a/weasyprint/layout/tables.py b/weasyprint/layout/tables.py
index c81d77b78..fd66e9c32 100644
--- a/weasyprint/layout/tables.py
+++ b/weasyprint/layout/tables.py
@@ -15,9 +15,8 @@
from .preferred import max_content_width, table_and_columns_preferred_widths
-def table_layout(context, table, max_position_y, skip_stack,
- containing_block, device_size, page_is_empty, absolute_boxes,
- fixed_boxes):
+def table_layout(context, table, max_position_y, skip_stack, containing_block,
+ page_is_empty, absolute_boxes, fixed_boxes):
"""Layout for a table box."""
# Avoid a circular import
from .blocks import block_container_layout
@@ -77,7 +76,8 @@ def group_layout(group, position_y, max_position_y,
else:
skip, skip_stack = skip_stack
assert not skip_stack # No breaks inside rows for now
- for index_row, row in group.enumerate_skip(skip):
+ for i, row in enumerate(group.children[skip:]):
+ index_row = i + skip
resolve_percentages(row, containing_block=table)
row.position_x = rows_x
row.position_y = position_y
@@ -122,7 +122,6 @@ def group_layout(group, position_y, max_position_y,
context, cell,
max_position_y=float('inf'),
skip_stack=None,
- device_size=device_size,
page_is_empty=True,
absolute_boxes=absolute_boxes,
fixed_boxes=fixed_boxes)
@@ -251,7 +250,8 @@ def body_groups_layout(skip_stack, position_y, max_position_y,
skip, skip_stack = skip_stack
new_table_children = []
resume_at = None
- for index_group, group in table.enumerate_skip(skip):
+ for i, group in enumerate(table.children[skip:]):
+ index_group = i + skip
if group.is_header or group.is_footer:
continue
new_group, resume_at = group_layout(
diff --git a/weasyprint/tests/test_boxes.py b/weasyprint/tests/test_boxes.py
index b90c79a2c..d14429c96 100644
--- a/weasyprint/tests/test_boxes.py
+++ b/weasyprint/tests/test_boxes.py
@@ -585,7 +585,6 @@ def test_tables_5():
@assert_no_logs
def test_tables_6():
- # TODO: re-enable this once we support inline-table
# Rule 3.2
assert_tree(parse_all('
abc', + '
abc', +)) +def test_display_none_root(html): + box = parse_all(html) + assert box.style['display'] == 'block' + assert not box.children diff --git a/weasyprint/tests/test_css.py b/weasyprint/tests/test_css.py index 5046675d0..dcb38bd27 100644 --- a/weasyprint/tests/test_css.py +++ b/weasyprint/tests/test_css.py @@ -161,8 +161,8 @@ def test_annotate_document(): assert after['border_top_width'] == 42 assert after['border_bottom_width'] == 3 - # TODO much more tests here: test that origin and selector precedence - # and inheritance are correct, ... + # TODO: much more tests here: test that origin and selector precedence + # and inheritance are correct… @assert_no_logs @@ -303,9 +303,9 @@ def test_page_selectors(style, selectors): ('::lipsum { margin: 2cm', ['WARNING: Invalid or unsupported selector']), ('foo { margin-color: red', ['WARNING: Ignored', 'unknown property']), ('foo { margin-top: red', ['WARNING: Ignored', 'invalid value']), - ('@import "relative-uri.css', + ('@import "relative-uri.css"', ['ERROR: Relative URI reference without a base URI']), - ('@import "invalid-protocol://absolute-URL', + ('@import "invalid-protocol://absolute-URL"', ['ERROR: Failed to load stylesheet at']), )) def test_warnings(source, messages): diff --git a/weasyprint/tests/test_css_validation.py b/weasyprint/tests/test_css_validation.py index 098f16dad..b0173cb3c 100644 --- a/weasyprint/tests/test_css_validation.py +++ b/weasyprint/tests/test_css_validation.py @@ -572,7 +572,6 @@ def test_font(): assert expand_to_dict( 'font: small-caps condensed normal 700 large serif' ) == { - # 'font_style': 'normal', XXX shouldn’t this be here? 'font_stretch': 'condensed', 'font_variant_caps': 'small-caps', 'font_weight': 700, @@ -587,6 +586,10 @@ def test_font(): assert_invalid('font: 12px') assert_invalid('font: 12px/foo serif') assert_invalid('font: 12px "Invalid" family') + assert_invalid('font: normal normal normal normal normal large serif') + assert_invalid('font: normal small-caps italic 700 condensed large serif') + assert_invalid('font: small-caps italic 700 normal condensed large serif') + assert_invalid('font: small-caps italic 700 condensed normal large serif') @assert_no_logs diff --git a/weasyprint/tests/test_draw/test_list.py b/weasyprint/tests/test_draw/test_list.py index 3f227e046..b295f9599 100644 --- a/weasyprint/tests/test_draw/test_list.py +++ b/weasyprint/tests/test_draw/test_list.py @@ -25,10 +25,10 @@ ''' ____________ ____________ - __rBBB______ - __BBBB______ - __BBBB______ - __BBBB______ + ___rBBB_____ + ___BBBB_____ + ___BBBB_____ + ___BBBB_____ ____________ ____________ ____________ diff --git a/weasyprint/tests/test_draw/test_transform.py b/weasyprint/tests/test_draw/test_transform.py index 58c0cce5c..4e843ac39 100644 --- a/weasyprint/tests/test_draw/test_transform.py +++ b/weasyprint/tests/test_draw/test_transform.py @@ -218,7 +218,7 @@ def test_2d_transform_10(): @@ -266,7 +266,7 @@ def test_2d_transform_12(): diff --git a/weasyprint/tests/test_layout/test_image.py b/weasyprint/tests/test_layout/test_image.py index 19d60386f..6d9d818ef 100644 --- a/weasyprint/tests/test_layout/test_image.py +++ b/weasyprint/tests/test_layout/test_image.py @@ -302,6 +302,26 @@ def test_images_16(): assert img.height == 200 +@assert_no_logs +def test_images_17(): + page, = parse(''' +
+ |
+ |