From 44adb875e3810d4100e2f2983201a45aeb4137ca Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Tue, 29 May 2012 16:01:59 +0200 Subject: [PATCH 01/79] Add basic management of float elements --- weasyprint/css/computed_values.py | 12 ++++++++++++ weasyprint/css/validation.py | 14 ++++++++++++++ weasyprint/layout/blocks.py | 12 +++++++++--- weasyprint/stacking.py | 3 ++- 4 files changed, 37 insertions(+), 4 deletions(-) diff --git a/weasyprint/css/computed_values.py b/weasyprint/css/computed_values.py index 3120ff62..3d4a21c1 100644 --- a/weasyprint/css/computed_values.py +++ b/weasyprint/css/computed_values.py @@ -328,6 +328,18 @@ def border_width(computer, name, value): return length(computer, name, value, pixels_only=True) +@register_computer('clear') +def clear(computer, name, values): + """Compute the ``clear`` property.""" + return values + + +@register_computer('float') +def float(computer, name, values): + """Compute the ``float`` property.""" + return values + + @register_computer('content') def content(computer, name, values): """Compute the ``content`` property.""" diff --git a/weasyprint/css/validation.py b/weasyprint/css/validation.py index 30328ede..e4db13dc 100644 --- a/weasyprint/css/validation.py +++ b/weasyprint/css/validation.py @@ -330,6 +330,13 @@ def caption_side(keyword): return keyword in ('top', 'bottom') +@validator() +@single_keyword +def clear(keyword): + """``clear`` property validation.""" + return keyword in ('left', 'right', 'both', 'none') + + @validator() @single_token def clip(token): @@ -498,6 +505,13 @@ def display(keyword): 'table-row', 'table-column-group', 'table-column', 'table-cell') +@validator() +@single_keyword +def float(keyword): + """``float`` property validation.""" + return keyword in ('left', 'right', 'none') + + @validator() def font_family(tokens): """``font-family`` property validation.""" diff --git a/weasyprint/layout/blocks.py b/weasyprint/layout/blocks.py index df6bea07..78b2de7a 100644 --- a/weasyprint/layout/blocks.py +++ b/weasyprint/layout/blocks.py @@ -13,6 +13,7 @@ from __future__ import division, unicode_literals from .absolute import absolute_layout, AbsolutePlaceholder +from .float import float_layout, FloatPlaceholder from .inlines import (iter_line_boxes, replaced_box_width, replaced_box_height, handle_min_max_width, min_max_replaced_height, min_max_auto_replaced) @@ -225,6 +226,7 @@ def block_container_layout(document, box, max_position_y, skip_stack, absolute_boxes = [] new_children = [] + float_children = [] next_page = 'any' is_start = skip_stack is None @@ -247,9 +249,10 @@ def block_container_layout(document, box, max_position_y, skip_stack, new_children.append(placeholder) else: document.fixed_boxes.append(placeholder) - else: - # TODO: Floats - new_children.append(child) + elif child.style.float in ('left', 'right'): + placeholder = FloatPlaceholder(child) + float_children.append(placeholder) + new_children.append(placeholder) continue if isinstance(child, boxes.LineBox): @@ -427,6 +430,9 @@ def block_container_layout(document, box, max_position_y, skip_stack, for absolute_box in absolute_boxes: absolute_layout(document, absolute_box, new_box) + for float_box in float_children: + float_layout(document, float_box, new_box, absolute_boxes) + for child in new_box.children: relative_positioning(child, (new_box.width, new_box.height)) diff --git a/weasyprint/stacking.py b/weasyprint/stacking.py index 95b18331..d9a252d6 100644 --- a/weasyprint/stacking.py +++ b/weasyprint/stacking.py @@ -14,6 +14,7 @@ import operator from .formatting_structure import boxes from .layout.absolute import AbsolutePlaceholder +from .layout.float import FloatPlaceholder _Z_INDEX_GETTER = operator.attrgetter('z_index') @@ -68,7 +69,7 @@ class StackingContext(object): blocks_and_cells = [] def dispatch_children(box): - if isinstance(box, AbsolutePlaceholder): + if isinstance(box, (AbsolutePlaceholder, FloatPlaceholder)): box = box._box if not isinstance(box, boxes.ParentBox): From 51a809fa37c6fad0b1b0b8da1edd45be39eb9417 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Tue, 29 May 2012 18:31:28 +0200 Subject: [PATCH 02/79] Add the missing file :p --- weasyprint/layout/float.py | 93 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 weasyprint/layout/float.py diff --git a/weasyprint/layout/float.py b/weasyprint/layout/float.py new file mode 100644 index 00000000..cb5c9dd9 --- /dev/null +++ b/weasyprint/layout/float.py @@ -0,0 +1,93 @@ +# coding: utf8 +""" + weasyprint.float + ---------------- + + :copyright: Copyright 2011-2012 Simon Sapin and contributors, see AUTHORS. + :license: BSD, see LICENSE for details. + +""" + +from __future__ import division, unicode_literals + +from .absolute import absolute_layout +from .inlines import replaced_box_width, replaced_box_height +from .percentages import resolve_percentages, resolve_position_percentages +from .preferred import shrink_to_fit +from ..formatting_structure import boxes + + +class FloatPlaceholder(object): + """Left where an float box was taken out of the flow.""" + def __init__(self, box): + # Work around the overloaded __setattr__ + object.__setattr__(self, '_box', box) + object.__setattr__(self, '_layout_done', False) + + def set_laid_out_box(self, new_box): + object.__setattr__(self, '_box', new_box) + object.__setattr__(self, '_layout_done', True) + + def translate(self, dx=0, dy=0): + if self._layout_done: + self._box.translate(dx, dy) + else: + # Descendants do not have a position yet. + self._box.position_x += dx + self._box.position_y += dy + + def copy(self): + new_placeholder = FloatPlaceholder(self._box.copy()) + object.__setattr__(new_placeholder, '_layout_done', self._layout_done) + return new_placeholder + + # Pretend to be the box itself + def __getattr__(self, name): + return getattr(self._box, name) + + def __setattr__(self, name, value): + setattr(self._box, name, value) + + +def float_layout(document, placeholder, containing_block, absolute_boxes): + """Set the width and position of floating ``box``.""" + box = placeholder._box + + cb = containing_block + cb_x = cb.content_box_x() + cb_y = cb.content_box_y() + cb_width = cb.width + cb_height = cb.height + + resolve_percentages(box, (cb_width, cb_height)) + resolve_position_percentages(box, (cb_width, cb_height)) + + if box.margin_left == 'auto': + box.margin_left = 0 + if box.margin_right == 'auto': + box.margin_right = 0 + + if box.width == 'auto': + if isinstance(box, boxes.BlockReplacedBox): + box.width = replaced_box_width(box, None) + box.height = replaced_box_height(box, None) + else: + box.width = shrink_to_fit(box, containing_block.width) + + # avoid a circular import + from .blocks import block_container_layout + new_box, _, _, _, _ = block_container_layout( + document, box, max_position_y=float('inf'), + skip_stack=None, device_size=None, page_is_empty=False, + absolute_boxes=absolute_boxes, adjoining_margins=None) + + for child_placeholder in absolute_boxes: + absolute_layout(document, child_placeholder, new_box) + + if new_box.style.float == 'left': + new_box.position_x = cb_x + else: + assert new_box.style.float == 'right' + new_box.position_x = cb_x - new_box.margin_width() + + placeholder.set_laid_out_box(new_box) From 6e56816727b34ed63ef37e47cdc89de5c82a224e Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Wed, 30 May 2012 00:50:17 +0200 Subject: [PATCH 03/79] Use utf-8 as encoding for w3 tests --- weasyprint/tests/w3_test_suite/web.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/weasyprint/tests/w3_test_suite/web.py b/weasyprint/tests/w3_test_suite/web.py index e8ed5663..21bee3f8 100644 --- a/weasyprint/tests/w3_test_suite/web.py +++ b/weasyprint/tests/w3_test_suite/web.py @@ -150,8 +150,10 @@ def run(suite_directory): @app.route('/render/.png') def render(test_id): - png = HTML(safe_join(suite_directory, test_id + '.htm')).write_png( - stylesheets=[page_size_stylesheet]) + # TODO: handle encodings + png = HTML( + safe_join(suite_directory, test_id + '.htm'), + encoding='utf-8').write_png(stylesheets=[page_size_stylesheet]) return png, 200, {'Content-Type': 'image/png'} From 6449ee4cbfbcc069323df4137a7618e04e99b4de Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Wed, 30 May 2012 03:27:08 +0200 Subject: [PATCH 04/79] Absolute doesn't work yet, but much better than before --- weasyprint/document.py | 1 + weasyprint/layout/blocks.py | 8 ++-- weasyprint/layout/float.py | 82 ++++++++++++++++++++++++++++++++---- weasyprint/layout/inlines.py | 14 ++++-- weasyprint/layout/pages.py | 1 + 5 files changed, 91 insertions(+), 15 deletions(-) diff --git a/weasyprint/document.py b/weasyprint/document.py index 08c308c8..2529851b 100644 --- a/weasyprint/document.py +++ b/weasyprint/document.py @@ -24,6 +24,7 @@ class Document(object): def __init__(self, backend, dom, user_stylesheets, user_agent_stylesheets): self.backend = backend self.fixed_boxes = [] + self.excluded_shapes = [] self.surface = backend.get_dummy_surface() self.dom = dom #: lxml HtmlElement object self.user_stylesheets = user_stylesheets diff --git a/weasyprint/layout/blocks.py b/weasyprint/layout/blocks.py index 78b2de7a..a6f04abc 100644 --- a/weasyprint/layout/blocks.py +++ b/weasyprint/layout/blocks.py @@ -241,8 +241,8 @@ def block_container_layout(document, box, max_position_y, skip_stack, child.position_y = position_y if not child.is_in_normal_flow(): + child.position_y += collapse_margin(adjoining_margins) if child.style.position in ('absolute', 'fixed'): - child.position_y += collapse_margin(adjoining_margins) placeholder = AbsolutePlaceholder(child) if child.style.position == 'absolute': absolute_boxes.append(placeholder) @@ -430,12 +430,12 @@ def block_container_layout(document, box, max_position_y, skip_stack, for absolute_box in absolute_boxes: absolute_layout(document, absolute_box, new_box) - for float_box in float_children: - float_layout(document, float_box, new_box, absolute_boxes) - for child in new_box.children: relative_positioning(child, (new_box.width, new_box.height)) + for float_box in float_children: + float_layout(document, float_box, new_box, absolute_boxes) + return new_box, resume_at, next_page, adjoining_margins, collapsing_through diff --git a/weasyprint/layout/float.py b/weasyprint/layout/float.py index cb5c9dd9..4ba9dbdf 100644 --- a/weasyprint/layout/float.py +++ b/weasyprint/layout/float.py @@ -11,7 +11,6 @@ from __future__ import division, unicode_literals from .absolute import absolute_layout -from .inlines import replaced_box_width, replaced_box_height from .percentages import resolve_percentages, resolve_position_percentages from .preferred import shrink_to_fit from ..formatting_structure import boxes @@ -54,8 +53,6 @@ def float_layout(document, placeholder, containing_block, absolute_boxes): box = placeholder._box cb = containing_block - cb_x = cb.content_box_x() - cb_y = cb.content_box_y() cb_width = cb.width cb_height = cb.height @@ -67,6 +64,9 @@ def float_layout(document, placeholder, containing_block, absolute_boxes): if box.margin_right == 'auto': box.margin_right = 0 + # avoid a circular import + from .inlines import replaced_box_width, replaced_box_height + if box.width == 'auto': if isinstance(box, boxes.BlockReplacedBox): box.width = replaced_box_width(box, None) @@ -76,6 +76,7 @@ def float_layout(document, placeholder, containing_block, absolute_boxes): # avoid a circular import from .blocks import block_container_layout + new_box, _, _, _, _ = block_container_layout( document, box, max_position_y=float('inf'), skip_stack=None, device_size=None, page_is_empty=False, @@ -84,10 +85,75 @@ def float_layout(document, placeholder, containing_block, absolute_boxes): for child_placeholder in absolute_boxes: absolute_layout(document, child_placeholder, new_box) - if new_box.style.float == 'left': - new_box.position_x = cb_x - else: - assert new_box.style.float == 'right' - new_box.position_x = cb_x - new_box.margin_width() + find_float_position(document, new_box, containing_block) + + document.excluded_shapes.append(new_box) placeholder.set_laid_out_box(new_box) + + +def find_float_position(document, box, containing_block): + """Get the right position of the float ``box``.""" + # See http://www.w3.org/TR/CSS2/visuren.html#dis-pos-flo + + position_x, position_y = box.position_x, box.position_y + + # Point 4 is already handled as box.position_y is set according to the + # containing box top position, with collapsing margins handled + # TODO: are the collapsing margins *really* handled? + + # Handle clear + for excluded_shape in document.excluded_shapes: + x, y, w, h = ( + excluded_shape.position_x, excluded_shape.position_y, + excluded_shape.margin_width(), excluded_shape.margin_height()) + if box.style.clear in (excluded_shape.style.float, 'both'): + position_y = max(excluded_shape.position_y, y + h) + + # Points 5 and 6, box.position_y is set to the highest position_y possible + if document.excluded_shapes: + position_y = max(position_y, document.excluded_shapes[-1].position_y) + + # Points 1 and 2 + while True: + left_bounds = [ + shape.position_x + shape.margin_width() + for shape in document.excluded_shapes + if shape.style.float == 'left' + and (shape.position_y <= position_y < + shape.position_y + shape.margin_height())] + right_bounds = [ + shape.position_x + for shape in document.excluded_shapes + if shape.style.float == 'right' + and (shape.position_y <= position_y < + shape.position_y + shape.margin_height())] + max_left_bound = containing_block.content_box_x() + max_right_bound = \ + containing_block.content_box_x() + containing_block.width + if left_bounds or right_bounds: + if left_bounds: + max_left_bound = max(left_bounds) + if right_bounds: + max_right_bound = min(right_bounds) + # Points 3, 7 and 8 + if box.margin_width() > max_right_bound - max_left_bound: + new_positon_y = min( + shape.position_y + shape.margin_height() + for shape in document.excluded_shapes + if (shape.position_y <= position_y < + shape.position_y + shape.margin_height())) + if new_positon_y > position_y: + position_y = new_positon_y + continue + break + + # Point 9 + # position_y is set now, let's define position_x + if box.style.float == 'left': + position_x = max_left_bound + else: + assert box.style.float == 'right' + position_x = max_right_bound - box.margin_width() + + box.translate(position_x - box.position_x, position_y - box.position_y) diff --git a/weasyprint/layout/inlines.py b/weasyprint/layout/inlines.py index 95f980ec..5dfadce0 100644 --- a/weasyprint/layout/inlines.py +++ b/weasyprint/layout/inlines.py @@ -16,6 +16,7 @@ import functools import cairo from .absolute import absolute_layout, AbsolutePlaceholder +from .float import float_layout, FloatPlaceholder from .markers import image_marker_layout from .percentages import resolve_percentages, resolve_one_percentage from .preferred import shrink_to_fit @@ -527,6 +528,7 @@ def split_inline_box(document, box, position_x, max_x, skip_stack, content_box_left = position_x children = [] + float_children = [] preserved_line_break = False is_start = skip_stack is None @@ -550,9 +552,10 @@ def split_inline_box(document, box, position_x, max_x, skip_stack, children.append(placeholder) else: document.fixed_boxes.append(placeholder) - else: - # TODO: Floats - children.append(child) + elif child.style.float in ('left', 'right'): + placeholder = FloatPlaceholder(child) + float_children.append(placeholder) + children.append(placeholder) continue new_child, resume_at, preserved = split_inline_level( @@ -625,6 +628,11 @@ def split_inline_box(document, box, position_x, max_x, skip_stack, if new_box.style.position == 'relative': for absolute_box in absolute_boxes: absolute_layout(document, absolute_box, new_box) + + for float_box in float_children: + print(float_box, new_box) + float_layout(document, float_box, new_box, absolute_boxes) + return new_box, resume_at, preserved_line_break diff --git a/weasyprint/layout/pages.py b/weasyprint/layout/pages.py index 7a7eb471..255dd467 100644 --- a/weasyprint/layout/pages.py +++ b/weasyprint/layout/pages.py @@ -475,6 +475,7 @@ def make_page(document, root_box, page_type, resume_at): or ``None`` for the first page. """ + document.excluded_shapes = [] page = make_empty_page(document, root_box, page_type) device_size = page.style.size From d4ccf3db636d0dd32464df1dcf708ca4b9edfa1f Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Wed, 30 May 2012 03:36:37 +0200 Subject: [PATCH 05/79] Remove monkey patches about float --- weasyprint/tests/test_boxes.py | 20 +++++--------------- weasyprint/tests/test_layout.py | 16 +++++++--------- 2 files changed, 12 insertions(+), 24 deletions(-) diff --git a/weasyprint/tests/test_boxes.py b/weasyprint/tests/test_boxes.py index a16c140c..c4a6f025 100644 --- a/weasyprint/tests/test_boxes.py +++ b/weasyprint/tests/test_boxes.py @@ -79,15 +79,6 @@ def to_lists(box_tree): return serialize(unwrap_html_body(box_tree)) -def validate_float( - real_non_shorthand, base_url, name, values, required=False): - """Fake validator for ``float``.""" - value = values[0].value - if name == 'float' and value == 'left': - return [(name, value)] - return real_non_shorthand(base_url, name, values, required) - - @contextlib.contextmanager def monkeypatch_validation(replacement): """Create a context manager patching the validation mechanism. @@ -112,12 +103,11 @@ def monkeypatch_validation(replacement): def parse(html_content): """Parse some HTML, apply stylesheets and transform to boxes.""" - with monkeypatch_validation(validate_float): - document = TestPNGDocument(html_content, - # Dummy filename, but in the right directory. - base_url=resource_filename('')) - box, = build.dom_to_box(document, document.dom) - return box + document = TestPNGDocument(html_content, + # Dummy filename, but in the right directory. + base_url=resource_filename('')) + box, = build.dom_to_box(document, document.dom) + return box def parse_all(html_content, base_url=resource_filename('')): diff --git a/weasyprint/tests/test_layout.py b/weasyprint/tests/test_layout.py index 79a8c5fd..44be26fc 100644 --- a/weasyprint/tests/test_layout.py +++ b/weasyprint/tests/test_layout.py @@ -15,7 +15,7 @@ from __future__ import division, unicode_literals from .testing_utils import ( TestPNGDocument, resource_filename, FONTS, assert_no_logs, capture_logs) -from .test_boxes import monkeypatch_validation, validate_float +from .test_boxes import monkeypatch_validation from ..formatting_structure import boxes from ..layout.inlines import split_inline_box from ..layout.percentages import resolve_percentages @@ -39,14 +39,12 @@ def parse_without_layout(html_content): def parse(html_content, return_document=False): """Parse some HTML, apply stylesheets, transform to boxes and lay out.""" - # TODO: remove this patching when floats are validated - with monkeypatch_validation(validate_float): - document = TestPNGDocument(html_content, - base_url=resource_filename('')) - if return_document: - return document - else: - return document.pages + document = TestPNGDocument(html_content, + base_url=resource_filename('')) + if return_document: + return document + else: + return document.pages @assert_no_logs From 6a6757a52c2b561186cb253a1cd21679ee63d470 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Wed, 30 May 2012 04:19:24 +0200 Subject: [PATCH 06/79] That's much better --- weasyprint/layout/float.py | 24 +++++++++++++----------- weasyprint/layout/inlines.py | 3 +-- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/weasyprint/layout/float.py b/weasyprint/layout/float.py index 4ba9dbdf..edfd82df 100644 --- a/weasyprint/layout/float.py +++ b/weasyprint/layout/float.py @@ -19,6 +19,7 @@ from ..formatting_structure import boxes class FloatPlaceholder(object): """Left where an float box was taken out of the flow.""" def __init__(self, box): + assert not isinstance(box, FloatPlaceholder) # Work around the overloaded __setattr__ object.__setattr__(self, '_box', box) object.__setattr__(self, '_layout_done', False) @@ -69,27 +70,28 @@ def float_layout(document, placeholder, containing_block, absolute_boxes): if box.width == 'auto': if isinstance(box, boxes.BlockReplacedBox): - box.width = replaced_box_width(box, None) - box.height = replaced_box_height(box, None) + replaced_box_width(box, None) + replaced_box_height(box, None) else: box.width = shrink_to_fit(box, containing_block.width) # avoid a circular import from .blocks import block_container_layout - new_box, _, _, _, _ = block_container_layout( - document, box, max_position_y=float('inf'), - skip_stack=None, device_size=None, page_is_empty=False, - absolute_boxes=absolute_boxes, adjoining_margins=None) + if isinstance(box, boxes.BlockBox): + box, _, _, _, _ = block_container_layout( + document, box, max_position_y=float('inf'), + skip_stack=None, device_size=None, page_is_empty=False, + absolute_boxes=absolute_boxes, adjoining_margins=None) for child_placeholder in absolute_boxes: - absolute_layout(document, child_placeholder, new_box) + absolute_layout(document, child_placeholder, box) - find_float_position(document, new_box, containing_block) + find_float_position(document, box, containing_block) - document.excluded_shapes.append(new_box) + document.excluded_shapes.append(box) - placeholder.set_laid_out_box(new_box) + placeholder.set_laid_out_box(box) def find_float_position(document, box, containing_block): @@ -108,7 +110,7 @@ def find_float_position(document, box, containing_block): excluded_shape.position_x, excluded_shape.position_y, excluded_shape.margin_width(), excluded_shape.margin_height()) if box.style.clear in (excluded_shape.style.float, 'both'): - position_y = max(excluded_shape.position_y, y + h) + position_y = max(position_y, y + h) # Points 5 and 6, box.position_y is set to the highest position_y possible if document.excluded_shapes: diff --git a/weasyprint/layout/inlines.py b/weasyprint/layout/inlines.py index 5dfadce0..0b4ee264 100644 --- a/weasyprint/layout/inlines.py +++ b/weasyprint/layout/inlines.py @@ -543,8 +543,8 @@ def split_inline_box(document, box, position_x, max_x, skip_stack, for index, child in box.enumerate_skip(skip): child.position_y = box.position_y if not child.is_in_normal_flow(): + child.position_x = position_x if child.style.position in ('absolute', 'fixed'): - child.position_x = position_x placeholder = AbsolutePlaceholder(child) line_placeholders.append(placeholder) if child.style.position == 'absolute': @@ -630,7 +630,6 @@ def split_inline_box(document, box, position_x, max_x, skip_stack, absolute_layout(document, absolute_box, new_box) for float_box in float_children: - print(float_box, new_box) float_layout(document, float_box, new_box, absolute_boxes) return new_box, resume_at, preserved_line_break From a9512c12b6d30c724cf80802130dc76325d35997 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Wed, 30 May 2012 19:05:35 +0200 Subject: [PATCH 07/79] Fix the available width for shrink-to-fit on floats. --- weasyprint/layout/float.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/weasyprint/layout/float.py b/weasyprint/layout/float.py index edfd82df..b3d86ad9 100644 --- a/weasyprint/layout/float.py +++ b/weasyprint/layout/float.py @@ -73,7 +73,11 @@ def float_layout(document, placeholder, containing_block, absolute_boxes): replaced_box_width(box, None) replaced_box_height(box, None) else: - box.width = shrink_to_fit(box, containing_block.width) + available_width = containing_block.width - ( + box.margin_left + box.margin_right + + box.border_left_width + box.border_right_width + + box.padding_left + box.padding_right) + box.width = shrink_to_fit(box, available_width) # avoid a circular import from .blocks import block_container_layout From a225f194e80d31d97b66c3cba62d8703e64052d6 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Wed, 30 May 2012 19:16:36 +0200 Subject: [PATCH 08/79] Handle the clearance for non-float block-level boxes --- weasyprint/layout/blocks.py | 15 +++++++++------ weasyprint/layout/float.py | 36 ++++++++++++++++++++++-------------- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/weasyprint/layout/blocks.py b/weasyprint/layout/blocks.py index a6f04abc..7a1fb81e 100644 --- a/weasyprint/layout/blocks.py +++ b/weasyprint/layout/blocks.py @@ -13,7 +13,7 @@ from __future__ import division, unicode_literals from .absolute import absolute_layout, AbsolutePlaceholder -from .float import float_layout, FloatPlaceholder +from .float import float_layout, get_clearance, FloatPlaceholder from .inlines import (iter_line_boxes, replaced_box_width, replaced_box_height, handle_min_max_width, min_max_replaced_height, min_max_auto_replaced) @@ -33,6 +33,11 @@ def block_level_layout(document, box, max_position_y, skip_stack, content box of the current page area. """ + clearance = get_clearance(document, box) + if clearance: + box.position_y += clearance + adjoining_margins = [] + if isinstance(box, boxes.TableBox): return table_layout( document, box, max_position_y, skip_stack, containing_block, @@ -61,6 +66,7 @@ def block_box_layout(document, box, max_position_y, skip_stack, table_wrapper_width(box, (containing_block.width, containing_block.height), absolute_boxes) block_level_width(box, containing_block) + new_box, resume_at, next_page, adjoining_margins, collapsing_through = \ block_container_layout( document, box, max_position_y, skip_stack, @@ -226,7 +232,6 @@ def block_container_layout(document, box, max_position_y, skip_stack, absolute_boxes = [] new_children = [] - float_children = [] next_page = 'any' is_start = skip_stack is None @@ -251,7 +256,8 @@ def block_container_layout(document, box, max_position_y, skip_stack, document.fixed_boxes.append(placeholder) elif child.style.float in ('left', 'right'): placeholder = FloatPlaceholder(child) - float_children.append(placeholder) + float_layout(document, placeholder, box, absolute_boxes) + document.excluded_shapes.append(placeholder) new_children.append(placeholder) continue @@ -433,9 +439,6 @@ def block_container_layout(document, box, max_position_y, skip_stack, for child in new_box.children: relative_positioning(child, (new_box.width, new_box.height)) - for float_box in float_children: - float_layout(document, float_box, new_box, absolute_boxes) - return new_box, resume_at, next_page, adjoining_margins, collapsing_through diff --git a/weasyprint/layout/float.py b/weasyprint/layout/float.py index b3d86ad9..b8f3d010 100644 --- a/weasyprint/layout/float.py +++ b/weasyprint/layout/float.py @@ -65,6 +65,10 @@ def float_layout(document, placeholder, containing_block, absolute_boxes): if box.margin_right == 'auto': box.margin_right = 0 + clearance = get_clearance(document, box) + if clearance: + box.position_y += clearance + # avoid a circular import from .inlines import replaced_box_width, replaced_box_height @@ -87,13 +91,15 @@ def float_layout(document, placeholder, containing_block, absolute_boxes): document, box, max_position_y=float('inf'), skip_stack=None, device_size=None, page_is_empty=False, absolute_boxes=absolute_boxes, adjoining_margins=None) + else: + assert isinstance(box, boxes.BlockReplacedBox) for child_placeholder in absolute_boxes: absolute_layout(document, child_placeholder, box) find_float_position(document, box, containing_block) - document.excluded_shapes.append(box) + document.excluded_shapes.append(placeholder) placeholder.set_laid_out_box(box) @@ -103,34 +109,27 @@ def find_float_position(document, box, containing_block): # See http://www.w3.org/TR/CSS2/visuren.html#dis-pos-flo position_x, position_y = box.position_x, box.position_y + excluded_shapes = document.excluded_shapes # Point 4 is already handled as box.position_y is set according to the # containing box top position, with collapsing margins handled # TODO: are the collapsing margins *really* handled? - # Handle clear - for excluded_shape in document.excluded_shapes: - x, y, w, h = ( - excluded_shape.position_x, excluded_shape.position_y, - excluded_shape.margin_width(), excluded_shape.margin_height()) - if box.style.clear in (excluded_shape.style.float, 'both'): - position_y = max(position_y, y + h) - # Points 5 and 6, box.position_y is set to the highest position_y possible - if document.excluded_shapes: - position_y = max(position_y, document.excluded_shapes[-1].position_y) + if excluded_shapes: + position_y = max(position_y, excluded_shapes[-1].position_y) # Points 1 and 2 while True: left_bounds = [ shape.position_x + shape.margin_width() - for shape in document.excluded_shapes + for shape in excluded_shapes if shape.style.float == 'left' and (shape.position_y <= position_y < shape.position_y + shape.margin_height())] right_bounds = [ shape.position_x - for shape in document.excluded_shapes + for shape in excluded_shapes if shape.style.float == 'right' and (shape.position_y <= position_y < shape.position_y + shape.margin_height())] @@ -146,7 +145,7 @@ def find_float_position(document, box, containing_block): if box.margin_width() > max_right_bound - max_left_bound: new_positon_y = min( shape.position_y + shape.margin_height() - for shape in document.excluded_shapes + for shape in excluded_shapes if (shape.position_y <= position_y < shape.position_y + shape.margin_height())) if new_positon_y > position_y: @@ -163,3 +162,12 @@ def find_float_position(document, box, containing_block): position_x = max_right_bound - box.margin_width() box.translate(position_x - box.position_x, position_y - box.position_y) + + +def get_clearance(document, box): + clearance = 0 + for excluded_shape in document.excluded_shapes: + if box.style.clear in (excluded_shape.style.float, 'both'): + y, h = excluded_shape.position_y, excluded_shape.margin_height() + clearance = max(clearance, y + h - box.position_y) + return clearance From ea7993e4eb241cfd45071a895c1e7f92a49a64c4 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Wed, 30 May 2012 23:40:54 +0200 Subject: [PATCH 09/79] Fix a lot of things for float boxes --- weasyprint/layout/blocks.py | 2 +- weasyprint/layout/float.py | 69 ++++++++++++++++++++-------------- weasyprint/layout/inlines.py | 42 ++++++++++----------- weasyprint/layout/preferred.py | 17 ++++++--- 4 files changed, 73 insertions(+), 57 deletions(-) diff --git a/weasyprint/layout/blocks.py b/weasyprint/layout/blocks.py index 7a1fb81e..a8331f80 100644 --- a/weasyprint/layout/blocks.py +++ b/weasyprint/layout/blocks.py @@ -274,7 +274,7 @@ def block_container_layout(document, box, max_position_y, skip_stack, new_lines = [] is_page_break = False for line, resume_at in lines_iterator: - new_position_y = position_y + line.height + new_position_y = line.position_y + line.height # Allow overflow if the first line of the page is higher # than the page itself so that we put *something* on this # page and can advance in the document. diff --git a/weasyprint/layout/float.py b/weasyprint/layout/float.py index b8f3d010..5e720e15 100644 --- a/weasyprint/layout/float.py +++ b/weasyprint/layout/float.py @@ -11,6 +11,7 @@ from __future__ import division, unicode_literals from .absolute import absolute_layout +from .markers import list_marker_layout from .percentages import resolve_percentages, resolve_position_percentages from .preferred import shrink_to_fit from ..formatting_structure import boxes @@ -77,20 +78,20 @@ def float_layout(document, placeholder, containing_block, absolute_boxes): replaced_box_width(box, None) replaced_box_height(box, None) else: - available_width = containing_block.width - ( - box.margin_left + box.margin_right + - box.border_left_width + box.border_right_width + - box.padding_left + box.padding_right) - box.width = shrink_to_fit(box, available_width) + box.width = shrink_to_fit(box, containing_block.width) # avoid a circular import from .blocks import block_container_layout if isinstance(box, boxes.BlockBox): + excluded_shapes = document.excluded_shapes + document.excluded_shapes = [] box, _, _, _, _ = block_container_layout( document, box, max_position_y=float('inf'), skip_stack=None, device_size=None, page_is_empty=False, absolute_boxes=absolute_boxes, adjoining_margins=None) + list_marker_layout(document, box) + document.excluded_shapes = excluded_shapes else: assert isinstance(box, boxes.BlockReplacedBox) @@ -108,18 +109,44 @@ def find_float_position(document, box, containing_block): """Get the right position of the float ``box``.""" # See http://www.w3.org/TR/CSS2/visuren.html#dis-pos-flo - position_x, position_y = box.position_x, box.position_y - excluded_shapes = document.excluded_shapes - # Point 4 is already handled as box.position_y is set according to the # containing box top position, with collapsing margins handled # TODO: are the collapsing margins *really* handled? # Points 5 and 6, box.position_y is set to the highest position_y possible - if excluded_shapes: - position_y = max(position_y, excluded_shapes[-1].position_y) + if document.excluded_shapes: + highest_y = document.excluded_shapes[-1].position_y + if box.position_y < highest_y: + box.translate(0, highest_y - box.position_y) # Points 1 and 2 + position_x, position_y, available_width = avoid_collisions( + document, box, containing_block) + + # Point 9 + # position_y is set now, let's define position_x + # for float: left elements, it's already done! + if box.style.float == 'right': + position_x = position_x + available_width - box.margin_width() + + box.translate(position_x - box.position_x, position_y - box.position_y) + + +def get_clearance(document, box): + clearance = 0 + for excluded_shape in document.excluded_shapes: + if box.style.clear in (excluded_shape.style.float, 'both'): + y, h = excluded_shape.position_y, excluded_shape.margin_height() + clearance = max(clearance, y + h - box.position_y) + return clearance + + +def avoid_collisions(document, box, containing_block, outer=True): + excluded_shapes = document.excluded_shapes + position_y = box.position_y + + box_width = box.margin_width() if outer else box.width + while True: left_bounds = [ shape.position_x + shape.margin_width() @@ -142,7 +169,7 @@ def find_float_position(document, box, containing_block): if right_bounds: max_right_bound = min(right_bounds) # Points 3, 7 and 8 - if box.margin_width() > max_right_bound - max_left_bound: + if box_width > max_right_bound - max_left_bound: new_positon_y = min( shape.position_y + shape.margin_height() for shape in excluded_shapes @@ -153,21 +180,7 @@ def find_float_position(document, box, containing_block): continue break - # Point 9 - # position_y is set now, let's define position_x - if box.style.float == 'left': - position_x = max_left_bound - else: - assert box.style.float == 'right' - position_x = max_right_bound - box.margin_width() + position_x = max_left_bound + available_width = max_right_bound - max_left_bound - box.translate(position_x - box.position_x, position_y - box.position_y) - - -def get_clearance(document, box): - clearance = 0 - for excluded_shape in document.excluded_shapes: - if box.style.clear in (excluded_shape.style.float, 'both'): - y, h = excluded_shape.position_y, excluded_shape.margin_height() - clearance = max(clearance, y + h - box.position_y) - return clearance + return position_x, position_y, available_width diff --git a/weasyprint/layout/inlines.py b/weasyprint/layout/inlines.py index 0b4ee264..848f66bb 100644 --- a/weasyprint/layout/inlines.py +++ b/weasyprint/layout/inlines.py @@ -16,10 +16,10 @@ import functools import cairo from .absolute import absolute_layout, AbsolutePlaceholder -from .float import float_layout, FloatPlaceholder +from .float import avoid_collisions from .markers import image_marker_layout from .percentages import resolve_percentages, resolve_one_percentage -from .preferred import shrink_to_fit +from .preferred import shrink_to_fit, inline_preferred_minimum_width from .tables import find_in_flow_baseline, table_wrapper_width from ..text import TextFragment from ..formatting_structure import boxes @@ -53,15 +53,20 @@ def iter_line_boxes(document, box, position_y, skip_stack, if resume_at is None: return skip_stack = resume_at - position_y += line.height + position_y = line.position_y + line.height def get_next_linebox(document, linebox, position_y, skip_stack, containing_block, device_size, absolute_boxes): """Return ``(line, resume_at)``.""" - position_x = linebox.position_x linebox.position_y = position_y - max_x = position_x + containing_block.width + linebox.width = inline_preferred_minimum_width( + linebox, skip_stack=skip_stack) + position_x, position_y, available_width = avoid_collisions( + document, linebox, containing_block, outer=False) + linebox.position_x = position_x + linebox.position_y = position_y + max_x = position_x + available_width if skip_stack is None: # text-indent only at the start of the first line # Other percentages (margins, width, ...) do not apply. @@ -83,7 +88,7 @@ def get_next_linebox(document, linebox, position_y, skip_stack, bottom, top = inline_box_verticality(line, baseline_y=0) last = resume_at is None or preserved_line_break - offset_x = text_align(document, line, containing_block, last) + offset_x = text_align(document, line, available_width, last) if bottom is None: # No children at all line.position_y = position_y @@ -528,23 +533,21 @@ def split_inline_box(document, box, position_x, max_x, skip_stack, content_box_left = position_x children = [] - float_children = [] preserved_line_break = False + if box.style.position == 'relative': + absolute_boxes = [] + is_start = skip_stack is None if is_start: skip = 0 else: skip, skip_stack = skip_stack - - if box.style.position == 'relative': - absolute_boxes = [] - for index, child in box.enumerate_skip(skip): child.position_y = box.position_y if not child.is_in_normal_flow(): - child.position_x = position_x if child.style.position in ('absolute', 'fixed'): + child.position_x = position_x placeholder = AbsolutePlaceholder(child) line_placeholders.append(placeholder) if child.style.position == 'absolute': @@ -552,10 +555,9 @@ def split_inline_box(document, box, position_x, max_x, skip_stack, children.append(placeholder) else: document.fixed_boxes.append(placeholder) - elif child.style.float in ('left', 'right'): - placeholder = FloatPlaceholder(child) - float_children.append(placeholder) - children.append(placeholder) + else: + # TODO: Floats + children.append(child) continue new_child, resume_at, preserved = split_inline_level( @@ -628,10 +630,6 @@ def split_inline_box(document, box, position_x, max_x, skip_stack, if new_box.style.position == 'relative': for absolute_box in absolute_boxes: absolute_layout(document, absolute_box, new_box) - - for float_box in float_children: - float_layout(document, float_box, new_box, absolute_boxes) - return new_box, resume_at, preserved_line_break @@ -785,7 +783,7 @@ def inline_box_verticality(box, baseline_y): return max_y, min_y -def text_align(document, line, containing_block, last): +def text_align(document, line, available_width, last): """Return how much the line should be moved horizontally according to the `text-align` property. @@ -800,7 +798,7 @@ def text_align(document, line, containing_block, last): align = 'right' if line.style.direction == 'rtl' else 'left' if align == 'left': return 0 - offset = containing_block.width - line.width + offset = available_width - line.width if align == 'justify': justify_line(document, line, offset) return 0 diff --git a/weasyprint/layout/preferred.py b/weasyprint/layout/preferred.py index a81c4a95..830bfec1 100644 --- a/weasyprint/layout/preferred.py +++ b/weasyprint/layout/preferred.py @@ -139,10 +139,14 @@ def block_preferred_width(box, outer=True): return _block_preferred_width(box, preferred_width, outer) -def inline_preferred_minimum_width(box, outer=True): +def inline_preferred_minimum_width(box, outer=True, skip_stack=None): """Return the preferred minimum width for an ``InlineBox``.""" widest_line = 0 - for child in box.children: + if skip_stack is None: + skip = 0 + else: + skip, skip_stack = skip_stack + for index, child in box.enumerate_skip(skip): if not child.is_in_normal_flow(): continue # Skip @@ -153,10 +157,11 @@ def inline_preferred_minimum_width(box, outer=True): current_line = block_preferred_minimum_width(child) elif isinstance(child, boxes.InlineBox): # TODO: handle forced line breaks - current_line = inline_preferred_minimum_width(child) + current_line = inline_preferred_minimum_width(child, skip_stack) else: assert isinstance(child, boxes.TextBox) - current_line = max(text_lines_width(child, width=0)) + current_line = max( + text_lines_width(child, width=0, skip=skip)) widest_line = max(widest_line, current_line) return widest_line @@ -368,11 +373,11 @@ def table_preferred_width(box, outer=True): return adjust(box, outer, width) -def text_lines_width(box, width): +def text_lines_width(box, width, skip=None): """Return the list of line widths for a ``TextBox``.""" # TODO: find the real surface, to have correct hinting context = cairo.Context(cairo.PDFSurface(None, 1, 1)) - fragment = TextFragment(box.text, box.style, context, width=width) + fragment = TextFragment(box.text[skip:], box.style, context, width=width) return fragment.line_widths() From 3bb002e61ff9fecabce0086f3a6816b631637b00 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Thu, 31 May 2012 00:22:14 +0200 Subject: [PATCH 10/79] Correcly handle float elements in inline blocks --- weasyprint/layout/float.py | 7 +++++++ weasyprint/layout/inlines.py | 17 ++++++++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/weasyprint/layout/float.py b/weasyprint/layout/float.py index 5e720e15..8cc6b82c 100644 --- a/weasyprint/layout/float.py +++ b/weasyprint/layout/float.py @@ -160,9 +160,13 @@ def avoid_collisions(document, box, containing_block, outer=True): if shape.style.float == 'right' and (shape.position_y <= position_y < shape.position_y + shape.margin_height())] + + # Set the default maximum bounds max_left_bound = containing_block.content_box_x() max_right_bound = \ containing_block.content_box_x() + containing_block.width + + # Set the real maximum bounds according to sibling float elements if left_bounds or right_bounds: if left_bounds: max_left_bound = max(left_bounds) @@ -170,14 +174,17 @@ def avoid_collisions(document, box, containing_block, outer=True): max_right_bound = min(right_bounds) # Points 3, 7 and 8 if box_width > max_right_bound - max_left_bound: + # The box does not fit here new_positon_y = min( shape.position_y + shape.margin_height() for shape in excluded_shapes if (shape.position_y <= position_y < shape.position_y + shape.margin_height())) if new_positon_y > position_y: + # We can find a solution with a higher position_y position_y = new_positon_y continue + # No solution, we must put the box here break position_x = max_left_bound diff --git a/weasyprint/layout/inlines.py b/weasyprint/layout/inlines.py index 848f66bb..fb6e6c2f 100644 --- a/weasyprint/layout/inlines.py +++ b/weasyprint/layout/inlines.py @@ -16,7 +16,7 @@ import functools import cairo from .absolute import absolute_layout, AbsolutePlaceholder -from .float import avoid_collisions +from .float import avoid_collisions, float_layout, FloatPlaceholder from .markers import image_marker_layout from .percentages import resolve_percentages, resolve_one_percentage from .preferred import shrink_to_fit, inline_preferred_minimum_width @@ -546,8 +546,8 @@ def split_inline_box(document, box, position_x, max_x, skip_stack, for index, child in box.enumerate_skip(skip): child.position_y = box.position_y if not child.is_in_normal_flow(): + child.position_x = position_x if child.style.position in ('absolute', 'fixed'): - child.position_x = position_x placeholder = AbsolutePlaceholder(child) line_placeholders.append(placeholder) if child.style.position == 'absolute': @@ -555,9 +555,16 @@ def split_inline_box(document, box, position_x, max_x, skip_stack, children.append(placeholder) else: document.fixed_boxes.append(placeholder) - else: - # TODO: Floats - children.append(child) + elif child.style.float in ('left', 'right'): + # Set a maximum line width to lay out the float element, it + # will be correctly set after that + if box.width == 'auto': + box.width = ( + containing_block.content_box_x() + + containing_block.width - position_x) + placeholder = FloatPlaceholder(child) + float_layout(document, placeholder, box, absolute_boxes) + children.append(placeholder) continue new_child, resume_at, preserved = split_inline_level( From bc27971ac6ae588403b691a5198f67aa02e51695 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Thu, 31 May 2012 01:14:15 +0200 Subject: [PATCH 11/79] Typo fixes --- weasyprint/text.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/weasyprint/text.py b/weasyprint/text.py index 28dced05..c01c4b1f 100644 --- a/weasyprint/text.py +++ b/weasyprint/text.py @@ -27,6 +27,7 @@ PANGO_STYLE = { 'oblique': Pango.Style.OBLIQUE, } + class TextFragment(object): """Text renderer using Pango. @@ -55,7 +56,7 @@ class TextFragment(object): ' ', ' ' % ( word_spacing + letter_spacing,)) markup = '%s' % ( - letter_spacing , markup) + letter_spacing, markup) attributes_list = Pango.parse_markup(markup, -1, '\x00')[1] self.layout.set_attributes(attributes_list) if width is not None: From 69c43e6d6ca01a8d012b818543b596d513ba7549 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Thu, 31 May 2012 02:05:35 +0200 Subject: [PATCH 12/79] Allow the preferred minimum width of inline elements to get only the first line --- weasyprint/layout/inlines.py | 2 +- weasyprint/layout/preferred.py | 23 +++++++++++++++++------ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/weasyprint/layout/inlines.py b/weasyprint/layout/inlines.py index fb6e6c2f..3aebf64d 100644 --- a/weasyprint/layout/inlines.py +++ b/weasyprint/layout/inlines.py @@ -61,7 +61,7 @@ def get_next_linebox(document, linebox, position_y, skip_stack, """Return ``(line, resume_at)``.""" linebox.position_y = position_y linebox.width = inline_preferred_minimum_width( - linebox, skip_stack=skip_stack) + linebox, skip_stack=skip_stack, first_line=True) position_x, position_y, available_width = avoid_collisions( document, linebox, containing_block, outer=False) linebox.position_x = position_x diff --git a/weasyprint/layout/preferred.py b/weasyprint/layout/preferred.py index 830bfec1..8b2ce8aa 100644 --- a/weasyprint/layout/preferred.py +++ b/weasyprint/layout/preferred.py @@ -139,8 +139,15 @@ def block_preferred_width(box, outer=True): return _block_preferred_width(box, preferred_width, outer) -def inline_preferred_minimum_width(box, outer=True, skip_stack=None): - """Return the preferred minimum width for an ``InlineBox``.""" +def inline_preferred_minimum_width(box, outer=True, skip_stack=None, + first_line=False): + """Return the preferred minimum width for an ``InlineBox``. + + The width is calculated from the lines from ``skip_stack``. If + ``first_line`` is ``True``, only the first line minimum width is + calculated. + + """ widest_line = 0 if skip_stack is None: skip = 0 @@ -157,11 +164,15 @@ def inline_preferred_minimum_width(box, outer=True, skip_stack=None): current_line = block_preferred_minimum_width(child) elif isinstance(child, boxes.InlineBox): # TODO: handle forced line breaks - current_line = inline_preferred_minimum_width(child, skip_stack) + current_line = inline_preferred_minimum_width( + child, skip_stack, first_line) else: assert isinstance(child, boxes.TextBox) - current_line = max( - text_lines_width(child, width=0, skip=skip)) + widths = text_lines_width(child, width=0, skip=skip) + if first_line: + return widths.next() + else: + current_line = max(widths) widest_line = max(widest_line, current_line) return widest_line @@ -377,7 +388,7 @@ def text_lines_width(box, width, skip=None): """Return the list of line widths for a ``TextBox``.""" # TODO: find the real surface, to have correct hinting context = cairo.Context(cairo.PDFSurface(None, 1, 1)) - fragment = TextFragment(box.text[skip:], box.style, context, width=width) + fragment = TextFragment(box.text[skip:], box.style, context, width) return fragment.line_widths() From d747fafe7f4d699467bea10dfd7a2c8d7d2e8dae Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Thu, 31 May 2012 02:08:33 +0200 Subject: [PATCH 13/79] Use named parameters when needed in inline_preferred_minimun_width --- weasyprint/layout/preferred.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weasyprint/layout/preferred.py b/weasyprint/layout/preferred.py index 8b2ce8aa..af57195f 100644 --- a/weasyprint/layout/preferred.py +++ b/weasyprint/layout/preferred.py @@ -165,7 +165,7 @@ def inline_preferred_minimum_width(box, outer=True, skip_stack=None, elif isinstance(child, boxes.InlineBox): # TODO: handle forced line breaks current_line = inline_preferred_minimum_width( - child, skip_stack, first_line) + child, skip_stack=skip_stack, first_line=first_line) else: assert isinstance(child, boxes.TextBox) widths = text_lines_width(child, width=0, skip=skip) From ec3e1943d599333718701af8ed412125511d4855 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Thu, 31 May 2012 02:45:16 +0200 Subject: [PATCH 14/79] Remove the useless float placeholder --- weasyprint/layout/blocks.py | 9 ++++---- weasyprint/layout/float.py | 45 +++++------------------------------- weasyprint/layout/inlines.py | 7 +++--- weasyprint/stacking.py | 3 +-- 4 files changed, 14 insertions(+), 50 deletions(-) diff --git a/weasyprint/layout/blocks.py b/weasyprint/layout/blocks.py index a8331f80..19469ee4 100644 --- a/weasyprint/layout/blocks.py +++ b/weasyprint/layout/blocks.py @@ -13,7 +13,7 @@ from __future__ import division, unicode_literals from .absolute import absolute_layout, AbsolutePlaceholder -from .float import float_layout, get_clearance, FloatPlaceholder +from .float import float_layout, get_clearance from .inlines import (iter_line_boxes, replaced_box_width, replaced_box_height, handle_min_max_width, min_max_replaced_height, min_max_auto_replaced) @@ -255,10 +255,9 @@ def block_container_layout(document, box, max_position_y, skip_stack, else: document.fixed_boxes.append(placeholder) elif child.style.float in ('left', 'right'): - placeholder = FloatPlaceholder(child) - float_layout(document, placeholder, box, absolute_boxes) - document.excluded_shapes.append(placeholder) - new_children.append(placeholder) + child = float_layout(document, child, box, absolute_boxes) + document.excluded_shapes.append(child) + new_children.append(child) continue if isinstance(child, boxes.LineBox): diff --git a/weasyprint/layout/float.py b/weasyprint/layout/float.py index 8cc6b82c..addc925a 100644 --- a/weasyprint/layout/float.py +++ b/weasyprint/layout/float.py @@ -17,43 +17,8 @@ from .preferred import shrink_to_fit from ..formatting_structure import boxes -class FloatPlaceholder(object): - """Left where an float box was taken out of the flow.""" - def __init__(self, box): - assert not isinstance(box, FloatPlaceholder) - # Work around the overloaded __setattr__ - object.__setattr__(self, '_box', box) - object.__setattr__(self, '_layout_done', False) - - def set_laid_out_box(self, new_box): - object.__setattr__(self, '_box', new_box) - object.__setattr__(self, '_layout_done', True) - - def translate(self, dx=0, dy=0): - if self._layout_done: - self._box.translate(dx, dy) - else: - # Descendants do not have a position yet. - self._box.position_x += dx - self._box.position_y += dy - - def copy(self): - new_placeholder = FloatPlaceholder(self._box.copy()) - object.__setattr__(new_placeholder, '_layout_done', self._layout_done) - return new_placeholder - - # Pretend to be the box itself - def __getattr__(self, name): - return getattr(self._box, name) - - def __setattr__(self, name, value): - setattr(self._box, name, value) - - -def float_layout(document, placeholder, containing_block, absolute_boxes): +def float_layout(document, box, containing_block, absolute_boxes): """Set the width and position of floating ``box``.""" - box = placeholder._box - cb = containing_block cb_width = cb.width cb_height = cb.height @@ -98,11 +63,11 @@ def float_layout(document, placeholder, containing_block, absolute_boxes): for child_placeholder in absolute_boxes: absolute_layout(document, child_placeholder, box) - find_float_position(document, box, containing_block) + box = find_float_position(document, box, containing_block) - document.excluded_shapes.append(placeholder) + document.excluded_shapes.append(box) - placeholder.set_laid_out_box(box) + return box def find_float_position(document, box, containing_block): @@ -131,6 +96,8 @@ def find_float_position(document, box, containing_block): box.translate(position_x - box.position_x, position_y - box.position_y) + return box + def get_clearance(document, box): clearance = 0 diff --git a/weasyprint/layout/inlines.py b/weasyprint/layout/inlines.py index 3aebf64d..fea29763 100644 --- a/weasyprint/layout/inlines.py +++ b/weasyprint/layout/inlines.py @@ -16,7 +16,7 @@ import functools import cairo from .absolute import absolute_layout, AbsolutePlaceholder -from .float import avoid_collisions, float_layout, FloatPlaceholder +from .float import avoid_collisions, float_layout from .markers import image_marker_layout from .percentages import resolve_percentages, resolve_one_percentage from .preferred import shrink_to_fit, inline_preferred_minimum_width @@ -562,9 +562,8 @@ def split_inline_box(document, box, position_x, max_x, skip_stack, box.width = ( containing_block.content_box_x() + containing_block.width - position_x) - placeholder = FloatPlaceholder(child) - float_layout(document, placeholder, box, absolute_boxes) - children.append(placeholder) + child = float_layout(document, child, box, absolute_boxes) + children.append(child) continue new_child, resume_at, preserved = split_inline_level( diff --git a/weasyprint/stacking.py b/weasyprint/stacking.py index d9a252d6..95b18331 100644 --- a/weasyprint/stacking.py +++ b/weasyprint/stacking.py @@ -14,7 +14,6 @@ import operator from .formatting_structure import boxes from .layout.absolute import AbsolutePlaceholder -from .layout.float import FloatPlaceholder _Z_INDEX_GETTER = operator.attrgetter('z_index') @@ -69,7 +68,7 @@ class StackingContext(object): blocks_and_cells = [] def dispatch_children(box): - if isinstance(box, (AbsolutePlaceholder, FloatPlaceholder)): + if isinstance(box, AbsolutePlaceholder): box = box._box if not isinstance(box, boxes.ParentBox): From 8d42985c7610eab5b13eb758734b5169fba8f832 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Thu, 31 May 2012 03:04:51 +0200 Subject: [PATCH 15/79] Remove useless variables for floats --- weasyprint/layout/float.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/weasyprint/layout/float.py b/weasyprint/layout/float.py index addc925a..81063434 100644 --- a/weasyprint/layout/float.py +++ b/weasyprint/layout/float.py @@ -19,12 +19,9 @@ from ..formatting_structure import boxes def float_layout(document, box, containing_block, absolute_boxes): """Set the width and position of floating ``box``.""" - cb = containing_block - cb_width = cb.width - cb_height = cb.height - - resolve_percentages(box, (cb_width, cb_height)) - resolve_position_percentages(box, (cb_width, cb_height)) + resolve_percentages(box, (containing_block.width, containing_block.height)) + resolve_position_percentages( + box, (containing_block.width, containing_block.height)) if box.margin_left == 'auto': box.margin_left = 0 From 2c3e0e825d47469566c76cc0fbe24c7ba0078b72 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Thu, 31 May 2012 14:59:34 +0200 Subject: [PATCH 16/79] Fix Py3k compat: next(iterator) instead of iterator.next() --- weasyprint/layout/preferred.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weasyprint/layout/preferred.py b/weasyprint/layout/preferred.py index af57195f..524feee0 100644 --- a/weasyprint/layout/preferred.py +++ b/weasyprint/layout/preferred.py @@ -170,7 +170,7 @@ def inline_preferred_minimum_width(box, outer=True, skip_stack=None, assert isinstance(child, boxes.TextBox) widths = text_lines_width(child, width=0, skip=skip) if first_line: - return widths.next() + return next(widths) else: current_line = max(widths) widest_line = max(widest_line, current_line) From 4c55eba456410f30628249fae870a438428a460c Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Thu, 31 May 2012 16:46:09 +0200 Subject: [PATCH 17/79] Don't layout the absolute boxes in the float layout --- weasyprint/layout/float.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/weasyprint/layout/float.py b/weasyprint/layout/float.py index 81063434..86786030 100644 --- a/weasyprint/layout/float.py +++ b/weasyprint/layout/float.py @@ -57,9 +57,6 @@ def float_layout(document, box, containing_block, absolute_boxes): else: assert isinstance(box, boxes.BlockReplacedBox) - for child_placeholder in absolute_boxes: - absolute_layout(document, child_placeholder, box) - box = find_float_position(document, box, containing_block) document.excluded_shapes.append(box) From deb7bac3b2ee2fa798116296e4272dbd22975c76 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Thu, 31 May 2012 18:08:14 +0200 Subject: [PATCH 18/79] Set the column_widths attribute for float tables --- weasyprint/layout/float.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/weasyprint/layout/float.py b/weasyprint/layout/float.py index 86786030..03195f24 100644 --- a/weasyprint/layout/float.py +++ b/weasyprint/layout/float.py @@ -10,10 +10,10 @@ from __future__ import division, unicode_literals -from .absolute import absolute_layout from .markers import list_marker_layout from .percentages import resolve_percentages, resolve_position_percentages from .preferred import shrink_to_fit +from .tables import table_wrapper_width from ..formatting_structure import boxes @@ -45,6 +45,11 @@ def float_layout(document, box, containing_block, absolute_boxes): # avoid a circular import from .blocks import block_container_layout + if box.is_table_wrapper: + table_wrapper_width( + box, (containing_block.width, containing_block.height), + absolute_boxes) + if isinstance(box, boxes.BlockBox): excluded_shapes = document.excluded_shapes document.excluded_shapes = [] From 8eead6332b6b9444f9a64bc10643f6fef80d7744 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Thu, 31 May 2012 19:55:29 +0200 Subject: [PATCH 19/79] Don't add the floats in the excluded shapes twice --- weasyprint/layout/blocks.py | 1 - 1 file changed, 1 deletion(-) diff --git a/weasyprint/layout/blocks.py b/weasyprint/layout/blocks.py index 19469ee4..79436d4d 100644 --- a/weasyprint/layout/blocks.py +++ b/weasyprint/layout/blocks.py @@ -256,7 +256,6 @@ def block_container_layout(document, box, max_position_y, skip_stack, document.fixed_boxes.append(placeholder) elif child.style.float in ('left', 'right'): child = float_layout(document, child, box, absolute_boxes) - document.excluded_shapes.append(child) new_children.append(child) continue From 0ce32375184109b6cf5e4865408319e75741677d Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Thu, 31 May 2012 19:55:43 +0200 Subject: [PATCH 20/79] Create a block formatting context for absolute elements --- weasyprint/layout/absolute.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/weasyprint/layout/absolute.py b/weasyprint/layout/absolute.py index 8a33cce2..e375bdae 100644 --- a/weasyprint/layout/absolute.py +++ b/weasyprint/layout/absolute.py @@ -72,12 +72,15 @@ def absolute_layout(document, placeholder, containing_block): resolve_percentages(box, (cb_width, cb_height)) resolve_position_percentages(box, (cb_width, cb_height)) + excluded_shapes = document.excluded_shapes + document.excluded_shapes = [] # Absolute tables are wrapped into block boxes if isinstance(box, boxes.BlockBox): new_box = absolute_block(document, box, containing_block) else: assert isinstance(box, boxes.BlockReplacedBox) new_box = absolute_replaced(document, box, containing_block) + document.excluded_shapes = excluded_shapes placeholder.set_laid_out_box(new_box) From afd1ce9c851eb3426f4258fd88e285fdac1daa0c Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Thu, 31 May 2012 20:03:10 +0200 Subject: [PATCH 21/79] Create a new block formatting context for non-block block containers --- weasyprint/layout/blocks.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/weasyprint/layout/blocks.py b/weasyprint/layout/blocks.py index 79436d4d..bc34fb1f 100644 --- a/weasyprint/layout/blocks.py +++ b/weasyprint/layout/blocks.py @@ -204,6 +204,11 @@ def block_container_layout(document, box, max_position_y, skip_stack, #if box.style.overflow != 'visible': # ... + # See http://www.w3.org/TR/CSS21/visuren.html#block-formatting + if not isinstance(box, boxes.BlockBox): + excluded_shapes = document.excluded_shapes + document.excluded_shapes = [] + if box.margin_top == 'auto': box.margin_top = 0 if box.margin_bottom == 'auto': @@ -437,6 +442,9 @@ def block_container_layout(document, box, max_position_y, skip_stack, for child in new_box.children: relative_positioning(child, (new_box.width, new_box.height)) + if not isinstance(box, boxes.BlockBox): + document.excluded_shapes = excluded_shapes + return new_box, resume_at, next_page, adjoining_margins, collapsing_through From d3ea76ffbfa73f45b1a5fd7b4c372b4d62cb5988 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Thu, 31 May 2012 21:05:10 +0200 Subject: [PATCH 22/79] Keep float elements to get preferred widths --- weasyprint/layout/preferred.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/weasyprint/layout/preferred.py b/weasyprint/layout/preferred.py index 524feee0..03a78ead 100644 --- a/weasyprint/layout/preferred.py +++ b/weasyprint/layout/preferred.py @@ -82,7 +82,7 @@ def _block_preferred_width(box, function, outer): # http://dbaron.org/css/intrinsic/#outer-intrinsic children_widths = [ function(child, outer=True) for child in box.children - if child.is_in_normal_flow()] + if not child.is_absolutely_positioned()] width = max(children_widths) if children_widths else 0 else: assert width.unit == 'px' @@ -154,7 +154,7 @@ def inline_preferred_minimum_width(box, outer=True, skip_stack=None, else: skip, skip_stack = skip_stack for index, child in box.enumerate_skip(skip): - if not child.is_in_normal_flow(): + if child.is_absolutely_positioned(): continue # Skip if isinstance(child, boxes.InlineReplacedBox): @@ -182,7 +182,7 @@ def inline_preferred_width(box, outer=True): widest_line = 0 current_line = 0 for child in box.children: - if not child.is_in_normal_flow(): + if child.is_absolutely_positioned(): continue # Skip if isinstance(child, boxes.InlineReplacedBox): @@ -343,7 +343,7 @@ def table_and_columns_preferred_widths(box, outer=True, table_preferred_width = sum(column_preferred_widths) + total_border_spacing captions = [child for child in box.children - if child is not table and child.is_in_normal_flow()] + if child is not table and not child.is_absolutely_positioned()] if captions: caption_width = max( From c00881790c99c19099cd579b213f733b304630b8 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Thu, 31 May 2012 22:33:28 +0200 Subject: [PATCH 23/79] Increase the auto height of block formatting context roots --- weasyprint/document.py | 20 +++++++++++++++++++- weasyprint/layout/absolute.py | 5 ++--- weasyprint/layout/blocks.py | 7 +++---- weasyprint/layout/float.py | 5 ++--- 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/weasyprint/document.py b/weasyprint/document.py index 0b1a8ddf..604f87a1 100644 --- a/weasyprint/document.py +++ b/weasyprint/document.py @@ -24,7 +24,6 @@ class Document(object): def __init__(self, backend, dom, user_stylesheets, user_agent_stylesheets): self.backend = backend self.fixed_boxes = [] - self.excluded_shapes = [] self.surface = backend.get_dummy_surface() self.dom = dom #: lxml HtmlElement object self.user_stylesheets = user_stylesheets @@ -33,6 +32,9 @@ class Document(object): self._computed_styles = None self._formatting_structure = None self._pages = None + self._excluded_shapes_lists = [] + + self.create_block_formatting_context() # TODO: remove this when Margin boxes variable dimension is correct. self._auto_margin_boxes_warning_shown = False @@ -90,3 +92,19 @@ class Document(object): draw.draw_page(self, page, context) backend.finish(self) + + def create_block_formatting_context(self): + self.excluded_shapes = [] + self._excluded_shapes_lists.append(self.excluded_shapes) + + def finish_block_formatting_context(self, root_box): + excluded_shapes = self._excluded_shapes_lists.pop() + self.excluded_shapes = self._excluded_shapes_lists[-1] + + # See http://www.w3.org/TR/CSS2/visudet.html#root-height + if root_box.style.height == 'auto': + box_bottom = root_box.content_box_y() + root_box.height + for shape in excluded_shapes: + shape_bottom = shape.position_y + shape.margin_height() + if shape_bottom > box_bottom: + root_box.height += shape_bottom - box_bottom diff --git a/weasyprint/layout/absolute.py b/weasyprint/layout/absolute.py index e375bdae..65a2fb9b 100644 --- a/weasyprint/layout/absolute.py +++ b/weasyprint/layout/absolute.py @@ -72,15 +72,14 @@ def absolute_layout(document, placeholder, containing_block): resolve_percentages(box, (cb_width, cb_height)) resolve_position_percentages(box, (cb_width, cb_height)) - excluded_shapes = document.excluded_shapes - document.excluded_shapes = [] + document.create_block_formatting_context() # Absolute tables are wrapped into block boxes if isinstance(box, boxes.BlockBox): new_box = absolute_block(document, box, containing_block) else: assert isinstance(box, boxes.BlockReplacedBox) new_box = absolute_replaced(document, box, containing_block) - document.excluded_shapes = excluded_shapes + document.finish_block_formatting_context(new_box) placeholder.set_laid_out_box(new_box) diff --git a/weasyprint/layout/blocks.py b/weasyprint/layout/blocks.py index bc34fb1f..3f364a73 100644 --- a/weasyprint/layout/blocks.py +++ b/weasyprint/layout/blocks.py @@ -206,8 +206,7 @@ def block_container_layout(document, box, max_position_y, skip_stack, # See http://www.w3.org/TR/CSS21/visuren.html#block-formatting if not isinstance(box, boxes.BlockBox): - excluded_shapes = document.excluded_shapes - document.excluded_shapes = [] + document.create_block_formatting_context() if box.margin_top == 'auto': box.margin_top = 0 @@ -442,8 +441,8 @@ def block_container_layout(document, box, max_position_y, skip_stack, for child in new_box.children: relative_positioning(child, (new_box.width, new_box.height)) - if not isinstance(box, boxes.BlockBox): - document.excluded_shapes = excluded_shapes + if not isinstance(new_box, boxes.BlockBox): + document.finish_block_formatting_context(new_box) return new_box, resume_at, next_page, adjoining_margins, collapsing_through diff --git a/weasyprint/layout/float.py b/weasyprint/layout/float.py index 03195f24..f060622c 100644 --- a/weasyprint/layout/float.py +++ b/weasyprint/layout/float.py @@ -51,14 +51,13 @@ def float_layout(document, box, containing_block, absolute_boxes): absolute_boxes) if isinstance(box, boxes.BlockBox): - excluded_shapes = document.excluded_shapes - document.excluded_shapes = [] + document.create_block_formatting_context() box, _, _, _, _ = block_container_layout( document, box, max_position_y=float('inf'), skip_stack=None, device_size=None, page_is_empty=False, absolute_boxes=absolute_boxes, adjoining_margins=None) list_marker_layout(document, box) - document.excluded_shapes = excluded_shapes + document.finish_block_formatting_context(box) else: assert isinstance(box, boxes.BlockReplacedBox) From 592caf55e068fdb942e4ac8100a5442030ac66c5 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Sat, 2 Jun 2012 08:36:56 +0200 Subject: [PATCH 24/79] Fix calls to preferred after merging the hinting fix. Conflicts: weasyprint/layout/pages.py weasyprint/layout/preferred.py --- weasyprint/layout/float.py | 2 +- weasyprint/layout/inlines.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/weasyprint/layout/float.py b/weasyprint/layout/float.py index f060622c..1a1d8021 100644 --- a/weasyprint/layout/float.py +++ b/weasyprint/layout/float.py @@ -40,7 +40,7 @@ def float_layout(document, box, containing_block, absolute_boxes): replaced_box_width(box, None) replaced_box_height(box, None) else: - box.width = shrink_to_fit(box, containing_block.width) + box.width = shrink_to_fit(document, box, containing_block.width) # avoid a circular import from .blocks import block_container_layout diff --git a/weasyprint/layout/inlines.py b/weasyprint/layout/inlines.py index be5f349a..7440a900 100644 --- a/weasyprint/layout/inlines.py +++ b/weasyprint/layout/inlines.py @@ -61,7 +61,7 @@ def get_next_linebox(document, linebox, position_y, skip_stack, """Return ``(line, resume_at)``.""" linebox.position_y = position_y linebox.width = inline_preferred_minimum_width( - linebox, skip_stack=skip_stack, first_line=True) + document, linebox, skip_stack=skip_stack, first_line=True) position_x, position_y, available_width = avoid_collisions( document, linebox, containing_block, outer=False) linebox.position_x = position_x From b1361612c67728a677cccba39f39cf7d3611d174 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Sat, 2 Jun 2012 08:46:11 +0200 Subject: [PATCH 25/79] Fix floating tables. --- weasyprint/layout/float.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weasyprint/layout/float.py b/weasyprint/layout/float.py index 1a1d8021..c1c7d855 100644 --- a/weasyprint/layout/float.py +++ b/weasyprint/layout/float.py @@ -47,7 +47,7 @@ def float_layout(document, box, containing_block, absolute_boxes): if box.is_table_wrapper: table_wrapper_width( - box, (containing_block.width, containing_block.height), + document, box, (containing_block.width, containing_block.height), absolute_boxes) if isinstance(box, boxes.BlockBox): From 10eabe5e181c2563cc36af35cdb1f42cc34c7cb3 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Mon, 4 Jun 2012 01:54:51 +0200 Subject: [PATCH 26/79] Insert the absolute boxes before their own children --- weasyprint/stacking.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/weasyprint/stacking.py b/weasyprint/stacking.py index a161e81c..32ce182e 100644 --- a/weasyprint/stacking.py +++ b/weasyprint/stacking.py @@ -100,11 +100,13 @@ class StackingContext(object): if child.style.position != 'static': assert child.style.z_index == 'auto' # "Fake" context: sub-contexts are already removed - child_contexts.append(StackingContext.from_box( + child_contexts.insert( + len(child_contexts), StackingContext.from_box( child, page, child_contexts)) continue elif child.is_floated(): - floats.append(StackingContext.from_box( + floats.insert( + len(child_contexts), StackingContext.from_box( child, page, child_contexts)) continue elif isinstance(child, boxes.BlockLevelBox): From 505ee8de26afcea369f95db0de1a4a02b3951a61 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Mon, 4 Jun 2012 19:22:18 +0200 Subject: [PATCH 27/79] Handle the mix/max-widths/heights for absolute and float --- weasyprint/layout/absolute.py | 115 ++++++++++++++++++++-------------- weasyprint/layout/blocks.py | 12 ++-- weasyprint/layout/float.py | 14 +++-- weasyprint/layout/inlines.py | 41 +----------- weasyprint/layout/min_max.py | 52 +++++++++++++++ 5 files changed, 140 insertions(+), 94 deletions(-) create mode 100644 weasyprint/layout/min_max.py diff --git a/weasyprint/layout/absolute.py b/weasyprint/layout/absolute.py index af32a312..db0e0273 100644 --- a/weasyprint/layout/absolute.py +++ b/weasyprint/layout/absolute.py @@ -13,6 +13,7 @@ from __future__ import division, unicode_literals from .percentages import resolve_percentages, resolve_position_percentages from .preferred import shrink_to_fit from .markers import list_marker_layout +from .min_max import handle_min_max_width from .tables import table_wrapper_width from ..formatting_structure import boxes @@ -50,69 +51,27 @@ class AbsolutePlaceholder(object): setattr(self._box, name, value) -def absolute_layout(document, placeholder, containing_block): - """Set the width of absolute positioned ``box``.""" - box = placeholder._box - - cb = containing_block - # TODO: handle inline boxes (point 10.1.4.1) - # http://www.w3.org/TR/CSS2/visudet.html#containing-block-details - if isinstance(containing_block, boxes.PageBox): - cb_x = cb.content_box_x() - cb_y = cb.content_box_y() - cb_width = cb.width - cb_height = cb.height - else: - cb_x = cb.padding_box_x() - cb_y = cb.padding_box_y() - cb_width = cb.padding_width() - cb_height = cb.padding_height() - containing_block = cb_x, cb_y, cb_width, cb_height - - resolve_percentages(box, (cb_width, cb_height)) - resolve_position_percentages(box, (cb_width, cb_height)) - - document.create_block_formatting_context() - # Absolute tables are wrapped into block boxes - if isinstance(box, boxes.BlockBox): - new_box = absolute_block(document, box, containing_block) - else: - assert isinstance(box, boxes.BlockReplacedBox) - new_box = absolute_replaced(document, box, containing_block) - document.finish_block_formatting_context(new_box) - - placeholder.set_laid_out_box(new_box) - - -def absolute_block(document, box, containing_block): +@handle_min_max_width +def absolute_width(box, document, containing_block): # http://www.w3.org/TR/CSS2/visudet.html#abs-replaced-width # These names are waaay too long margin_l = box.margin_left margin_r = box.margin_right - margin_t = box.margin_top - margin_b = box.margin_bottom padding_l = box.padding_left padding_r = box.padding_right - padding_t = box.padding_top - padding_b = box.padding_bottom border_l = box.border_left_width border_r = box.border_right_width - border_t = box.border_top_width - border_b = box.border_bottom_width width = box.width - height = box.height left = box.left right = box.right - top = box.top - bottom = box.bottom cb_x, cb_y, cb_width, cb_height = containing_block # TODO: handle bidi padding_plus_borders_x = padding_l + padding_r + border_l + border_r - translate_x = translate_y = 0 - translate_box_width = translate_box_height = False + translate_x = 0 + translate_box_width = False default_translate_x = cb_x - box.position_x if left == right == width == 'auto': if margin_l == 'auto': @@ -163,9 +122,28 @@ def absolute_block(document, box, containing_block): elif right == 'auto': translate_x = left + default_translate_x + return translate_box_width, translate_x + + +def absolute_height(box, document, containing_block): + # These names are waaay too long + margin_t = box.margin_top + margin_b = box.margin_bottom + padding_t = box.padding_top + padding_b = box.padding_bottom + border_t = box.border_top_width + border_b = box.border_bottom_width + height = box.height + top = box.top + bottom = box.bottom + + cb_x, cb_y, cb_width, cb_height = containing_block + # http://www.w3.org/TR/CSS2/visudet.html#abs-non-replaced-height paddings_plus_borders_y = padding_t + padding_b + border_t + border_b + translate_y = 0 + translate_box_height = False default_translate_y = cb_y - box.position_y if top == bottom == height == 'auto': pass # Keep the static position @@ -203,6 +181,17 @@ def absolute_block(document, box, containing_block): elif bottom == 'auto': translate_y = top + default_translate_y + return translate_box_height, translate_y + + +def absolute_block(document, box, containing_block): + cb_x, cb_y, cb_width, cb_height = containing_block + + translate_box_width, translate_x = absolute_width( + box, document, containing_block) + translate_box_height, translate_y = absolute_height( + box, document, containing_block) + # This box is the containing block for absolute descendants. absolute_boxes = [] @@ -234,6 +223,40 @@ def absolute_block(document, box, containing_block): return new_box +def absolute_layout(document, placeholder, containing_block): + """Set the width of absolute positioned ``box``.""" + box = placeholder._box + + cb = containing_block + # TODO: handle inline boxes (point 10.1.4.1) + # http://www.w3.org/TR/CSS2/visudet.html#containing-block-details + if isinstance(containing_block, boxes.PageBox): + cb_x = cb.content_box_x() + cb_y = cb.content_box_y() + cb_width = cb.width + cb_height = cb.height + else: + cb_x = cb.padding_box_x() + cb_y = cb.padding_box_y() + cb_width = cb.padding_width() + cb_height = cb.padding_height() + containing_block = cb_x, cb_y, cb_width, cb_height + + resolve_percentages(box, (cb_width, cb_height)) + resolve_position_percentages(box, (cb_width, cb_height)) + + document.create_block_formatting_context() + # Absolute tables are wrapped into block boxes + if isinstance(box, boxes.BlockBox): + new_box = absolute_block(document, box, containing_block) + else: + assert isinstance(box, boxes.BlockReplacedBox) + new_box = absolute_replaced(document, box, containing_block) + document.finish_block_formatting_context(new_box) + + placeholder.set_laid_out_box(new_box) + + def absolute_replaced(document, box, containing_block): # avoid a circular import from .inlines import inline_replaced_box_width_height diff --git a/weasyprint/layout/blocks.py b/weasyprint/layout/blocks.py index 77093bb4..b84e2370 100644 --- a/weasyprint/layout/blocks.py +++ b/weasyprint/layout/blocks.py @@ -15,9 +15,9 @@ from __future__ import division, unicode_literals from .absolute import absolute_layout, AbsolutePlaceholder from .float import float_layout, get_clearance from .inlines import (iter_line_boxes, replaced_box_width, replaced_box_height, - handle_min_max_width, min_max_replaced_height, - min_max_auto_replaced) + min_max_replaced_height, min_max_auto_replaced) from .markers import list_marker_layout +from .min_max import handle_min_max_width from .tables import table_layout, table_wrapper_width from .percentages import resolve_percentages, resolve_position_percentages from ..formatting_structure import boxes @@ -430,9 +430,6 @@ def block_container_layout(document, box, max_position_y, skip_stack, # http://www.w3.org/TR/CSS21/visudet.html#normal-block if new_box.height == 'auto': new_box.height = position_y - new_box.content_box_y() - new_box.height = max( - min(new_box.height, new_box.max_height), - new_box.min_height) if new_box.style.position == 'relative': # New containing block, resolve the layout of the absolute descendants @@ -445,6 +442,11 @@ def block_container_layout(document, box, max_position_y, skip_stack, if not isinstance(new_box, boxes.BlockBox): document.finish_block_formatting_context(new_box) + # After finish_block_formatting_context which may increment new_box.height + new_box.height = max( + min(new_box.height, new_box.max_height), + new_box.min_height) + return new_box, resume_at, next_page, adjoining_margins, collapsing_through diff --git a/weasyprint/layout/float.py b/weasyprint/layout/float.py index c1c7d855..44dc68e9 100644 --- a/weasyprint/layout/float.py +++ b/weasyprint/layout/float.py @@ -11,12 +11,18 @@ from __future__ import division, unicode_literals from .markers import list_marker_layout +from .min_max import handle_min_max_width from .percentages import resolve_percentages, resolve_position_percentages from .preferred import shrink_to_fit from .tables import table_wrapper_width from ..formatting_structure import boxes +@handle_min_max_width +def float_width(box, document, containing_block): + box.width = shrink_to_fit(document, box, containing_block.width) + + def float_layout(document, box, containing_block, absolute_boxes): """Set the width and position of floating ``box``.""" resolve_percentages(box, (containing_block.width, containing_block.height)) @@ -33,14 +39,14 @@ def float_layout(document, box, containing_block, absolute_boxes): box.position_y += clearance # avoid a circular import - from .inlines import replaced_box_width, replaced_box_height + from .inlines import min_max_replaced_width, min_max_replaced_height if box.width == 'auto': if isinstance(box, boxes.BlockReplacedBox): - replaced_box_width(box, None) - replaced_box_height(box, None) + min_max_replaced_width(box, None) + min_max_replaced_height(box, None) else: - box.width = shrink_to_fit(document, box, containing_block.width) + float_width(box, document, containing_block) # avoid a circular import from .blocks import block_container_layout diff --git a/weasyprint/layout/inlines.py b/weasyprint/layout/inlines.py index 7c581b7f..393cd951 100644 --- a/weasyprint/layout/inlines.py +++ b/weasyprint/layout/inlines.py @@ -11,13 +11,13 @@ """ from __future__ import division, unicode_literals -import functools import cairo from .absolute import absolute_layout, AbsolutePlaceholder from .float import avoid_collisions, float_layout from .markers import image_marker_layout +from .min_max import handle_min_max_width, handle_min_max_height from .percentages import resolve_percentages, resolve_one_percentage from .preferred import shrink_to_fit, inline_preferred_minimum_width from .tables import find_in_flow_baseline, table_wrapper_width @@ -290,44 +290,6 @@ def replaced_box_height(box, device_size): # box.height = min(150, device_width / 2) -def handle_min_max_width(function): - """Decorate a function that sets the used width of a box to handle - {min,max}-width. - """ - @functools.wraps(function) - def wrapper(box, *args): - computed_margins = box.margin_left, box.margin_right - function(box, *args) - if box.width > box.max_width: - box.width = box.max_width - box.margin_left, box.margin_right = computed_margins - function(box, *args) - if box.width < box.min_width: - box.width = box.min_width - box.margin_left, box.margin_right = computed_margins - function(box, *args) - return wrapper - - -def handle_min_max_height(function): - """Decorate a function that sets the used height of a box to handle - {min,max}-height. - """ - @functools.wraps(function) - def wrapper(box, *args): - computed_margins = box.margin_top, box.margin_bottom - function(box, *args) - if box.height > box.max_height: - box.height = box.max_height - box.margin_top, box.margin_bottom = computed_margins - function(box, *args) - if box.height < box.min_height: - box.height = box.min_height - box.margin_top, box.margin_bottom = computed_margins - function(box, *args) - return wrapper - - min_max_replaced_width = handle_min_max_width(replaced_box_width) min_max_replaced_height = handle_min_max_height(replaced_box_height) @@ -339,6 +301,7 @@ def inline_replaced_box_layout(box, device_size): setattr(box, 'margin_' + side, 0) inline_replaced_box_width_height(box, device_size) + def inline_replaced_box_width_height(box, device_size): if box.style.width == 'auto' and box.style.height == 'auto': replaced_box_width(box, device_size) diff --git a/weasyprint/layout/min_max.py b/weasyprint/layout/min_max.py new file mode 100644 index 00000000..c5ec090d --- /dev/null +++ b/weasyprint/layout/min_max.py @@ -0,0 +1,52 @@ +# coding: utf8 +""" + weasyprint.layout.min_max + ------------------------- + + :copyright: Copyright 2011-2012 Simon Sapin and contributors, see AUTHORS. + :license: BSD, see LICENSE for details. + +""" + +from __future__ import division, unicode_literals +import functools + + +def handle_min_max_width(function): + """Decorate a function that sets the used width of a box to handle + {min,max}-width. + """ + @functools.wraps(function) + def wrapper(box, *args): + computed_margins = box.margin_left, box.margin_right + result = function(box, *args) + if box.width > box.max_width: + box.width = box.max_width + box.margin_left, box.margin_right = computed_margins + result = function(box, *args) + if box.width < box.min_width: + box.width = box.min_width + box.margin_left, box.margin_right = computed_margins + result = function(box, *args) + return result + return wrapper + + +def handle_min_max_height(function): + """Decorate a function that sets the used height of a box to handle + {min,max}-height. + """ + @functools.wraps(function) + def wrapper(box, *args): + computed_margins = box.margin_top, box.margin_bottom + result = function(box, *args) + if box.height > box.max_height: + box.height = box.max_height + box.margin_top, box.margin_bottom = computed_margins + result = function(box, *args) + if box.height < box.min_height: + box.height = box.min_height + box.margin_top, box.margin_bottom = computed_margins + result = function(box, *args) + return result + return wrapper From 1624335b4083395127bba54a6bfdafe6bf8022a0 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Tue, 5 Jun 2012 10:33:19 +0200 Subject: [PATCH 28/79] Remove useless "computed values" functions. --- weasyprint/css/computed_values.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/weasyprint/css/computed_values.py b/weasyprint/css/computed_values.py index c90bf85c..0c858965 100644 --- a/weasyprint/css/computed_values.py +++ b/weasyprint/css/computed_values.py @@ -327,18 +327,6 @@ def border_width(computer, name, value): return length(computer, name, value, pixels_only=True) -@register_computer('clear') -def clear(computer, name, values): - """Compute the ``clear`` property.""" - return values - - -@register_computer('float') -def float(computer, name, values): - """Compute the ``float`` property.""" - return values - - @register_computer('content') def content(computer, name, values): """Compute the ``content`` property.""" From 182f1883c9e174718bbe52b7d46d7e2f4ea81baa Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Tue, 5 Jun 2012 11:27:51 +0200 Subject: [PATCH 29/79] Disable anti-aliasing on background-color Prefer crisp edges for the rectangle. I think this only affects the PNG output, not PDF. --- weasyprint/draw.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/weasyprint/draw.py b/weasyprint/draw.py index c274d456..4007ab69 100644 --- a/weasyprint/draw.py +++ b/weasyprint/draw.py @@ -257,6 +257,9 @@ def draw_background(document, context, style, painting_area, positioning_area): return with context.stacked(): + # Prefer crisp edges on background rectangles. + context.set_antialias(cairo.ANTIALIAS_NONE) + if painting_area: context.rectangle(*painting_area) context.clip() From d4409e8f4611eb6f38b01708eb767e8bdef29e84 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Tue, 5 Jun 2012 13:52:57 +0200 Subject: [PATCH 30/79] Add the Acid2 test (marked as expected failure). --- weasyprint/compat.py | 10 ++ weasyprint/tests/resources/acid2/index.html | 145 ++++++++++++++++++ .../tests/resources/acid2/reference.html | 17 ++ .../tests/resources/acid2/reference.png | Bin 0 -> 2261 bytes weasyprint/tests/test_draw.py | 75 ++++++--- 5 files changed, 224 insertions(+), 23 deletions(-) create mode 100644 weasyprint/tests/resources/acid2/index.html create mode 100644 weasyprint/tests/resources/acid2/reference.html create mode 100644 weasyprint/tests/resources/acid2/reference.png diff --git a/weasyprint/compat.py b/weasyprint/compat.py index ebbda283..887a78c3 100644 --- a/weasyprint/compat.py +++ b/weasyprint/compat.py @@ -44,6 +44,11 @@ if sys.version_info[0] >= 3: data = data.decode('utf8') return email.message_from_string(data) + + def ints_from_bytes(byte_string): + """Return a list of ints from a byte string""" + return list(byte_string) + else: # Python 2 from urlparse import urljoin, urlsplit @@ -80,3 +85,8 @@ else: if isinstance(data, unicode): data = data.encode('utf8') return email.message_from_string(data) + + + def ints_from_bytes(byte_string): + """Return a list of ints from a byte string""" + return map(ord, byte_string) diff --git a/weasyprint/tests/resources/acid2/index.html b/weasyprint/tests/resources/acid2/index.html new file mode 100644 index 00000000..b6432099 --- /dev/null +++ b/weasyprint/tests/resources/acid2/index.html @@ -0,0 +1,145 @@ + + + + The Second Acid Test + + + + +
+

Standards compliant?

+

Take The Acid2 Test and compare it to the reference rendering.

+
+

Hello World!

+
+

+

+
                              
+
ERROR
+
+
+
+
 
+
+
    +
  • +
  • +
  • +
  • +
+
+
+ + \ No newline at end of file diff --git a/weasyprint/tests/resources/acid2/reference.html b/weasyprint/tests/resources/acid2/reference.html new file mode 100644 index 00000000..d67a7386 --- /dev/null +++ b/weasyprint/tests/resources/acid2/reference.html @@ -0,0 +1,17 @@ + + + + The Second Acid Test (Reference Rendering) + + + +

Hello World!

+

Follow this link to view the reference image, which should be rendered below the text "Hello World!" on the test page in the same way that this paragraph is rendered below that text on this page.

+ + \ No newline at end of file diff --git a/weasyprint/tests/resources/acid2/reference.png b/weasyprint/tests/resources/acid2/reference.png new file mode 100644 index 0000000000000000000000000000000000000000..7aee7609d6ade6e39ad53b04a9f61e55f3b00c76 GIT binary patch literal 2261 zcmZvecT^L|7KbC!qzedn8x+GTMFL1!kN}}~hzJQ5qzY1$B7_hUP>7)g2uchVq&MjT zA}G>U5=5FTi$JJ?6lqDQK|mhvThDRd**P<{*5-xn{(f!$G1n+5=QpIVw2A~5vj3~L+E;0aoDm2>m^PE}dWVqeG!qLa-4I$DN6 zL?{qHJ6aAv&VbR*&;3$gE-JrXQHnk$>J@XZS|HH^NwGjkSQ+>gOs(Uy?}-;tRc)k# zS~O{}uvXq&jW40Qw7H!Q{?4@Jz@FZaS+@RM(lIb62}I0AATpy-a7lpPu@mwpaB;ma5!3ej5iOawKwbzhwoC?nSmui*SU7#)vs)V{dc)<}VW+ z1WZpK8jI-N9fX#Jr`ulCWx*tD^BpOz>7dyW?wO-!1LoFWBy>VL;<__et zDY7L*d8+5QE;hf`!c}@aoJUqHV1hjmLN(d+ z8#-OY?b+`1iF=aG#)X(0<{Do?m1 z0C2$x`uj7n?rNV{*OEvc?uRBK!-g4Kr3xEG8`4EaTKK;0AQ^!cU-Z|T&XS{&y{~$9 z_P*_GG4AYb4D?-ivlDQ%9UES_ie)ju5|a0=z!Jvt1g-IRrn^@@IMmei ztE;ow(;i$s>PTBb-4kUMG87}V1RrrHCm)|E7nRF1aNeaABjo)KfPt;x5_-AV00063 zfxzb@%bisP#UGNrN4D-!mtzjRnW5CN0BIm_XvpBCU0cg`QT0e|AX@e?lDZP0?VXio z8zFnwgafNbh-!_Znf*E7%k!La>=c=~9YM?;#fb`-&AD#Mw$AT1${Ctoh=94OB~U0b z+&$`)-0TXW>2?rjB>0mF z+s)0ZW|;7SPr+1XAK|!o-`6x69*4RDV@dGa$j+na-F^dR;Yb?@cj<-?*|sn^jrl$t zT~Wxb2_i&^#8XLU);2cm#7*-0?oL?1Yu3CNlNvhC5`%^;lM8mPu5B;2i`0N3iM}H@ zYADa95D8Pqg~~P3tqhS>HF<*QD^BieV4wgW7z&DjISqD>w|Xxw++-dB<6E;k2p>Pn z6n3jqM23c=OJpd2SiKU8E1N2J)X>T+;4=F))v~O@ZwiO|6y)aNVT2smq?7np61^AR z>_==rhBCOesGD>lBvMXRR!V4nQ$p20vzXAba3<9fnC#eLcGY6y37^`E&kd)vS<={nhfy-6EJ@)k|QgSzgbOhd(Rp>wITj6Lbz5 z=-5RjlcQTuVOm%$5%^mc!pJR9*xEY2-MuEKTu}MqV;(6ln9ksbs7>@KGQEo*X&Ryq zf7&v6QbJNyO>K#V*}c6lw{l(w6rrL>A|(mn8i7)f6$N^P{gFe+7%E5H;pJtYKyzc` zS8-2d_^ToBKBKJ*M1-yd2!$~`a69v?D%JWiCI&7nC}=8k9|g?_k3~U`Cz!;>#_DGF z2WZh~!NN?dx~C5QfzvC_UEN?QvojQ26kc1Y0Rz6*;pT0gj=#lrgTz*;qR>W_=B}<2 zY&LszbaeHvap17o7+2RUmy{lOQu6WSw8Y3xZ6q|=f>TH5o#!f_9db%IeKt#Y{B?}= z!%6hpo3N5idA_de^<(v@BXt23i|}v7F7?qfqOJ*KrL1cqVqq(9Fsc2+H9j{Xz2NGr z#U-9H+zJmdJ*5vf_#e!$(h-*{Tl8G)mmf^{!5bAFBA#PU@+?(aaj|zS2a{0N zx#S}ED%5vu(Z?CS1-p@}U_N20R zII`KV?CYJLnQXh*TD-k@kc(yhDMb@6h2iUwN=Gc|#)V{!{fi}t($*QM(vFIsR|9tx zN=(&7{?hM?`tQzdENFKJyD*IZ=tzsJE@*t8c&J)tJ0`L|8~<-*kcm0@`*!!oniUbg z#*#iG+rq~PKa;TNZ@iLA^77Xf_>qY)5`wPHyTV#p_=~y!YzmnOE)Czf+;|~+cHCMi z&md&>Rr>*$zl;8vJh)8$QaP+tSn)&twpHo2q@R+3f^$%c_r?Eb`uD2{fz#y!k`Ppk rv;5q-m*6c7dgE{6|D4^D%G5vlSW`2yTA=9Qy8^H@g_~3ux#9l{WcO5+ literal 0 HcmV?d00001 diff --git a/weasyprint/tests/test_draw.py b/weasyprint/tests/test_draw.py index 0a4aa986..28058658 100644 --- a/weasyprint/tests/test_draw.py +++ b/weasyprint/tests/test_draw.py @@ -19,10 +19,13 @@ import shutil import itertools from io import BytesIO +import pytest import pystacia -from ..compat import xrange +from ..compat import xrange, ints_from_bytes from ..urls import ensure_url +from .. import HTML, CSS +from ..backends import PNGBackend from .testing_utils import ( resource_filename, TestPNGDocument, FONTS, assert_no_logs, capture_logs) @@ -34,17 +37,6 @@ _ = b'\xff\xff\xff\xff' # white r = b'\xff\x00\x00\xff' # red B = b'\x00\x00\xff\xff' # blue BYTES_PER_PIXELS = 4 -PIXEL_FORMAT = 'rgba(%i, %i, %i, %i)' - - -def format_pixel(pixels, width, x, y): # pragma: no cover - """Return the pixel color as ``#RRGGBB``.""" - start = (y * width + x) * BYTES_PER_PIXELS - end = start + BYTES_PER_PIXELS - pixel_bytes = pixels[start:end] - # Py2/3 compat: - pixel_ints = tuple(ord(byte) for byte in pixel_bytes.decode('latin1')) - return PIXEL_FORMAT % pixel_ints def assert_pixels(name, expected_width, expected_height, expected_lines, @@ -143,21 +135,30 @@ def document_to_pixels(document, name, expected_width, expected_height, return raw -def assert_pixels_equal(name, width, height, lines, expected_lines): +def assert_pixels_equal(name, width, height, raw, expected_raw, tolerance=0): """ Take 2 matrices of height by width pixels and assert that they are the same. """ - if lines != expected_lines: # pragma: no cover - write_png(name + '.expected', expected_lines, width, height) - write_png(name, lines, width, height) - for y in xrange(height): - for x in xrange(width): - pixel = format_pixel(lines, width, x, y) - expected_pixel = format_pixel(expected_lines, width, x, y) - assert pixel == expected_pixel , ( - 'Pixel (%i, %i) in %s: expected %s, got %s' % ( - x, y, name, expected_pixel, pixel)) + if raw != expected_raw: # pragma: no cover + for i, (value, expected) in enumerate(zip( + ints_from_bytes(raw), + ints_from_bytes(expected_raw) + )): + if abs(value - expected) > tolerance: + write_png(name, raw, width, height) + write_png(name + '.expected', expected_raw, + width, height) + pixel_n = i // BYTES_PER_PIXELS + x = pixel_n // width + y = pixel_n % width + i % BYTES_PER_PIXELS + pixel = tuple(ints_from_bytes(raw[i:i + BYTES_PER_PIXELS])) + expected_pixel = tuple(ints_from_bytes( + expected_raw[i:i + BYTES_PER_PIXELS])) + assert 0, ( + 'Pixel (%i, %i) in %s: expected rgba%s, got rgab%s' + % (x, y, name, expected_pixel, pixel)) @assert_no_logs @@ -1911,3 +1912,31 @@ def test_2d_transform():
''') + + +@pytest.mark.xfail +@assert_no_logs +def test_acid2(): + """A local version of http://acid2.acidtests.org/""" + size = 640 + tolerance = 3 + + stylesheet = CSS(string=''' + @page { -weasy-size: %ipx; margin: 0 } + + /* Remove the introduction from the test, it is not in the reference */ + .intro { display: none } + + /* Work around 'vertical-align: top' not being implemented */ + a[href="reference.png"], img[src="reference.png"] { display: block } + ''' % size) + def get_pixels(filename): + return document_to_pixels( + HTML(resource_filename(filename))._get_document( + PNGBackend, [stylesheet]), + 'acid2', size, size) + + with capture_logs(): + result = get_pixels('acid2/index.html') + reference = get_pixels('acid2/reference.html') + assert_pixels_equal('acid2', size, size, result, reference, tolerance) From 39e503858dd96758529008e545698d3026bba245 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Tue, 5 Jun 2012 17:14:33 +0200 Subject: [PATCH 31/79] Fix margin collapsing with clearance --- weasyprint/css/properties.py | 1 + weasyprint/layout/blocks.py | 34 ++++++++++++++++------------------ weasyprint/layout/float.py | 5 +++-- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/weasyprint/css/properties.py b/weasyprint/css/properties.py index 76a40f39..0e1116a6 100644 --- a/weasyprint/css/properties.py +++ b/weasyprint/css/properties.py @@ -238,6 +238,7 @@ TABLE_WRAPPER_BOX_PROPERTIES = set(''' left right z_index + clear '''.split()) diff --git a/weasyprint/layout/blocks.py b/weasyprint/layout/blocks.py index b84e2370..ae8bf147 100644 --- a/weasyprint/layout/blocks.py +++ b/weasyprint/layout/blocks.py @@ -33,16 +33,26 @@ def block_level_layout(document, box, max_position_y, skip_stack, content box of the current page area. """ - clearance = get_clearance(document, box) - if clearance: - box.position_y += clearance - adjoining_margins = [] - if isinstance(box, boxes.TableBox): return table_layout( document, box, max_position_y, skip_stack, containing_block, device_size, page_is_empty, absolute_boxes) - elif isinstance(box, boxes.BlockBox): + + resolve_percentages(box, containing_block) + + if box.margin_top == 'auto': + box.margin_top = 0 + if box.margin_bottom == 'auto': + box.margin_bottom = 0 + + collapsed_margin = collapse_margin(adjoining_margins + [box.margin_top]) + clearance = get_clearance(document, box, collapsed_margin) + if clearance: + top_border_edge = box.position_y + collapsed_margin + clearance + box.position_y = top_border_edge - box.margin_top + adjoining_margins = [] + + if isinstance(box, boxes.BlockBox): return block_box_layout(document, box, max_position_y, skip_stack, containing_block, device_size, page_is_empty, absolute_boxes, adjoining_margins) @@ -61,7 +71,6 @@ def block_box_layout(document, box, max_position_y, skip_stack, containing_block, device_size, page_is_empty, absolute_boxes, adjoining_margins): """Lay out the block ``box``.""" - resolve_percentages(box, containing_block) if box.is_table_wrapper: table_wrapper_width( document, box, (containing_block.width, containing_block.height), @@ -86,12 +95,6 @@ min_max_block_replaced_width = handle_min_max_width(block_replaced_width) def block_replaced_box_layout(box, containing_block, device_size): """Lay out the block :class:`boxes.ReplacedBox` ``box``.""" - resolve_percentages(box, containing_block) - if box.margin_top == 'auto': - box.margin_top = 0 - if box.margin_bottom == 'auto': - box.margin_bottom = 0 - if box.style.width == 'auto' and box.style.height == 'auto': block_replaced_width(box, containing_block, device_size) replaced_box_height(box, device_size) @@ -209,11 +212,6 @@ def block_container_layout(document, box, max_position_y, skip_stack, if not isinstance(box, boxes.BlockBox): document.create_block_formatting_context() - if box.margin_top == 'auto': - box.margin_top = 0 - if box.margin_bottom == 'auto': - box.margin_bottom = 0 - if adjoining_margins is None: adjoining_margins = [] diff --git a/weasyprint/layout/float.py b/weasyprint/layout/float.py index 44dc68e9..e9a81bd6 100644 --- a/weasyprint/layout/float.py +++ b/weasyprint/layout/float.py @@ -103,12 +103,13 @@ def find_float_position(document, box, containing_block): return box -def get_clearance(document, box): +def get_clearance(document, box, collapsed_margin=0): clearance = 0 + hypothetical_position = box.position_y + collapsed_margin for excluded_shape in document.excluded_shapes: if box.style.clear in (excluded_shape.style.float, 'both'): y, h = excluded_shape.position_y, excluded_shape.margin_height() - clearance = max(clearance, y + h - box.position_y) + clearance = max(clearance, y + h - hypothetical_position) return clearance From d88ecbb6baf69543015962e2ac234a63af230e13 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Tue, 5 Jun 2012 17:56:58 +0200 Subject: [PATCH 32/79] Make the strut have an effect on line boxes --- weasyprint/layout/inlines.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/weasyprint/layout/inlines.py b/weasyprint/layout/inlines.py index 393cd951..adc37448 100644 --- a/weasyprint/layout/inlines.py +++ b/weasyprint/layout/inlines.py @@ -758,6 +758,12 @@ def inline_box_verticality(box, baseline_y): min_y = children_min_y if children_max_y > max_y: max_y = children_max_y + + if isinstance(box, boxes.LineBox): + ascent = -box.baseline + descent = ascent + box.height + max_y = max(max_y, descent) + min_y = min(min_y, ascent) return max_y, min_y From 568e0aeb7076eadf379470337e227ebf47681071 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Wed, 6 Jun 2012 09:49:56 +0200 Subject: [PATCH 33/79] Fix the drawing order of fixed boxes --- weasyprint/document.py | 2 +- weasyprint/formatting_structure/boxes.py | 1 + weasyprint/layout/__init__.py | 21 ++++++++++++++------- weasyprint/layout/blocks.py | 16 +++++++++------- weasyprint/layout/inlines.py | 16 +++++++++------- weasyprint/layout/pages.py | 6 +++--- 6 files changed, 37 insertions(+), 25 deletions(-) diff --git a/weasyprint/document.py b/weasyprint/document.py index 604f87a1..e874252e 100644 --- a/weasyprint/document.py +++ b/weasyprint/document.py @@ -23,7 +23,7 @@ class Document(object): """Abstract output document.""" def __init__(self, backend, dom, user_stylesheets, user_agent_stylesheets): self.backend = backend - self.fixed_boxes = [] + self.current_page = None self.surface = backend.get_dummy_surface() self.dom = dom #: lxml HtmlElement object self.user_stylesheets = user_stylesheets diff --git a/weasyprint/formatting_structure/boxes.py b/weasyprint/formatting_structure/boxes.py index 7407e8d2..82f3b301 100644 --- a/weasyprint/formatting_structure/boxes.py +++ b/weasyprint/formatting_structure/boxes.py @@ -556,6 +556,7 @@ class PageBox(ParentBox): # Page boxes are not linked to any element. super(PageBox, self).__init__( element_tag=None, sourceline=None, style=style, children=[]) + self.fixed_boxes = [] def __repr__(self): return '<%s %s>' % (type(self).__name__, self.page_type) diff --git a/weasyprint/layout/__init__.py b/weasyprint/layout/__init__.py index 9884e64b..261085d3 100644 --- a/weasyprint/layout/__init__.py +++ b/weasyprint/layout/__init__.py @@ -25,6 +25,15 @@ from .absolute import absolute_layout from .pages import make_all_pages, make_margin_boxes +def layout_fixed_boxes(document, pages): + """Lay out and yield the fixed boxes of ``pages``.""" + for page in pages: + for fixed_box in page.fixed_boxes: + fixed_box_for_page = fixed_box.copy() + absolute_layout(document, fixed_box_for_page, page) + yield fixed_box_for_page + + def layout_document(document, root_box): """Lay out the whole document. @@ -38,14 +47,12 @@ def layout_document(document, root_box): pages = list(make_all_pages(document, root_box)) page_counter = [1] counter_values = {'page': page_counter, 'pages': [len(pages)]} - for page in pages: + for i, page in enumerate(pages): + root_children = [] root, = page.children - root_children = list(root.children) - for fixed_box in document.fixed_boxes: - fixed_box_for_page = fixed_box.copy() - absolute_layout(document, fixed_box_for_page, page) - root_children.append(fixed_box_for_page) - + root_children.extend(layout_fixed_boxes(document, pages[:i])) + root_children.extend(root.children) + root_children.extend(layout_fixed_boxes(document, pages[i+1:])) root = root.copy_with_children(root_children) page.children = (root,) + tuple( make_margin_boxes(document, page, counter_values)) diff --git a/weasyprint/layout/blocks.py b/weasyprint/layout/blocks.py index ae8bf147..a16a2474 100644 --- a/weasyprint/layout/blocks.py +++ b/weasyprint/layout/blocks.py @@ -250,13 +250,12 @@ def block_container_layout(document, box, max_position_y, skip_stack, if not child.is_in_normal_flow(): child.position_y += collapse_margin(adjoining_margins) - if child.style.position in ('absolute', 'fixed'): + if child.is_absolutely_positioned(): placeholder = AbsolutePlaceholder(child) - if child.style.position == 'absolute': - absolute_boxes.append(placeholder) - new_children.append(placeholder) - else: - document.fixed_boxes.append(placeholder) + new_children.append(placeholder) + absolute_boxes.append(placeholder) + if child.style.position == 'fixed': + document.current_page.fixed_boxes.append(placeholder) elif child.style.float in ('left', 'right'): child = float_layout(document, child, box, absolute_boxes) new_children.append(child) @@ -432,7 +431,10 @@ def block_container_layout(document, box, max_position_y, skip_stack, if new_box.style.position == 'relative': # New containing block, resolve the layout of the absolute descendants for absolute_box in absolute_boxes: - absolute_layout(document, absolute_box, new_box) + if absolute_box.style.position == 'absolute': + absolute_layout(document, absolute_box, new_box) + else: + absolute_layout(document, absolute_box, document.current_page) for child in new_box.children: relative_positioning(child, (new_box.width, new_box.height)) diff --git a/weasyprint/layout/inlines.py b/weasyprint/layout/inlines.py index adc37448..054919cc 100644 --- a/weasyprint/layout/inlines.py +++ b/weasyprint/layout/inlines.py @@ -516,14 +516,13 @@ def split_inline_box(document, box, position_x, max_x, skip_stack, child.position_y = box.position_y if not child.is_in_normal_flow(): child.position_x = position_x - if child.style.position in ('absolute', 'fixed'): + if child.is_absolutely_positioned(): placeholder = AbsolutePlaceholder(child) line_placeholders.append(placeholder) - if child.style.position == 'absolute': - absolute_boxes.append(placeholder) - children.append(placeholder) - else: - document.fixed_boxes.append(placeholder) + children.append(placeholder) + absolute_boxes.append(placeholder) + if child.style.position == 'fixed': + document.current_page.fixed_boxes.append(placeholder) elif child.style.float in ('left', 'right'): # Set a maximum line width to lay out the float element, it # will be correctly set after that @@ -604,7 +603,10 @@ def split_inline_box(document, box, position_x, max_x, skip_stack, if new_box.style.position == 'relative': for absolute_box in absolute_boxes: - absolute_layout(document, absolute_box, new_box) + if absolute_box.style.position == 'absolute': + absolute_layout(document, absolute_box, new_box) + else: + absolute_layout(document, absolute_box, document.current_page) return new_box, resume_at, preserved_line_break diff --git a/weasyprint/layout/pages.py b/weasyprint/layout/pages.py index 1b56fcaa..06ff1a42 100644 --- a/weasyprint/layout/pages.py +++ b/weasyprint/layout/pages.py @@ -466,7 +466,7 @@ def make_page(document, root_box, page_type, resume_at, content_empty): style = document.style_for(page_type) # Propagated from the root or . style.overflow = root_box.viewport_overflow - page = boxes.PageBox(page_type, style) + document.current_page = page = boxes.PageBox(page_type, style) device_size = page.style.size page.outer_width, page.outer_height = device_size @@ -506,11 +506,11 @@ def make_page(document, root_box, page_type, resume_at, content_empty): for absolute_box in absolute_boxes: absolute_layout(document, absolute_box, page) - page = page.copy_with_children(children) + document.current_page = page.copy_with_children(children) if content_empty: resume_at = previous_resume_at - return page, resume_at, next_page + return document.current_page, resume_at, next_page def make_all_pages(document, root_box): From 043d71dc298f8b3dcb4000c39d9109cad743eb8d Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Wed, 6 Jun 2012 12:04:09 +0200 Subject: [PATCH 34/79] Pass the fixed_boxes argument instead of using current_page --- weasyprint/document.py | 1 - weasyprint/formatting_structure/boxes.py | 2 +- weasyprint/layout/__init__.py | 5 ++- weasyprint/layout/absolute.py | 14 +++---- weasyprint/layout/blocks.py | 36 ++++++++--------- weasyprint/layout/float.py | 8 ++-- weasyprint/layout/inlines.py | 50 ++++++++++++------------ weasyprint/layout/pages.py | 16 +++++--- weasyprint/layout/tables.py | 14 ++++--- 9 files changed, 77 insertions(+), 69 deletions(-) diff --git a/weasyprint/document.py b/weasyprint/document.py index e874252e..2d43d5e7 100644 --- a/weasyprint/document.py +++ b/weasyprint/document.py @@ -23,7 +23,6 @@ class Document(object): """Abstract output document.""" def __init__(self, backend, dom, user_stylesheets, user_agent_stylesheets): self.backend = backend - self.current_page = None self.surface = backend.get_dummy_surface() self.dom = dom #: lxml HtmlElement object self.user_stylesheets = user_stylesheets diff --git a/weasyprint/formatting_structure/boxes.py b/weasyprint/formatting_structure/boxes.py index 82f3b301..877407c9 100644 --- a/weasyprint/formatting_structure/boxes.py +++ b/weasyprint/formatting_structure/boxes.py @@ -554,9 +554,9 @@ class PageBox(ParentBox): def __init__(self, page_type, style): self.page_type = page_type # Page boxes are not linked to any element. + self.fixed_boxes = [] super(PageBox, self).__init__( element_tag=None, sourceline=None, style=style, children=[]) - self.fixed_boxes = [] def __repr__(self): return '<%s %s>' % (type(self).__name__, self.page_type) diff --git a/weasyprint/layout/__init__.py b/weasyprint/layout/__init__.py index 261085d3..95ff9ac5 100644 --- a/weasyprint/layout/__init__.py +++ b/weasyprint/layout/__init__.py @@ -30,7 +30,10 @@ def layout_fixed_boxes(document, pages): for page in pages: for fixed_box in page.fixed_boxes: fixed_box_for_page = fixed_box.copy() - absolute_layout(document, fixed_box_for_page, page) + # Use an empty list as last argument because the fixed boxes in the + # fixed box has already been added to page.fixed_boxes, we don't + # want to get them again + absolute_layout(document, fixed_box_for_page, page, []) yield fixed_box_for_page diff --git a/weasyprint/layout/absolute.py b/weasyprint/layout/absolute.py index db0e0273..8e2fe42d 100644 --- a/weasyprint/layout/absolute.py +++ b/weasyprint/layout/absolute.py @@ -184,7 +184,7 @@ def absolute_height(box, document, containing_block): return translate_box_height, translate_y -def absolute_block(document, box, containing_block): +def absolute_block(document, box, containing_block, fixed_boxes): cb_x, cb_y, cb_width, cb_height = containing_block translate_box_width, translate_x = absolute_width( @@ -196,8 +196,7 @@ def absolute_block(document, box, containing_block): absolute_boxes = [] if box.is_table_wrapper: - table_wrapper_width( - document, box, (cb_width, cb_height), absolute_boxes) + table_wrapper_width(document, box, (cb_width, cb_height)) # avoid a circular import from .blocks import block_container_layout @@ -206,12 +205,13 @@ def absolute_block(document, box, containing_block): new_box, _, _, _, _ = block_container_layout( document, box, max_position_y=float('inf'), skip_stack=None, device_size=None, page_is_empty=False, - absolute_boxes=absolute_boxes, adjoining_margins=None) + absolute_boxes=absolute_boxes, fixed_boxes=fixed_boxes, + adjoining_margins=None) list_marker_layout(document, new_box) for child_placeholder in absolute_boxes: - absolute_layout(document, child_placeholder, new_box) + absolute_layout(document, child_placeholder, new_box, fixed_boxes) if translate_box_width: translate_x -= new_box.width @@ -223,7 +223,7 @@ def absolute_block(document, box, containing_block): return new_box -def absolute_layout(document, placeholder, containing_block): +def absolute_layout(document, placeholder, containing_block, fixed_boxes): """Set the width of absolute positioned ``box``.""" box = placeholder._box @@ -248,7 +248,7 @@ def absolute_layout(document, placeholder, containing_block): document.create_block_formatting_context() # Absolute tables are wrapped into block boxes if isinstance(box, boxes.BlockBox): - new_box = absolute_block(document, box, containing_block) + new_box = absolute_block(document, box, containing_block, fixed_boxes) else: assert isinstance(box, boxes.BlockReplacedBox) new_box = absolute_replaced(document, box, containing_block) diff --git a/weasyprint/layout/blocks.py b/weasyprint/layout/blocks.py index a16a2474..73440bfd 100644 --- a/weasyprint/layout/blocks.py +++ b/weasyprint/layout/blocks.py @@ -25,7 +25,7 @@ from ..formatting_structure import boxes def block_level_layout(document, box, max_position_y, skip_stack, containing_block, device_size, page_is_empty, - absolute_boxes, adjoining_margins): + absolute_boxes, fixed_boxes, adjoining_margins): """Lay out the block-level ``box``. :param max_position_y: the absolute vertical position (as in @@ -36,7 +36,7 @@ def block_level_layout(document, box, max_position_y, skip_stack, if isinstance(box, boxes.TableBox): return table_layout( document, box, max_position_y, skip_stack, containing_block, - device_size, page_is_empty, absolute_boxes) + device_size, page_is_empty, absolute_boxes, fixed_boxes) resolve_percentages(box, containing_block) @@ -55,7 +55,7 @@ def block_level_layout(document, box, max_position_y, skip_stack, if isinstance(box, boxes.BlockBox): return block_box_layout(document, box, max_position_y, skip_stack, containing_block, device_size, page_is_empty, - absolute_boxes, adjoining_margins) + absolute_boxes, fixed_boxes, adjoining_margins) elif isinstance(box, boxes.BlockReplacedBox): box = block_replaced_box_layout(box, containing_block, device_size) resume_at = None @@ -69,18 +69,17 @@ def block_level_layout(document, box, max_position_y, skip_stack, def block_box_layout(document, box, max_position_y, skip_stack, containing_block, device_size, page_is_empty, - absolute_boxes, adjoining_margins): + absolute_boxes, fixed_boxes, adjoining_margins): """Lay out the block ``box``.""" if box.is_table_wrapper: table_wrapper_width( - document, box, (containing_block.width, containing_block.height), - absolute_boxes) + document, box, (containing_block.width, containing_block.height)) block_level_width(box, containing_block) new_box, resume_at, next_page, adjoining_margins, collapsing_through = \ block_container_layout( - document, box, max_position_y, skip_stack, - device_size, page_is_empty, absolute_boxes, adjoining_margins) + document, box, max_position_y, skip_stack, device_size, + page_is_empty, absolute_boxes, fixed_boxes, adjoining_margins) list_marker_layout(document, new_box) return new_box, resume_at, next_page, adjoining_margins, collapsing_through @@ -197,7 +196,7 @@ def relative_positioning(box, containing_block): def block_container_layout(document, box, max_position_y, skip_stack, device_size, page_is_empty, absolute_boxes, - adjoining_margins=None): + fixed_boxes, adjoining_margins=None): """Set the ``box`` height.""" assert isinstance(box, boxes.BlockContainerBox) @@ -253,11 +252,13 @@ def block_container_layout(document, box, max_position_y, skip_stack, if child.is_absolutely_positioned(): placeholder = AbsolutePlaceholder(child) new_children.append(placeholder) - absolute_boxes.append(placeholder) - if child.style.position == 'fixed': - document.current_page.fixed_boxes.append(placeholder) + if child.style.position == 'absolute': + absolute_boxes.append(placeholder) + else: + fixed_boxes.append(placeholder) elif child.style.float in ('left', 'right'): - child = float_layout(document, child, box, absolute_boxes) + child = float_layout( + document, child, box, absolute_boxes, fixed_boxes) new_children.append(child) continue @@ -270,7 +271,7 @@ def block_container_layout(document, box, max_position_y, skip_stack, new_containing_block = box lines_iterator = iter_line_boxes( document, child, position_y, skip_stack, - new_containing_block, device_size, absolute_boxes) + new_containing_block, device_size, absolute_boxes, fixed_boxes) new_lines = [] is_page_break = False for line, resume_at in lines_iterator: @@ -338,7 +339,7 @@ def block_container_layout(document, box, max_position_y, skip_stack, document, child, max_position_y, skip_stack, new_containing_block, device_size, page_is_empty and not new_children, - absolute_boxes, + absolute_boxes, fixed_boxes, adjoining_margins) skip_stack = None @@ -431,10 +432,7 @@ def block_container_layout(document, box, max_position_y, skip_stack, if new_box.style.position == 'relative': # New containing block, resolve the layout of the absolute descendants for absolute_box in absolute_boxes: - if absolute_box.style.position == 'absolute': - absolute_layout(document, absolute_box, new_box) - else: - absolute_layout(document, absolute_box, document.current_page) + absolute_layout(document, absolute_box, new_box, fixed_boxes) for child in new_box.children: relative_positioning(child, (new_box.width, new_box.height)) diff --git a/weasyprint/layout/float.py b/weasyprint/layout/float.py index e9a81bd6..f26e4540 100644 --- a/weasyprint/layout/float.py +++ b/weasyprint/layout/float.py @@ -23,7 +23,7 @@ def float_width(box, document, containing_block): box.width = shrink_to_fit(document, box, containing_block.width) -def float_layout(document, box, containing_block, absolute_boxes): +def float_layout(document, box, containing_block, absolute_boxes, fixed_boxes): """Set the width and position of floating ``box``.""" resolve_percentages(box, (containing_block.width, containing_block.height)) resolve_position_percentages( @@ -53,15 +53,15 @@ def float_layout(document, box, containing_block, absolute_boxes): if box.is_table_wrapper: table_wrapper_width( - document, box, (containing_block.width, containing_block.height), - absolute_boxes) + document, box, (containing_block.width, containing_block.height)) if isinstance(box, boxes.BlockBox): document.create_block_formatting_context() box, _, _, _, _ = block_container_layout( document, box, max_position_y=float('inf'), skip_stack=None, device_size=None, page_is_empty=False, - absolute_boxes=absolute_boxes, adjoining_margins=None) + absolute_boxes=absolute_boxes, fixed_boxes=fixed_boxes, + adjoining_margins=None) list_marker_layout(document, box) document.finish_block_formatting_context(box) else: diff --git a/weasyprint/layout/inlines.py b/weasyprint/layout/inlines.py index 054919cc..349cb4a3 100644 --- a/weasyprint/layout/inlines.py +++ b/weasyprint/layout/inlines.py @@ -26,8 +26,8 @@ from ..formatting_structure import boxes from ..css.computed_values import used_line_height -def iter_line_boxes(document, box, position_y, skip_stack, - containing_block, device_size, absolute_boxes): +def iter_line_boxes(document, box, position_y, skip_stack, containing_block, + device_size, absolute_boxes, fixed_boxes): """Return an iterator of ``(line, resume_at)``. ``line`` is a laid-out LineBox with as much content as possible that @@ -46,8 +46,8 @@ def iter_line_boxes(document, box, position_y, skip_stack, strut = used_line_height(containing_block.style) while 1: line, resume_at = get_next_linebox( - document, box, position_y, skip_stack, - containing_block, device_size, absolute_boxes) + document, box, position_y, skip_stack, containing_block, + device_size, absolute_boxes, fixed_boxes) if line is None: return # TODO: Make sure the line and the strut are on the same baseline. @@ -61,7 +61,8 @@ def iter_line_boxes(document, box, position_y, skip_stack, def get_next_linebox(document, linebox, position_y, skip_stack, - containing_block, device_size, absolute_boxes): + containing_block, device_size, absolute_boxes, + fixed_boxes): """Return ``(line, resume_at)``.""" linebox.position_y = position_y linebox.width = inline_preferred_minimum_width( @@ -86,7 +87,7 @@ def get_next_linebox(document, linebox, position_y, skip_stack, resolve_percentages(linebox, containing_block) line, resume_at, preserved_line_break = split_inline_box( document, linebox, position_x, max_x, skip_stack, containing_block, - device_size, absolute_boxes, line_placeholders) + device_size, absolute_boxes, fixed_boxes, line_placeholders) remove_last_whitespace(document, line) @@ -370,7 +371,7 @@ def min_max_auto_replaced(box): def atomic_box(document, box, position_x, skip_stack, containing_block, - device_size, absolute_boxes): + device_size, absolute_boxes, fixed_boxes): """Compute the width and the height of the atomic ``box``.""" if isinstance(box, boxes.ReplacedBox): if getattr(box, 'is_list_marker', False): @@ -383,17 +384,18 @@ def atomic_box(document, box, position_x, skip_stack, containing_block, table_wrapper_width( document, box, (containing_block.width, containing_block.height), - absolute_boxes) + absolute_boxes, fixed_boxes) box = inline_block_box_layout( document, box, position_x, skip_stack, containing_block, - device_size, absolute_boxes) + device_size, 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(document, box, position_x, skip_stack, - containing_block, device_size, absolute_boxes): + containing_block, device_size, absolute_boxes, + fixed_boxes): # Avoid a circular import from .blocks import block_container_layout @@ -412,7 +414,7 @@ def inline_block_box_layout(document, box, position_x, skip_stack, box, _, _, _, _ = block_container_layout( document, box, max_position_y=float('inf'), skip_stack=skip_stack, device_size=device_size, page_is_empty=True, - absolute_boxes=absolute_boxes) + absolute_boxes=absolute_boxes, fixed_boxes=fixed_boxes) box.baseline = inline_block_baseline(box) return box @@ -440,7 +442,7 @@ def inline_block_width(box, document, containing_block): def split_inline_level(document, box, position_x, max_x, skip_stack, containing_block, device_size, absolute_boxes, - line_placeholders): + fixed_boxes, line_placeholders): """Fit as much content as possible from an inline-level box in a width. Return ``(new_box, resume_at)``. ``resume_at`` is ``None`` if all of the @@ -476,11 +478,11 @@ def split_inline_level(document, box, position_x, max_x, skip_stack, box.margin_right = 0 new_box, resume_at, preserved_line_break = split_inline_box( document, box, position_x, max_x, skip_stack, containing_block, - device_size, absolute_boxes, line_placeholders) + device_size, absolute_boxes, fixed_boxes, line_placeholders) elif isinstance(box, boxes.AtomicInlineLevelBox): new_box = atomic_box( document, box, position_x, skip_stack, containing_block, - device_size, absolute_boxes) + device_size, absolute_boxes, fixed_boxes) new_box.position_x = position_x resume_at = None preserved_line_break = False @@ -490,7 +492,7 @@ def split_inline_level(document, box, position_x, max_x, skip_stack, def split_inline_box(document, box, position_x, max_x, skip_stack, containing_block, device_size, absolute_boxes, - line_placeholders): + fixed_boxes, line_placeholders): """Same behavior as split_inline_level.""" initial_position_x = position_x assert isinstance(box, (boxes.LineBox, boxes.InlineBox)) @@ -521,8 +523,10 @@ def split_inline_box(document, box, position_x, max_x, skip_stack, line_placeholders.append(placeholder) children.append(placeholder) absolute_boxes.append(placeholder) - if child.style.position == 'fixed': - document.current_page.fixed_boxes.append(placeholder) + if child.style.position == 'absolute': + absolute_boxes.append(placeholder) + else: + fixed_boxes.append(placeholder) elif child.style.float in ('left', 'right'): # Set a maximum line width to lay out the float element, it # will be correctly set after that @@ -530,13 +534,14 @@ def split_inline_box(document, box, position_x, max_x, skip_stack, box.width = ( containing_block.content_box_x() + containing_block.width - position_x) - child = float_layout(document, child, box, absolute_boxes) + child = float_layout( + document, child, box, absolute_boxes, fixed_boxes) children.append(child) continue new_child, resume_at, preserved = split_inline_level( - document, child, position_x, max_x, skip_stack, - containing_block, device_size, absolute_boxes, line_placeholders) + document, child, position_x, max_x, skip_stack, containing_block, + device_size, absolute_boxes, fixed_boxes, line_placeholders) skip_stack = None if preserved: preserved_line_break = True @@ -603,10 +608,7 @@ def split_inline_box(document, box, position_x, max_x, skip_stack, if new_box.style.position == 'relative': for absolute_box in absolute_boxes: - if absolute_box.style.position == 'absolute': - absolute_layout(document, absolute_box, new_box) - else: - absolute_layout(document, absolute_box, document.current_page) + absolute_layout(document, absolute_box, new_box, fixed_boxes) return new_box, resume_at, preserved_line_break diff --git a/weasyprint/layout/pages.py b/weasyprint/layout/pages.py index 06ff1a42..f7653048 100644 --- a/weasyprint/layout/pages.py +++ b/weasyprint/layout/pages.py @@ -466,7 +466,7 @@ def make_page(document, root_box, page_type, resume_at, content_empty): style = document.style_for(page_type) # Propagated from the root or . style.overflow = root_box.viewport_overflow - document.current_page = page = boxes.PageBox(page_type, style) + page = boxes.PageBox(page_type, style) device_size = page.style.size page.outer_width, page.outer_height = device_size @@ -495,22 +495,26 @@ def make_page(document, root_box, page_type, resume_at, content_empty): page_is_empty = True adjoining_margins = [] absolute_boxes = [] + fixed_boxes = [] root_box, resume_at, next_page, _, _ = block_level_layout( document, root_box, page_content_bottom, resume_at, initial_containing_block, device_size, page_is_empty, - absolute_boxes, adjoining_margins) + absolute_boxes, fixed_boxes, adjoining_margins) assert root_box children = [root_box] - for absolute_box in absolute_boxes: - absolute_layout(document, absolute_box, page) + for absolute_box in absolute_boxes + fixed_boxes: + # Use an empty list as last argument because the fixed boxes in the + # fixed box has already been added to fixed_boxes, we don't want to get + # them again + absolute_layout(document, absolute_box, page, []) - document.current_page = page.copy_with_children(children) + page = page.copy_with_children(children) if content_empty: resume_at = previous_resume_at - return document.current_page, resume_at, next_page + return page, resume_at, next_page def make_all_pages(document, root_box): diff --git a/weasyprint/layout/tables.py b/weasyprint/layout/tables.py index 6872a37b..b9a88dad 100644 --- a/weasyprint/layout/tables.py +++ b/weasyprint/layout/tables.py @@ -21,7 +21,8 @@ from .preferred import table_and_columns_preferred_widths def table_layout(document, table, max_position_y, skip_stack, - containing_block, device_size, page_is_empty, absolute_boxes): + containing_block, device_size, page_is_empty, absolute_boxes, + fixed_boxes): """Layout for a table box. For now only the fixed layout and separate border model are supported. @@ -105,7 +106,8 @@ def table_layout(document, table, max_position_y, skip_stack, skip_stack=None, device_size=device_size, page_is_empty=True, - absolute_boxes=absolute_boxes) + absolute_boxes=absolute_boxes, + fixed_boxes=fixed_boxes) if computed_cell_height != 'auto': cell.height = max(cell.height, computed_cell_height) new_row_children.append(cell) @@ -447,7 +449,7 @@ def fixed_table_layout(box, absolute_boxes): table.column_widths = column_widths -def auto_table_layout(document, box, containing_block, absolute_boxes): +def auto_table_layout(document, box, containing_block): """Run the auto table layout and return a list of column widths. http://www.w3.org/TR/CSS21/tables.html#auto-table-layout @@ -504,15 +506,15 @@ def auto_table_layout(document, box, containing_block, absolute_boxes): for column_width in table.column_widths] -def table_wrapper_width(document, wrapper, containing_block, absolute_boxes): +def table_wrapper_width(document, wrapper, containing_block): """Find the width of each column and derive the wrapper width.""" table = wrapper.get_wrapped_table() resolve_percentages(table, containing_block) if table.style.table_layout == 'fixed' and table.width != 'auto': - fixed_table_layout(wrapper, absolute_boxes) + fixed_table_layout(wrapper) else: - auto_table_layout(document, wrapper, containing_block, absolute_boxes) + auto_table_layout(document, wrapper, containing_block) wrapper.width = table.border_width() wrapper.style.width = Dimension(wrapper.width, 'px') From 706113c24b266c1d6c8c6ba0256ad627cc49a77f Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Wed, 6 Jun 2012 12:05:59 +0200 Subject: [PATCH 35/79] Revert "Make the strut have an effect on line boxes" This reverts commit d88ecbb6baf69543015962e2ac234a63af230e13. --- weasyprint/layout/inlines.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/weasyprint/layout/inlines.py b/weasyprint/layout/inlines.py index 349cb4a3..639bbb80 100644 --- a/weasyprint/layout/inlines.py +++ b/weasyprint/layout/inlines.py @@ -762,12 +762,6 @@ def inline_box_verticality(box, baseline_y): min_y = children_min_y if children_max_y > max_y: max_y = children_max_y - - if isinstance(box, boxes.LineBox): - ascent = -box.baseline - descent = ascent + box.height - max_y = max(max_y, descent) - min_y = min(min_y, ascent) return max_y, min_y From 6cbdab07992fc3fb4801bc0c7a8175565912fc00 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Wed, 6 Jun 2012 12:52:02 +0200 Subject: [PATCH 36/79] Fix various tests, remove monkeypatch validation --- weasyprint/layout/inlines.py | 3 +-- weasyprint/layout/pages.py | 2 +- weasyprint/layout/tables.py | 2 +- weasyprint/tests/test_boxes.py | 25 ------------------------- weasyprint/tests/test_layout.py | 5 ++--- 5 files changed, 5 insertions(+), 32 deletions(-) diff --git a/weasyprint/layout/inlines.py b/weasyprint/layout/inlines.py index 639bbb80..aa10d17f 100644 --- a/weasyprint/layout/inlines.py +++ b/weasyprint/layout/inlines.py @@ -383,8 +383,7 @@ def atomic_box(document, box, position_x, skip_stack, containing_block, if box.is_table_wrapper: table_wrapper_width( document, box, - (containing_block.width, containing_block.height), - absolute_boxes, fixed_boxes) + (containing_block.width, containing_block.height)) box = inline_block_box_layout( document, box, position_x, skip_stack, containing_block, device_size, absolute_boxes, fixed_boxes) diff --git a/weasyprint/layout/pages.py b/weasyprint/layout/pages.py index f7653048..baf1d4bd 100644 --- a/weasyprint/layout/pages.py +++ b/weasyprint/layout/pages.py @@ -429,7 +429,7 @@ def margin_box_content_layout(document, page, box): document, box, max_position_y=float('inf'), skip_stack=None, device_size=page.style.size, page_is_empty=True, - absolute_boxes=[]) + absolute_boxes=[], fixed_boxes=[]) assert resume_at is None vertical_align = box.style.vertical_align diff --git a/weasyprint/layout/tables.py b/weasyprint/layout/tables.py index b9a88dad..4ca5c9d6 100644 --- a/weasyprint/layout/tables.py +++ b/weasyprint/layout/tables.py @@ -364,7 +364,7 @@ def add_top_padding(box, extra_padding): child.translate(dy=extra_padding) -def fixed_table_layout(box, absolute_boxes): +def fixed_table_layout(box): """Run the fixed table layout and return a list of column widths http://www.w3.org/TR/CSS21/tables.html#fixed-table-layout diff --git a/weasyprint/tests/test_boxes.py b/weasyprint/tests/test_boxes.py index 8223d326..a35d768b 100644 --- a/weasyprint/tests/test_boxes.py +++ b/weasyprint/tests/test_boxes.py @@ -12,11 +12,8 @@ from __future__ import division, unicode_literals -import contextlib - from .testing_utils import ( resource_filename, TestPNGDocument, assert_no_logs, capture_logs) -from ..css import validation from ..formatting_structure import boxes, build, counters @@ -79,28 +76,6 @@ def to_lists(box_tree): return serialize(unwrap_html_body(box_tree)) -@contextlib.contextmanager -def monkeypatch_validation(replacement): - """Create a context manager patching the validation mechanism. - - This is useful to change the behaviour of the validation for one property - not yet supported, without affecting the validation for the other - properties. - - """ - real_non_shorthand = validation.validate_non_shorthand - - def patched_non_shorthand(*args, **kwargs): - """Wraps the validator into ``replacement``.""" - return replacement(real_non_shorthand, *args, **kwargs) - - validation.validate_non_shorthand = patched_non_shorthand - try: - yield - finally: - validation.validate_non_shorthand = real_non_shorthand - - def parse(html_content): """Parse some HTML, apply stylesheets and transform to boxes.""" document = TestPNGDocument(html_content, diff --git a/weasyprint/tests/test_layout.py b/weasyprint/tests/test_layout.py index 4ef50dea..a8a675d7 100644 --- a/weasyprint/tests/test_layout.py +++ b/weasyprint/tests/test_layout.py @@ -15,7 +15,6 @@ from __future__ import division, unicode_literals from .testing_utils import ( TestPNGDocument, resource_filename, FONTS, assert_no_logs, capture_logs) -from .test_boxes import monkeypatch_validation from ..formatting_structure import boxes from ..layout.inlines import split_inline_box from ..layout.percentages import resolve_percentages @@ -1644,7 +1643,7 @@ def test_inlinebox_spliting(): while 1: inlinebox.position_y = 0 box, skip, _ = split_inline_box( - document, inlinebox, 0, width, skip, parent, None, [], []) + document, inlinebox, 0, width, skip, parent, None, [], [], []) yield box if skip is None: break @@ -1758,7 +1757,7 @@ def test_inlinebox_text_after_spliting(): while 1: inlinebox.position_y = 0 box, skip, _ = split_inline_box( - document, inlinebox, 0, 100, skip, paragraph, None, [], []) + document, inlinebox, 0, 100, skip, paragraph, None, [], [], []) parts.append(box) if skip is None: break From 9b3e66ccbebdfa4347faaab86ba66d641f9c89b6 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Wed, 6 Jun 2012 15:01:42 +0200 Subject: [PATCH 37/79] Ignore tests with dom or interact flag --- weasyprint/tests/w3_test_suite/run.py | 23 ++++++++++++++++------- weasyprint/tests/w3_test_suite/web.py | 2 +- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/weasyprint/tests/w3_test_suite/run.py b/weasyprint/tests/w3_test_suite/run.py index 8f8620a9..6412223f 100644 --- a/weasyprint/tests/w3_test_suite/run.py +++ b/weasyprint/tests/w3_test_suite/run.py @@ -26,14 +26,16 @@ import pystacia from weasyprint import HTML, CSS from weasyprint.compat import urlopen +from .web import read_testinfo + TEST_SUITE_VERSION = '20110323' #BASE_URL = 'http://test.csswg.org/suites/css2.1/{}/html4/' # Download and extract the zip file from http://test.csswg.org/suites/css2.1/ -BASE_URL = 'file://' + os.path.expanduser('~/css2.1_test_suite/{}/html4/') - -BASE_URL = BASE_URL.format(TEST_SUITE_VERSION) +BASE_PATH = os.path.expanduser( + '~/css2.1_test_suite/{}/html4/').format(TEST_SUITE_VERSION) +BASE_URL = 'file://' + BASE_PATH RESULTS_DIRECTORY = os.path.join(os.path.dirname(__file__), 'test_results') @@ -41,6 +43,8 @@ PAGE_SIZE_STYLESHEET = CSS(string=''' @page { margin: 0; -weasy-size: 640px } ''') +IGNORED_FLAGS = {'interact', 'dom'} + def get_url(url): return closing(urlopen(BASE_URL + url)) @@ -73,8 +77,8 @@ def make_test_suite(): rendered = {} # Memoize def render(name): - result = rendered.get(name) - if result is None: + raw = rendered.get(name) + if raw is None: # name is sometimes "support/something.htm" basename = os.path.basename(name) png_filename = os.path.join(RESULTS_DIRECTORY, basename + '.png') @@ -83,7 +87,11 @@ def make_test_suite(): with closing(pystacia.read(png_filename)) as image: raw = image.get_raw('rgba') rendered[name] = raw - return result + return raw + + flags_by_id = { + test['test_id'] + '.htm': test['flags'] + for test in read_testinfo(BASE_PATH)} for equal, test, references in get_test_list(): # Use default parameter values to bind to the current values, @@ -93,7 +101,8 @@ def make_test_suite(): references_render = map(render, references) return all((test_render != ref_render) ^ equal for ref_render in references_render) - yield test, test_function + if not set(flags_by_id[test]).intersection(IGNORED_FLAGS): + yield test, test_function def main(): diff --git a/weasyprint/tests/w3_test_suite/web.py b/weasyprint/tests/w3_test_suite/web.py index ea5b7f19..f00d2cb1 100644 --- a/weasyprint/tests/w3_test_suite/web.py +++ b/weasyprint/tests/w3_test_suite/web.py @@ -24,7 +24,7 @@ from weasyprint import HTML, CSS def split(something): - return something.split(',') if something else '' + return something.split(',') if something else [] def read_testinfo(suite_directory): From 09895757ca6f7c23c90cc5f63e5b488621fa8350 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Fri, 8 Jun 2012 17:17:19 +0200 Subject: [PATCH 38/79] Now passing Acid 2! --- weasyprint/tests/test_draw.py | 1 - 1 file changed, 1 deletion(-) diff --git a/weasyprint/tests/test_draw.py b/weasyprint/tests/test_draw.py index ba673114..384e2e11 100644 --- a/weasyprint/tests/test_draw.py +++ b/weasyprint/tests/test_draw.py @@ -1920,7 +1920,6 @@ def test_2d_transform(): ''') -@pytest.mark.xfail @assert_no_logs def test_acid2(): """A local version of http://acid2.acidtests.org/""" From 2c445b75b9bd0c780f5793d7b9be43f8b52d0a8c Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Fri, 8 Jun 2012 18:50:20 +0200 Subject: [PATCH 39/79] 'text-align: top' is implemented, remove a hack for Acid 2. --- weasyprint/tests/test_draw.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/weasyprint/tests/test_draw.py b/weasyprint/tests/test_draw.py index 384e2e11..73269739 100644 --- a/weasyprint/tests/test_draw.py +++ b/weasyprint/tests/test_draw.py @@ -1931,9 +1931,6 @@ def test_acid2(): /* Remove the introduction from the test, it is not in the reference */ .intro { display: none } - - /* Work around 'vertical-align: top' not being implemented */ - a[href="reference.png"], img[src="reference.png"] { display: block } ''' % size) def get_pixels(filename): return document_to_pixels( From 937710bf9cc15505ce288cbaae300ee42784ce01 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Fri, 15 Jun 2012 15:14:43 +0200 Subject: [PATCH 40/79] Account for line height when avoiding collisions with floats. --- weasyprint/layout/float.py | 23 +++++---- weasyprint/layout/inlines.py | 99 ++++++++++++++++++++++-------------- 2 files changed, 72 insertions(+), 50 deletions(-) diff --git a/weasyprint/layout/float.py b/weasyprint/layout/float.py index f26e4540..fbf57758 100644 --- a/weasyprint/layout/float.py +++ b/weasyprint/layout/float.py @@ -120,18 +120,21 @@ def avoid_collisions(document, box, containing_block, outer=True): box_width = box.margin_width() if outer else box.width while True: + colliding_shapes = [ + shape for shape in excluded_shapes + if (shape.position_y <= position_y < + shape.position_y + shape.margin_height()) + or (shape.position_y < position_y + box.margin_height() <= + shape.position_y + shape.margin_height()) + ] left_bounds = [ shape.position_x + shape.margin_width() - for shape in excluded_shapes - if shape.style.float == 'left' - and (shape.position_y <= position_y < - shape.position_y + shape.margin_height())] + for shape in colliding_shapes + if shape.style.float == 'left'] right_bounds = [ shape.position_x - for shape in excluded_shapes - if shape.style.float == 'right' - and (shape.position_y <= position_y < - shape.position_y + shape.margin_height())] + for shape in colliding_shapes + if shape.style.float == 'right'] # Set the default maximum bounds max_left_bound = containing_block.content_box_x() @@ -149,9 +152,7 @@ def avoid_collisions(document, box, containing_block, outer=True): # The box does not fit here new_positon_y = min( shape.position_y + shape.margin_height() - for shape in excluded_shapes - if (shape.position_y <= position_y < - shape.position_y + shape.margin_height())) + for shape in colliding_shapes) if new_positon_y > position_y: # We can find a solution with a higher position_y position_y = new_positon_y diff --git a/weasyprint/layout/inlines.py b/weasyprint/layout/inlines.py index f75f8de9..5657818b 100644 --- a/weasyprint/layout/inlines.py +++ b/weasyprint/layout/inlines.py @@ -60,58 +60,79 @@ def get_next_linebox(document, linebox, position_y, skip_stack, containing_block, device_size, absolute_boxes, fixed_boxes): """Return ``(line, resume_at)``.""" - linebox.position_y = position_y - linebox.width = inline_preferred_minimum_width( - document, linebox, skip_stack=skip_stack, first_line=True) - position_x, position_y, available_width = avoid_collisions( - document, linebox, containing_block, outer=False) - linebox.position_x = position_x - linebox.position_y = position_y - max_x = position_x + available_width + resolve_percentages(linebox, containing_block) if skip_stack is None: # text-indent only at the start of the first line # Other percentages (margins, width, ...) do not apply. resolve_one_percentage(linebox, 'text_indent', containing_block.width) - position_x += linebox.text_indent + else: + linebox.text_indent = 0 skip_stack = skip_first_whitespace(linebox, skip_stack) if skip_stack == 'continue': return None, None - line_placeholders = [] + linebox.width = inline_preferred_minimum_width( + document, linebox, skip_stack=skip_stack, first_line=True) - resolve_percentages(linebox, containing_block) - line, resume_at, preserved_line_break = split_inline_box( - document, linebox, position_x, max_x, skip_stack, containing_block, - device_size, absolute_boxes, fixed_boxes, line_placeholders) + linebox.height, _ = strut_layout(linebox.style) + linebox.position_y = position_y + position_x, position_y, available_width = avoid_collisions( + document, linebox, containing_block, outer=False) + candidate_height = linebox.height + while 1: + linebox.position_x = position_x + linebox.position_y = position_y + max_x = position_x + available_width + position_x += linebox.text_indent - remove_last_whitespace(document, line) + line_placeholders = [] + line_absolutes = [] + line_fixed = [] - bottom, top = line_box_verticality(line) - last = resume_at is None or preserved_line_break - offset_x = text_align(document, line, available_width, last) - if bottom is None: - # No children at all - line.position_y = position_y - offset_y = 0 - if preserved_line_break: - # Only the strut. - line.baseline = line.margin_top - line.height += line.margin_top + line.margin_bottom + line, resume_at, preserved_line_break = split_inline_box( + document, linebox, position_x, max_x, skip_stack, containing_block, + device_size, line_absolutes, line_fixed, line_placeholders) + + remove_last_whitespace(document, line) + + bottom, top = line_box_verticality(line) + if bottom is None: + # No children at all + offset_y = 0 + if preserved_line_break: + # Only the strut. + line.baseline = line.margin_top + line.height += line.margin_top + line.margin_bottom + else: + line.height = 0 + line.baseline = 0 else: - line.height = 0 - line.baseline = 0 - else: - assert top is not None - line.baseline = -top - line.position_y = top - line.height = bottom - top - offset_y = position_y - top - line.margin_top = 0 - line.margin_bottom = 0 - if offset_x != 0 or offset_y != 0: - # This also translates children - line.translate(offset_x, offset_y) + assert top is not None + line.baseline = -top + line.position_y = top + line.height = bottom - top + offset_y = position_y - top + line.margin_top = 0 + line.margin_bottom = 0 + + offset_x = text_align(document, line, available_width, + last=resume_at is None or preserved_line_break) + if offset_x != 0 or offset_y != 0: + line.translate(offset_x, offset_y) + + if line.height <= candidate_height: + break + candidate_height = line.height + + position_x, position_y, available_width = avoid_collisions( + document, line, containing_block, outer=False) + if (position_x, position_y) == ( + linebox.position_x, linebox.position_y): + break + + absolute_boxes.extend(line_absolutes) + fixed_boxes.extend(line_fixed) for placeholder in line_placeholders: if placeholder.style._weasy_specified_display.startswith('inline'): From 0485e8e777d09afd11e07b7a897e47bfd23a048a Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Fri, 15 Jun 2012 15:22:17 +0200 Subject: [PATCH 41/79] Add some tests for float --- weasyprint/tests/test_layout.py | 96 +++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/weasyprint/tests/test_layout.py b/weasyprint/tests/test_layout.py index b09cb33e..3fc7d098 100644 --- a/weasyprint/tests/test_layout.py +++ b/weasyprint/tests/test_layout.py @@ -46,6 +46,12 @@ def parse(html_content, return_document=False): return document.pages +def outer_area(box): + """Return the (x, y, w, h) rectangle for the outer area of a box.""" + return (box.position_x, box.position_y, + box.margin_width(), box.margin_height()) + + @assert_no_logs def test_page(): """Test the layout for ``@page`` properties.""" @@ -3920,3 +3926,93 @@ def test_absolute_images(): assert (img2.width, img2.height) == (4, 4) # TODO: test the various cases in absolute_replaced() + + +@assert_no_logs +def test_floats(): + # adjacent-floats-001 + page, = parse(''' + +
+
+ ''') + html, = page.children + body, = html.children + div_1, div_2 = body.children + assert outer_area(div_1) == (0, 0, 100, 100) + assert outer_area(div_2) == (100, 0, 100, 100) + + # c414-flt-fit-000 + page, = parse(''' + +
+
+
+ + + ''') + html, = page.children + body, = html.children + div_1, div_2, div_4, anon_block = body.children + line_3, line_5 = anon_block.children + img_3, = line_3.children + img_5, = line_5.children + assert outer_area(div_1) == (0, 0, 100, 60) + assert outer_area(div_2) == (100, 0, 100, 60) + assert outer_area(img_3) == (200, 0, 60, 60) + + assert outer_area(div_4) == (0, 60, 100, 60) + assert outer_area(img_5) == (100, 60, 60, 60) + + # c414-flt-fit-002 + page, = parse(''' + +

⇦ A 1

+

⇦ B 2

+

⇦ A 3

+

B 4 ⇨

+

⇦ A 5

+

B 6 ⇨

+

B 8 ⇨

+

⇦ A 7

+

⇦ A 9

+

⇦ B 10

+ ''') + html, = page.children + body, = html.children + positions = [(paragraph.position_x, paragraph.position_y) + for paragraph in body.children] + assert positions == [ + (0, 0), (70, 0), (0, 20), (130, 20), (0, 40), (130, 40), + (130, 60), (0, 60), (0, 80), (70, 80), ] + + # c414-flt-wrap-000 ... more or less + page, = parse(''' + +

+

+ + ''') + html, = page.children + body, = html.children + p_1, p_2, anon_block = body.children + line_1, line_2 = anon_block.children + assert anon_block.position_y == 0 + assert (line_1.position_x, line_1.position_y) == (20, 0) + assert (line_2.position_x, line_2.position_y) == (0, 200) From f7d765e0c18ab0e4bf4392e81876c112ee3a5801 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Fri, 15 Jun 2012 17:59:15 +0200 Subject: [PATCH 42/79] =?UTF-8?q?Bug=20fixes=20and=20tests=20on=20floats?= =?UTF-8?q?=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- weasyprint/formatting_structure/build.py | 21 +++++------ weasyprint/layout/float.py | 20 +++++------ weasyprint/layout/inlines.py | 39 +++++++++----------- weasyprint/tests/test_boxes.py | 20 ++++------- weasyprint/tests/test_layout.py | 46 ++++++++++++++++++++++++ 5 files changed, 88 insertions(+), 58 deletions(-) diff --git a/weasyprint/formatting_structure/build.py b/weasyprint/formatting_structure/build.py index 6098eaa6..9a2187f1 100644 --- a/weasyprint/formatting_structure/build.py +++ b/weasyprint/formatting_structure/build.py @@ -664,7 +664,8 @@ def inline_in_block(box): assert not isinstance(child_box, boxes.LineBox) if new_line_children and child_box.is_absolutely_positioned(): new_line_children.append(child_box) - elif isinstance(child_box, boxes.InlineLevelBox): + elif isinstance(child_box, boxes.InlineLevelBox) or ( + new_line_children and child_box.is_floated()): # Do not append white space at the start of a line: # It would be removed during layout. if new_line_children or not ( @@ -770,7 +771,8 @@ def block_in_inline(box): 'siblings at this stage, got %r.' % box.children) stack = None while 1: - new_line, block, stack = _inner_block_in_inline(child, stack) + new_line, block, stack = _inner_block_in_inline( + child, floats=new_children, skip_stack=stack) if block is None: break anon = boxes.BlockBox.anonymous_from(box, [new_line]) @@ -798,7 +800,7 @@ def block_in_inline(box): return box -def _inner_block_in_inline(box, skip_stack=None): +def _inner_block_in_inline(box, floats, skip_stack=None): """Find a block-level box in an inline formatting context. If one is found, return ``(new_box, block_level_box, resume_at)``. @@ -829,17 +831,16 @@ def _inner_block_in_inline(box, skip_stack=None): index += 1 # Resume *after* the block else: if isinstance(child, boxes.InlineBox): - recursion = _inner_block_in_inline(child, skip_stack) + recursion = _inner_block_in_inline(child, floats, skip_stack) skip_stack = None new_child, block_level_box, resume_at = recursion + elif child.is_floated(): + floats.append(child) + changed = True + continue else: assert skip_stack is None # Should not skip here - if isinstance(child, boxes.ParentBox): - # inline-block or inline-table. - new_child = block_in_inline(child) - else: - # text or replaced box - new_child = child + new_child = block_in_inline(child) # block_level_box is still None. if new_child is not child: changed = True diff --git a/weasyprint/layout/float.py b/weasyprint/layout/float.py index fbf57758..cdd1ae7e 100644 --- a/weasyprint/layout/float.py +++ b/weasyprint/layout/float.py @@ -25,6 +25,10 @@ def float_width(box, document, containing_block): def float_layout(document, box, containing_block, absolute_boxes, fixed_boxes): """Set the width and position of floating ``box``.""" + # avoid a circular imports + from .blocks import block_container_layout + from .inlines import inline_replaced_box_width_height + resolve_percentages(box, (containing_block.width, containing_block.height)) resolve_position_percentages( box, (containing_block.width, containing_block.height)) @@ -38,18 +42,10 @@ def float_layout(document, box, containing_block, absolute_boxes, fixed_boxes): if clearance: box.position_y += clearance - # avoid a circular import - from .inlines import min_max_replaced_width, min_max_replaced_height - - if box.width == 'auto': - if isinstance(box, boxes.BlockReplacedBox): - min_max_replaced_width(box, None) - min_max_replaced_height(box, None) - else: - float_width(box, document, containing_block) - - # avoid a circular import - from .blocks import block_container_layout + if isinstance(box, boxes.BlockReplacedBox): + inline_replaced_box_width_height(box, device_size=None) + elif box.width == 'auto': + float_width(box, document, containing_block) if box.is_table_wrapper: table_wrapper_width( diff --git a/weasyprint/layout/inlines.py b/weasyprint/layout/inlines.py index 5657818b..ab2787ae 100644 --- a/weasyprint/layout/inlines.py +++ b/weasyprint/layout/inlines.py @@ -169,7 +169,7 @@ def skip_first_whitespace(box, skip_stack): if white_space in ('normal', 'nowrap', 'pre-line'): while index < length and box.text[index] == ' ': index += 1 - return index, None + return (index, None) if index else None if isinstance(box, (boxes.LineBox, boxes.InlineBox)): if index == 0 and not box.children: @@ -180,7 +180,7 @@ def skip_first_whitespace(box, skip_stack): if index >= len(box.children): return 'continue' result = skip_first_whitespace(box.children[index], None) - return index, result + return (index, result) if (index or result) else None assert skip_stack is None, 'unexpected skip inside %s' % box return None @@ -510,13 +510,15 @@ def split_inline_box(document, box, position_x, max_x, skip_stack, containing_block, device_size, absolute_boxes, fixed_boxes, line_placeholders): """Same behavior as split_inline_level.""" + is_start = skip_stack is None initial_position_x = position_x assert isinstance(box, (boxes.LineBox, boxes.InlineBox)) left_spacing = (box.padding_left + box.margin_left + box.border_left_width) right_spacing = (box.padding_right + box.margin_right + box.border_right_width) - position_x += left_spacing + if is_start: + position_x += left_spacing content_box_left = position_x children = [] @@ -525,35 +527,26 @@ def split_inline_box(document, box, position_x, max_x, skip_stack, if box.style.position == 'relative': absolute_boxes = [] - is_start = skip_stack is None if is_start: skip = 0 else: skip, skip_stack = skip_stack for index, child in box.enumerate_skip(skip): child.position_y = box.position_y - if not child.is_in_normal_flow(): + if child.is_absolutely_positioned(): child.position_x = position_x - if child.is_absolutely_positioned(): - placeholder = AbsolutePlaceholder(child) - line_placeholders.append(placeholder) - children.append(placeholder) + placeholder = AbsolutePlaceholder(child) + line_placeholders.append(placeholder) + children.append(placeholder) + absolute_boxes.append(placeholder) + if child.style.position == 'absolute': absolute_boxes.append(placeholder) - if child.style.position == 'absolute': - absolute_boxes.append(placeholder) - else: - fixed_boxes.append(placeholder) - elif child.style.float in ('left', 'right'): - # Set a maximum line width to lay out the float element, it - # will be correctly set after that - if box.width == 'auto': - box.width = ( - containing_block.content_box_x() + - containing_block.width - position_x) - child = float_layout( - document, child, box, absolute_boxes, fixed_boxes) - children.append(child) + else: + fixed_boxes.append(placeholder) continue + # Floats are not supposed to be here, there are moved to the + # top of the containing block during box creation. + assert child.is_in_normal_flow() new_child, resume_at, preserved = split_inline_level( document, child, position_x, max_x, skip_stack, containing_block, diff --git a/weasyprint/tests/test_boxes.py b/weasyprint/tests/test_boxes.py index a35d768b..a3323604 100644 --- a/weasyprint/tests/test_boxes.py +++ b/weasyprint/tests/test_boxes.py @@ -214,26 +214,20 @@ def test_inline_in_block(): box = build.block_in_inline(box) assert_tree(box, expected) - # Floats however stay out of line boxes + # Floats are pull to the top of their containing blocks source = '

Hello World!

' - expected = [ + box = parse(source) + box = build.inline_in_block(box) + box = build.block_in_inline(box) + assert_tree(box, [ ('p', 'Block', [ - ('p', 'AnonBlock', [ - ('p', 'Line', [ - ('p', 'Text', 'Hello ')])]), ('em', 'Block', [ ('em', 'Line', [ ('em', 'Text', 'World')])]), ('p', 'AnonBlock', [ ('p', 'Line', [ - ('p', 'Text', '!')])])])] - box = parse(source) - box = build.inline_in_block(box) - import pprint - pprint.pprint(to_lists(box)) - assert_tree(box, expected) - box = build.block_in_inline(box) - assert_tree(box, expected) + ('p', 'Text', 'Hello '), + ('p', 'Text', '!')])])])]) @assert_no_logs diff --git a/weasyprint/tests/test_layout.py b/weasyprint/tests/test_layout.py index 3fc7d098..47709466 100644 --- a/weasyprint/tests/test_layout.py +++ b/weasyprint/tests/test_layout.py @@ -4016,3 +4016,49 @@ def test_floats(): assert anon_block.position_y == 0 assert (line_1.position_x, line_1.position_y) == (20, 0) assert (line_2.position_x, line_2.position_y) == (0, 200) + + # floats-placement-vertical-001b + page, = parse(''' + + + + + + + + ''') + html, = page.children + body, = html.children + img_3, anon_block = body.children + line_1, line_2 = anon_block.children + span_1, = line_1.children + span_2, = line_2.children + img_1, = span_1.children + img_2, = span_2.children + assert outer_area(img_3) == (0, 0, 30, 30) + assert outer_area(img_1) == (30, 0, 50, 50) + assert outer_area(img_2) == (0, 50, 50, 50) + + # Variant of the above: no + page, = parse(''' + + + + + + ''') + html, = page.children + body, = html.children + img_3, anon_block = body.children + line_1, line_2 = anon_block.children + img_1, = line_1.children + img_2, = line_2.children + assert outer_area(img_3) == (0, 0, 30, 30) + assert outer_area(img_1) == (30, 0, 50, 50) + assert outer_area(img_2) == (0, 50, 50, 50) From e4f9ff5331cd60de2a85ebfb6fb09bf65040049d Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Fri, 15 Jun 2012 18:49:40 +0200 Subject: [PATCH 43/79] Don't collapse through clear boxes --- weasyprint/layout/blocks.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/weasyprint/layout/blocks.py b/weasyprint/layout/blocks.py index 73440bfd..7a0ce671 100644 --- a/weasyprint/layout/blocks.py +++ b/weasyprint/layout/blocks.py @@ -408,7 +408,8 @@ def block_container_layout(document, box, max_position_y, skip_stack, adjoining_margins = [] else: # top and bottom margin of this box - if box.height in ('auto', 0) and all(v == 0 for v in [ + if box.height in ('auto', 0) and box.style.clear == 'none' and all( + v == 0 for v in [ box.min_height, box.border_top_width, box.padding_top, box.border_bottom_width, box.padding_bottom]): collapsing_through = True From d62ba4936ab7e0518ed20ec87e339ea1c3b1f67b Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Mon, 18 Jun 2012 10:06:18 +0200 Subject: [PATCH 44/79] Don't collapse through boxes with clearance --- weasyprint/layout/blocks.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/weasyprint/layout/blocks.py b/weasyprint/layout/blocks.py index 7a0ce671..e65acc81 100644 --- a/weasyprint/layout/blocks.py +++ b/weasyprint/layout/blocks.py @@ -407,14 +407,16 @@ def block_container_layout(document, box, max_position_y, skip_stack, # not adjoining. (position_y is not used afterwards.) adjoining_margins = [] else: + collapsed_margin = collapse_margin(adjoining_margins) # top and bottom margin of this box - if box.height in ('auto', 0) and box.style.clear == 'none' and all( - v == 0 for v in [ + if (box.height in ('auto', 0) and + not get_clearance(document, box, collapsed_margin) and + all(v == 0 for v in [ box.min_height, box.border_top_width, box.padding_top, - box.border_bottom_width, box.padding_bottom]): + box.border_bottom_width, box.padding_bottom])): collapsing_through = True else: - position_y += collapse_margin(adjoining_margins) + position_y += collapsed_margin adjoining_margins = [] if box.border_bottom_width or box.padding_bottom or ( From 8a94ff2f9bcb39052e006922a0c1ecb0ddbb81bf Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Mon, 18 Jun 2012 10:44:54 +0200 Subject: [PATCH 45/79] Strip spaces at the beginning of text fragments to get real line widths --- weasyprint/layout/preferred.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/weasyprint/layout/preferred.py b/weasyprint/layout/preferred.py index c98c608f..9f2057a5 100644 --- a/weasyprint/layout/preferred.py +++ b/weasyprint/layout/preferred.py @@ -404,7 +404,10 @@ def table_preferred_width(document, box, outer=True): def text_lines_width(document, box, width, skip=None): """Return the list of line widths for a ``TextBox``.""" context = cairo.Context(document.surface) - fragment = TextFragment(box.text[skip:], box.style, context, width) + # TODO: without the lstrip, we get an extra empty line at the beginning. Is + # there a better solution to avoid that? + fragment = TextFragment( + box.text[skip:].lstrip(), box.style, context, width) return fragment.line_widths() From b3e3581f484c5444a73d95885d4617e0ecb39d8e Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Mon, 18 Jun 2012 18:04:20 +0200 Subject: [PATCH 46/79] Remove useless import --- weasyprint/layout/inlines.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weasyprint/layout/inlines.py b/weasyprint/layout/inlines.py index ab2787ae..d38c4cad 100644 --- a/weasyprint/layout/inlines.py +++ b/weasyprint/layout/inlines.py @@ -15,7 +15,7 @@ from __future__ import division, unicode_literals import cairo from .absolute import absolute_layout, AbsolutePlaceholder -from .float import avoid_collisions, float_layout +from .float import avoid_collisions from .markers import image_marker_layout from .min_max import handle_min_max_width, handle_min_max_height from .percentages import resolve_percentages, resolve_one_percentage From ef76204e992a198a0943a835043b923d48dff653 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Mon, 18 Jun 2012 19:31:24 +0200 Subject: [PATCH 47/79] Fix clearance and margin collapsing --- weasyprint/layout/blocks.py | 2 +- weasyprint/layout/float.py | 19 ++++++++++++------- weasyprint/layout/inlines.py | 4 ++-- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/weasyprint/layout/blocks.py b/weasyprint/layout/blocks.py index e65acc81..d3c28148 100644 --- a/weasyprint/layout/blocks.py +++ b/weasyprint/layout/blocks.py @@ -47,7 +47,7 @@ def block_level_layout(document, box, max_position_y, skip_stack, collapsed_margin = collapse_margin(adjoining_margins + [box.margin_top]) clearance = get_clearance(document, box, collapsed_margin) - if clearance: + if clearance is not None: top_border_edge = box.position_y + collapsed_margin + clearance box.position_y = top_border_edge - box.margin_top adjoining_margins = [] diff --git a/weasyprint/layout/float.py b/weasyprint/layout/float.py index cdd1ae7e..4bddcd69 100644 --- a/weasyprint/layout/float.py +++ b/weasyprint/layout/float.py @@ -39,7 +39,7 @@ def float_layout(document, box, containing_block, absolute_boxes, fixed_boxes): box.margin_right = 0 clearance = get_clearance(document, box) - if clearance: + if clearance is not None: box.position_y += clearance if isinstance(box, boxes.BlockReplacedBox): @@ -86,7 +86,7 @@ def find_float_position(document, box, containing_block): # Points 1 and 2 position_x, position_y, available_width = avoid_collisions( - document, box, containing_block) + document, box, containing_block, outer_height=False) # Point 9 # position_y is set now, let's define position_x @@ -100,27 +100,32 @@ def find_float_position(document, box, containing_block): def get_clearance(document, box, collapsed_margin=0): - clearance = 0 + """Return None if there is no clearance, otherwise the clearance value.""" + clearance = None hypothetical_position = box.position_y + collapsed_margin for excluded_shape in document.excluded_shapes: if box.style.clear in (excluded_shape.style.float, 'both'): y, h = excluded_shape.position_y, excluded_shape.margin_height() - clearance = max(clearance, y + h - hypothetical_position) + if hypothetical_position < y + h: + clearance = max( + (clearance or 0), y + h - hypothetical_position) return clearance -def avoid_collisions(document, box, containing_block, outer=True): +def avoid_collisions(document, box, containing_block, outer_width=True, + outer_height=True): excluded_shapes = document.excluded_shapes position_y = box.position_y - box_width = box.margin_width() if outer else box.width + box_width = box.margin_width() if outer_width else box.width + box_height = box.margin_height() if outer_height else box.height while True: colliding_shapes = [ shape for shape in excluded_shapes if (shape.position_y <= position_y < shape.position_y + shape.margin_height()) - or (shape.position_y < position_y + box.margin_height() <= + or (shape.position_y < position_y + box_height <= shape.position_y + shape.margin_height()) ] left_bounds = [ diff --git a/weasyprint/layout/inlines.py b/weasyprint/layout/inlines.py index d38c4cad..56a5517a 100644 --- a/weasyprint/layout/inlines.py +++ b/weasyprint/layout/inlines.py @@ -78,7 +78,7 @@ def get_next_linebox(document, linebox, position_y, skip_stack, linebox.height, _ = strut_layout(linebox.style) linebox.position_y = position_y position_x, position_y, available_width = avoid_collisions( - document, linebox, containing_block, outer=False) + document, linebox, containing_block, outer_width=False) candidate_height = linebox.height while 1: linebox.position_x = position_x @@ -126,7 +126,7 @@ def get_next_linebox(document, linebox, position_y, skip_stack, candidate_height = line.height position_x, position_y, available_width = avoid_collisions( - document, line, containing_block, outer=False) + document, line, containing_block, outer_width=False) if (position_x, position_y) == ( linebox.position_x, linebox.position_y): break From 9cf9e53d0bbb16b612863c1a00e4db233458aa66 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Mon, 18 Jun 2012 19:34:35 +0200 Subject: [PATCH 48/79] Fix a little bug with clearance --- weasyprint/layout/blocks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weasyprint/layout/blocks.py b/weasyprint/layout/blocks.py index d3c28148..b041eaca 100644 --- a/weasyprint/layout/blocks.py +++ b/weasyprint/layout/blocks.py @@ -410,7 +410,7 @@ def block_container_layout(document, box, max_position_y, skip_stack, collapsed_margin = collapse_margin(adjoining_margins) # top and bottom margin of this box if (box.height in ('auto', 0) and - not get_clearance(document, box, collapsed_margin) and + get_clearance(document, box, collapsed_margin) is None and all(v == 0 for v in [ box.min_height, box.border_top_width, box.padding_top, box.border_bottom_width, box.padding_bottom])): From f441f8c81085444e3abf07fbbd843383c4f93233 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Tue, 19 Jun 2012 00:00:28 +0200 Subject: [PATCH 49/79] Increase position_y after clear blocks --- weasyprint/layout/blocks.py | 6 +++++- weasyprint/layout/float.py | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/weasyprint/layout/blocks.py b/weasyprint/layout/blocks.py index b041eaca..837c4d73 100644 --- a/weasyprint/layout/blocks.py +++ b/weasyprint/layout/blocks.py @@ -256,7 +256,7 @@ def block_container_layout(document, box, max_position_y, skip_stack, absolute_boxes.append(placeholder) else: fixed_boxes.append(placeholder) - elif child.style.float in ('left', 'right'): + elif child.is_floated(): child = float_layout( document, child, box, absolute_boxes, fixed_boxes) new_children.append(child) @@ -371,6 +371,10 @@ def block_container_layout(document, box, max_position_y, skip_stack, else: position_y = new_position_y + if new_child.style.clear != 'none': + position_y = ( + new_child.border_box_y() + new_child.border_height()) + if new_child is None: if new_children: resume_at = (index, None) diff --git a/weasyprint/layout/float.py b/weasyprint/layout/float.py index 4bddcd69..48f318e7 100644 --- a/weasyprint/layout/float.py +++ b/weasyprint/layout/float.py @@ -76,7 +76,6 @@ def find_float_position(document, box, containing_block): # Point 4 is already handled as box.position_y is set according to the # containing box top position, with collapsing margins handled - # TODO: are the collapsing margins *really* handled? # Points 5 and 6, box.position_y is set to the highest position_y possible if document.excluded_shapes: From 4fe56316f65398fd5b590c089669b86ef68665d2 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Tue, 19 Jun 2012 14:52:30 +0200 Subject: [PATCH 50/79] Finally fix margin collapsing for blocks with clearance --- weasyprint/formatting_structure/boxes.py | 1 + weasyprint/layout/blocks.py | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/weasyprint/formatting_structure/boxes.py b/weasyprint/formatting_structure/boxes.py index 877407c9..dabc0f9a 100644 --- a/weasyprint/formatting_structure/boxes.py +++ b/weasyprint/formatting_structure/boxes.py @@ -284,6 +284,7 @@ class BlockLevelBox(Box): ``table`` generates a block-level box. """ + clearance = None class BlockContainerBox(ParentBox): diff --git a/weasyprint/layout/blocks.py b/weasyprint/layout/blocks.py index 837c4d73..c282febc 100644 --- a/weasyprint/layout/blocks.py +++ b/weasyprint/layout/blocks.py @@ -46,9 +46,9 @@ def block_level_layout(document, box, max_position_y, skip_stack, box.margin_bottom = 0 collapsed_margin = collapse_margin(adjoining_margins + [box.margin_top]) - clearance = get_clearance(document, box, collapsed_margin) - if clearance is not None: - top_border_edge = box.position_y + collapsed_margin + clearance + box.clearance = get_clearance(document, box, collapsed_margin) + if box.clearance is not None: + top_border_edge = box.position_y + collapsed_margin + box.clearance box.position_y = top_border_edge - box.margin_top adjoining_margins = [] @@ -371,7 +371,7 @@ def block_container_layout(document, box, max_position_y, skip_stack, else: position_y = new_position_y - if new_child.style.clear != 'none': + if new_child.clearance is not None: position_y = ( new_child.border_box_y() + new_child.border_height()) From c1e495bee13175a4fcac04707f986aee6ab87cca Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Wed, 20 Jun 2012 14:13:25 +0200 Subject: [PATCH 51/79] Add a todo about float blocks and collapse margins --- weasyprint/layout/blocks.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/weasyprint/layout/blocks.py b/weasyprint/layout/blocks.py index 4c1af3da..a2e5d287 100644 --- a/weasyprint/layout/blocks.py +++ b/weasyprint/layout/blocks.py @@ -430,6 +430,8 @@ def block_container_layout(document, box, max_position_y, skip_stack, border_box_y = box.position_y + collapse_margin( this_box_adjoining_margins) box.position_y = border_box_y - box.margin_top + # TODO: move the float boxes inside box too + # See W3C test margin-collapse-clear-004 collapsing_through = False if new_children: From e12880b11305ac8618ac34105fd6a29735d01834 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Thu, 21 Jun 2012 16:03:29 +0200 Subject: [PATCH 52/79] Handle float in inline as almost normal boxes --- weasyprint/formatting_structure/build.py | 10 ++----- weasyprint/layout/inlines.py | 35 ++++++++++++++++-------- weasyprint/layout/preferred.py | 4 +-- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/weasyprint/formatting_structure/build.py b/weasyprint/formatting_structure/build.py index 9a2187f1..bf22f64d 100644 --- a/weasyprint/formatting_structure/build.py +++ b/weasyprint/formatting_structure/build.py @@ -772,7 +772,7 @@ def block_in_inline(box): stack = None while 1: new_line, block, stack = _inner_block_in_inline( - child, floats=new_children, skip_stack=stack) + child, skip_stack=stack) if block is None: break anon = boxes.BlockBox.anonymous_from(box, [new_line]) @@ -800,7 +800,7 @@ def block_in_inline(box): return box -def _inner_block_in_inline(box, floats, skip_stack=None): +def _inner_block_in_inline(box, skip_stack=None): """Find a block-level box in an inline formatting context. If one is found, return ``(new_box, block_level_box, resume_at)``. @@ -831,13 +831,9 @@ def _inner_block_in_inline(box, floats, skip_stack=None): index += 1 # Resume *after* the block else: if isinstance(child, boxes.InlineBox): - recursion = _inner_block_in_inline(child, floats, skip_stack) + recursion = _inner_block_in_inline(child, skip_stack) skip_stack = None new_child, block_level_box, resume_at = recursion - elif child.is_floated(): - floats.append(child) - changed = True - continue else: assert skip_stack is None # Should not skip here new_child = block_in_inline(child) diff --git a/weasyprint/layout/inlines.py b/weasyprint/layout/inlines.py index 56a5517a..3b239b0e 100644 --- a/weasyprint/layout/inlines.py +++ b/weasyprint/layout/inlines.py @@ -15,7 +15,7 @@ from __future__ import division, unicode_literals import cairo from .absolute import absolute_layout, AbsolutePlaceholder -from .float import avoid_collisions +from .float import avoid_collisions, float_layout from .markers import image_marker_layout from .min_max import handle_min_max_width, handle_min_max_height from .percentages import resolve_percentages, resolve_one_percentage @@ -128,7 +128,7 @@ def get_next_linebox(document, linebox, position_y, skip_stack, position_x, position_y, available_width = avoid_collisions( document, line, containing_block, outer_width=False) if (position_x, position_y) == ( - linebox.position_x, linebox.position_y): + linebox.position_x, linebox.position_y): break absolute_boxes.extend(line_absolutes) @@ -544,9 +544,12 @@ def split_inline_box(document, box, position_x, max_x, skip_stack, else: fixed_boxes.append(placeholder) continue - # Floats are not supposed to be here, there are moved to the - # top of the containing block during box creation. - assert child.is_in_normal_flow() + elif child.is_floated(): + child.position_x = position_x + child = float_layout( + document, child, box, absolute_boxes, fixed_boxes) + children.append(child) + continue new_child, resume_at, preserved = split_inline_level( document, child, position_x, max_x, skip_stack, containing_block, @@ -703,20 +706,28 @@ def line_box_verticality(box): max_y, min_y = aligned_subtree_verticality( box, top_bottom_subtrees, baseline_y=0) for subtree in top_bottom_subtrees: - sub_max_y, sub_min_y = aligned_subtree_verticality( - subtree, top_bottom_subtrees, baseline_y=0) + if subtree.is_floated(): + sub_min_y = None + sub_max_y = None + else: + sub_max_y, sub_min_y = aligned_subtree_verticality( + subtree, top_bottom_subtrees, baseline_y=0) subtrees_with_min_max.append( (subtree, sub_max_y, sub_min_y)) if subtrees_with_min_max: - highest_sub = max( + sub_positions = [ sub_max_y - sub_min_y for subtree, sub_max_y, sub_min_y in subtrees_with_min_max - ) - max_y = max(max_y, min_y + highest_sub) + if not subtree.is_floated()] + if sub_positions: + highest_sub = max(sub_positions) + max_y = max(max_y, min_y + highest_sub) for subtree, sub_max_y, sub_min_y in subtrees_with_min_max: - if subtree.style.vertical_align == 'top': + if subtree.is_floated(): + dy = min_y - subtree.position_y + elif subtree.style.vertical_align == 'top': dy = min_y - sub_min_y else: assert subtree.style.vertical_align == 'bottom' @@ -767,6 +778,8 @@ def inline_box_verticality(box, top_bottom_subtrees, baseline_y): for child in box.children: if not child.is_in_normal_flow(): + if child.is_floated(): + top_bottom_subtrees.append(child) continue vertical_align = child.style.vertical_align if vertical_align == 'baseline': diff --git a/weasyprint/layout/preferred.py b/weasyprint/layout/preferred.py index 9f2057a5..04409e05 100644 --- a/weasyprint/layout/preferred.py +++ b/weasyprint/layout/preferred.py @@ -157,7 +157,7 @@ def inline_preferred_minimum_width(document, box, outer=True, skip_stack=None, else: skip, skip_stack = skip_stack for index, child in box.enumerate_skip(skip): - if child.is_absolutely_positioned(): + if not child.is_in_normal_flow(): continue # Skip if isinstance(child, boxes.InlineReplacedBox): @@ -188,7 +188,7 @@ def inline_preferred_width(document, box, outer=True): widest_line = 0 current_line = 0 for child in box.children: - if child.is_absolutely_positioned(): + if not child.is_in_normal_flow(): continue # Skip if isinstance(child, boxes.InlineReplacedBox): From a78c3d274904005d8c45d9aff1bcc29a3ed84938 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Thu, 21 Jun 2012 18:12:17 +0200 Subject: [PATCH 53/79] Small fixes about floats --- weasyprint/layout/inlines.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/weasyprint/layout/inlines.py b/weasyprint/layout/inlines.py index 3b239b0e..df32c692 100644 --- a/weasyprint/layout/inlines.py +++ b/weasyprint/layout/inlines.py @@ -80,6 +80,9 @@ def get_next_linebox(document, linebox, position_y, skip_stack, position_x, position_y, available_width = avoid_collisions( document, linebox, containing_block, outer_width=False) candidate_height = linebox.height + + excluded_shapes = document.excluded_shapes[:] + while 1: linebox.position_x = position_x linebox.position_y = position_y @@ -125,10 +128,13 @@ def get_next_linebox(document, linebox, position_y, skip_stack, break candidate_height = line.height + new_excluded_shapes = document.excluded_shapes + document.excluded_shapes = excluded_shapes position_x, position_y, available_width = avoid_collisions( document, line, containing_block, outer_width=False) if (position_x, position_y) == ( linebox.position_x, linebox.position_y): + document.excluded_shapes = new_excluded_shapes break absolute_boxes.extend(line_absolutes) @@ -531,6 +537,7 @@ def split_inline_box(document, box, position_x, max_x, skip_stack, skip = 0 else: skip, skip_stack = skip_stack + for index, child in box.enumerate_skip(skip): child.position_y = box.position_y if child.is_absolutely_positioned(): @@ -547,7 +554,7 @@ def split_inline_box(document, box, position_x, max_x, skip_stack, elif child.is_floated(): child.position_x = position_x child = float_layout( - document, child, box, absolute_boxes, fixed_boxes) + document, child, containing_block, absolute_boxes, fixed_boxes) children.append(child) continue From ab5ecd834645a385cf320bcdec2afecdfbef824a Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Fri, 22 Jun 2012 11:40:15 +0200 Subject: [PATCH 54/79] Fix some tests --- weasyprint/tests/test_boxes.py | 13 ++++++------- weasyprint/text.py | 10 +++++++--- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/weasyprint/tests/test_boxes.py b/weasyprint/tests/test_boxes.py index a3323604..f4a32d55 100644 --- a/weasyprint/tests/test_boxes.py +++ b/weasyprint/tests/test_boxes.py @@ -221,13 +221,12 @@ def test_inline_in_block(): box = build.block_in_inline(box) assert_tree(box, [ ('p', 'Block', [ - ('em', 'Block', [ - ('em', 'Line', [ - ('em', 'Text', 'World')])]), - ('p', 'AnonBlock', [ - ('p', 'Line', [ - ('p', 'Text', 'Hello '), - ('p', 'Text', '!')])])])]) + ('p', 'Line', [ + ('p', 'Text', 'Hello '), + ('em', 'Block', [ + ('em', 'Line', [ + ('em', 'Text', 'World')])]), + ('p', 'Text', '!')])])]) @assert_no_logs diff --git a/weasyprint/text.py b/weasyprint/text.py index 4c5b9c59..3259063e 100644 --- a/weasyprint/text.py +++ b/weasyprint/text.py @@ -114,9 +114,13 @@ def show_first_line(cairo_context, pango_layout, hinting): PangoCairo.show_layout_line(cairo_context, lines[0]) -def line_widths(document, box, width, skip=0): +def line_widths(document, box, width, skip=None): """Return the width for each line.""" - layout = create_layout(box.text, box.style, document.enable_hinting, width) - for line in layout.get_lines_readonly()[skip:]: + # TODO: without the lstrip, we get an extra empty line at the beginning. Is + # there a better solution to avoid that? + layout = create_layout( + box.text[(skip or 0):].lstrip(), box.style, + document.enable_hinting, width) + for line in layout.get_lines_readonly(): _ink_extents, logical_extents = line.get_extents() yield Pango.units_to_double(logical_extents.width) From e696d2b1962b2e64093b80b80ea30497a02f3b57 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Sat, 23 Jun 2012 11:47:38 +0200 Subject: [PATCH 55/79] Translate children moved by floats (but no available_width shrink yet) --- weasyprint/layout/inlines.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/weasyprint/layout/inlines.py b/weasyprint/layout/inlines.py index d59ed119..dd9340da 100644 --- a/weasyprint/layout/inlines.py +++ b/weasyprint/layout/inlines.py @@ -554,6 +554,13 @@ def split_inline_box(document, box, position_x, max_x, skip_stack, child = float_layout( document, child, containing_block, absolute_boxes, fixed_boxes) children.append(child) + for old_child in children[:index]: + if child.style.float == 'left' and box.style.text_align in ( + '-weasy-start', 'left', 'justify'): + old_child.translate(dx=child.margin_width()) + elif child.style.float == 'right' and box.style.text_align in ( + '-weasy-end', 'right', 'justify'): + old_child.translate(dx=-child.margin_width()) continue new_child, resume_at, preserved = split_inline_level( From aee82fd576e261f7bb7268ab27f65cfbfe32648d Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Sat, 23 Jun 2012 13:05:08 +0200 Subject: [PATCH 56/79] Add a todo and fix a little thing about text align for floats in inline --- weasyprint/layout/inlines.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/weasyprint/layout/inlines.py b/weasyprint/layout/inlines.py index c0604035..188e6a10 100644 --- a/weasyprint/layout/inlines.py +++ b/weasyprint/layout/inlines.py @@ -552,12 +552,13 @@ def split_inline_box(document, box, position_x, max_x, skip_stack, child = float_layout( document, child, containing_block, absolute_boxes, fixed_boxes) children.append(child) + # TODO: take the main text direction instead of text align for old_child in children[:index]: if child.style.float == 'left' and box.style.text_align in ( '-weasy-start', 'left', 'justify'): old_child.translate(dx=child.margin_width()) elif child.style.float == 'right' and box.style.text_align in ( - '-weasy-end', 'right', 'justify'): + '-weasy-end', 'right'): old_child.translate(dx=-child.margin_width()) continue From b67bebfb9244a40b8915d944a1338839d565d44e Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Sun, 24 Jun 2012 00:03:45 +0200 Subject: [PATCH 57/79] Fix the position of line children next to float boxes --- weasyprint/layout/inlines.py | 16 ++++++++++------ weasyprint/tests/test_layout.py | 6 ++---- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/weasyprint/layout/inlines.py b/weasyprint/layout/inlines.py index 188e6a10..c49b671f 100644 --- a/weasyprint/layout/inlines.py +++ b/weasyprint/layout/inlines.py @@ -552,14 +552,18 @@ def split_inline_box(document, box, position_x, max_x, skip_stack, child = float_layout( document, child, containing_block, absolute_boxes, fixed_boxes) children.append(child) - # TODO: take the main text direction instead of text align + # TODO: use the main text direction of the line for old_child in children[:index]: - if child.style.float == 'left' and box.style.text_align in ( - '-weasy-start', 'left', 'justify'): + if not old_child.is_in_normal_flow(): + continue + if child.style.float == 'left': # and direction is ltr old_child.translate(dx=child.margin_width()) - elif child.style.float == 'right' and box.style.text_align in ( - '-weasy-end', 'right'): - old_child.translate(dx=-child.margin_width()) + # elif child.style.float == 'right' and direction is rtl: + # old_child.translate(dx=-child.margin_width()) + if child.style.float == 'left': + position_x += child.margin_width() + elif child.style.float == 'right': + max_x -= child.margin_width() continue new_child, resume_at, preserved = split_inline_level( diff --git a/weasyprint/tests/test_layout.py b/weasyprint/tests/test_layout.py index 02b8e2ef..055ef346 100644 --- a/weasyprint/tests/test_layout.py +++ b/weasyprint/tests/test_layout.py @@ -4205,8 +4205,7 @@ def test_floats(): img_1, = span_1.children img_2, img_3 = span_2.children assert outer_area(img_1) == (0, 0, 50, 50) - # TODO: fix that - #assert outer_area(img_2) == (30, 50, 50, 50) + assert outer_area(img_2) == (30, 50, 50, 50) assert outer_area(img_3) == (0, 50, 30, 30) # Variant of the above: no @@ -4226,6 +4225,5 @@ def test_floats(): img_1, = line_1.children img_2, img_3 = line_2.children assert outer_area(img_1) == (0, 0, 50, 50) - # TODO: fix that - #assert outer_area(img_2) == (0, 50, 50, 50) + assert outer_area(img_2) == (30, 50, 50, 50) assert outer_area(img_3) == (0, 50, 30, 30) From b61f57b97ccd9d965fe1feef82b12bd50cabe5e8 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Mon, 25 Jun 2012 10:17:39 +0200 Subject: [PATCH 58/79] Avoid collision of replaced blocks and tables with floats --- weasyprint/layout/blocks.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/weasyprint/layout/blocks.py b/weasyprint/layout/blocks.py index 48fbfdd7..29ff6b01 100644 --- a/weasyprint/layout/blocks.py +++ b/weasyprint/layout/blocks.py @@ -13,7 +13,7 @@ from __future__ import division, unicode_literals from .absolute import absolute_layout, AbsolutePlaceholder -from .float import float_layout, get_clearance +from .float import float_layout, get_clearance, avoid_collisions from .inlines import (iter_line_boxes, replaced_box_width, replaced_box_height, min_max_replaced_height, min_max_auto_replaced) from .markers import list_marker_layout @@ -58,6 +58,10 @@ def block_level_layout(document, box, max_position_y, skip_stack, containing_block, device_size, page_is_empty, absolute_boxes, fixed_boxes, adjoining_margins) elif isinstance(box, boxes.BlockReplacedBox): + # Don't collide with floats + # http://www.w3.org/TR/CSS21/visuren.html#floats + box.position_x, box.position_y, _ = avoid_collisions( + document, box, containing_block) box = block_replaced_box_layout(box, containing_block, device_size) resume_at = None next_page = 'any' @@ -75,12 +79,21 @@ def block_box_layout(document, box, max_position_y, skip_stack, if box.is_table_wrapper: table_wrapper_width( document, box, (containing_block.width, containing_block.height)) - block_level_width(box, containing_block) + else: + block_level_width(box, containing_block) new_box, resume_at, next_page, adjoining_margins, collapsing_through = \ block_container_layout( document, box, max_position_y, skip_stack, device_size, page_is_empty, absolute_boxes, fixed_boxes, adjoining_margins) + if new_box.is_table_wrapper: + # Don't collide with floats + # http://www.w3.org/TR/CSS21/visuren.html#floats + position_x, position_y, _ = avoid_collisions( + document, new_box, containing_block) + new_box.translate( + position_x - new_box.position_x, position_y - new_box.position_y) + block_level_width(new_box, containing_block) list_marker_layout(document, new_box) return new_box, resume_at, next_page, adjoining_margins, collapsing_through From 3b740f32a64c813b43298fa2d57f043d090a4c58 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Mon, 25 Jun 2012 10:21:42 +0200 Subject: [PATCH 59/79] Fix stupid bug --- weasyprint/layout/blocks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weasyprint/layout/blocks.py b/weasyprint/layout/blocks.py index 29ff6b01..3a51ae65 100644 --- a/weasyprint/layout/blocks.py +++ b/weasyprint/layout/blocks.py @@ -86,7 +86,7 @@ def block_box_layout(document, box, max_position_y, skip_stack, block_container_layout( document, box, max_position_y, skip_stack, device_size, page_is_empty, absolute_boxes, fixed_boxes, adjoining_margins) - if new_box.is_table_wrapper: + if new_box and new_box.is_table_wrapper: # Don't collide with floats # http://www.w3.org/TR/CSS21/visuren.html#floats position_x, position_y, _ = avoid_collisions( From 683afe9f3a8f053bedeb7b5e8d53dc3e09b61b5d Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Mon, 25 Jun 2012 12:47:15 +0200 Subject: [PATCH 60/79] Fix tests --- weasyprint/layout/blocks.py | 10 ++++------ weasyprint/layout/float.py | 34 +++++++++++++++++++++++++--------- weasyprint/layout/inlines.py | 4 ++-- 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/weasyprint/layout/blocks.py b/weasyprint/layout/blocks.py index 3a51ae65..2bc399d3 100644 --- a/weasyprint/layout/blocks.py +++ b/weasyprint/layout/blocks.py @@ -58,11 +58,11 @@ def block_level_layout(document, box, max_position_y, skip_stack, containing_block, device_size, page_is_empty, absolute_boxes, fixed_boxes, adjoining_margins) elif isinstance(box, boxes.BlockReplacedBox): + box = block_replaced_box_layout(box, containing_block, device_size) # Don't collide with floats # http://www.w3.org/TR/CSS21/visuren.html#floats box.position_x, box.position_y, _ = avoid_collisions( - document, box, containing_block) - box = block_replaced_box_layout(box, containing_block, device_size) + document, box, containing_block, outer=False) resume_at = None next_page = 'any' adjoining_margins = [] @@ -79,8 +79,7 @@ def block_box_layout(document, box, max_position_y, skip_stack, if box.is_table_wrapper: table_wrapper_width( document, box, (containing_block.width, containing_block.height)) - else: - block_level_width(box, containing_block) + block_level_width(box, containing_block) new_box, resume_at, next_page, adjoining_margins, collapsing_through = \ block_container_layout( @@ -90,10 +89,9 @@ def block_box_layout(document, box, max_position_y, skip_stack, # Don't collide with floats # http://www.w3.org/TR/CSS21/visuren.html#floats position_x, position_y, _ = avoid_collisions( - document, new_box, containing_block) + document, new_box, containing_block, outer=False) new_box.translate( position_x - new_box.position_x, position_y - new_box.position_y) - block_level_width(new_box, containing_block) list_marker_layout(document, new_box) return new_box, resume_at, next_page, adjoining_margins, collapsing_through diff --git a/weasyprint/layout/float.py b/weasyprint/layout/float.py index 48f318e7..8fc89366 100644 --- a/weasyprint/layout/float.py +++ b/weasyprint/layout/float.py @@ -37,6 +37,10 @@ def float_layout(document, box, containing_block, absolute_boxes, fixed_boxes): box.margin_left = 0 if box.margin_right == 'auto': box.margin_right = 0 + if box.margin_top == 'auto': + box.margin_top = 0 + if box.margin_bottom == 'auto': + box.margin_bottom = 0 clearance = get_clearance(document, box) if clearance is not None: @@ -85,13 +89,13 @@ def find_float_position(document, box, containing_block): # Points 1 and 2 position_x, position_y, available_width = avoid_collisions( - document, box, containing_block, outer_height=False) + document, box, containing_block) # Point 9 # position_y is set now, let's define position_x # for float: left elements, it's already done! if box.style.float == 'right': - position_x = position_x + available_width - box.margin_width() + position_x += available_width - box.margin_width() box.translate(position_x - box.position_x, position_y - box.position_y) @@ -111,21 +115,23 @@ def get_clearance(document, box, collapsed_margin=0): return clearance -def avoid_collisions(document, box, containing_block, outer_width=True, - outer_height=True): +def avoid_collisions(document, box, containing_block, outer=True): excluded_shapes = document.excluded_shapes - position_y = box.position_y + position_y = box.position_y if outer else box.border_box_y() - box_width = box.margin_width() if outer_width else box.width - box_height = box.margin_height() if outer_height else box.height + box_width = box.margin_width() if outer else box.border_width() + box_height = box.margin_height() if outer else box.border_height() while True: colliding_shapes = [ shape for shape in excluded_shapes - if (shape.position_y <= position_y < + if (shape.position_y < position_y < shape.position_y + shape.margin_height()) - or (shape.position_y < position_y + box_height <= + or (shape.position_y < position_y + box_height < shape.position_y + shape.margin_height()) + or (shape.position_y >= position_y and + shape.position_y + shape.margin_height() <= + position_y + box_height) ] left_bounds = [ shape.position_x + shape.margin_width() @@ -141,6 +147,10 @@ def avoid_collisions(document, box, containing_block, outer_width=True, max_right_bound = \ containing_block.content_box_x() + containing_block.width + if not outer: + max_left_bound += box.margin_left + max_right_bound += box.margin_right + # Set the real maximum bounds according to sibling float elements if left_bounds or right_bounds: if left_bounds: @@ -162,5 +172,11 @@ def avoid_collisions(document, box, containing_block, outer_width=True, position_x = max_left_bound available_width = max_right_bound - max_left_bound + if box.style.float == 'right': + print(colliding_shapes, position_x, position_y, available_width) + + if not outer: + position_x -= box.margin_left + position_y -= box.margin_top return position_x, position_y, available_width diff --git a/weasyprint/layout/inlines.py b/weasyprint/layout/inlines.py index c49b671f..6b8cede8 100644 --- a/weasyprint/layout/inlines.py +++ b/weasyprint/layout/inlines.py @@ -76,7 +76,7 @@ def get_next_linebox(document, linebox, position_y, skip_stack, linebox.height, _ = strut_layout(linebox.style) linebox.position_y = position_y position_x, position_y, available_width = avoid_collisions( - document, linebox, containing_block, outer_width=False) + document, linebox, containing_block, outer=False) candidate_height = linebox.height excluded_shapes = document.excluded_shapes[:] @@ -129,7 +129,7 @@ def get_next_linebox(document, linebox, position_y, skip_stack, new_excluded_shapes = document.excluded_shapes document.excluded_shapes = excluded_shapes position_x, position_y, available_width = avoid_collisions( - document, line, containing_block, outer_width=False) + document, line, containing_block, outer=False) if (position_x, position_y) == ( linebox.position_x, linebox.position_y): document.excluded_shapes = new_excluded_shapes From 8c4cf4118d1d8dbb0a2f7465cb66f2599d56e4fb Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Mon, 25 Jun 2012 12:51:13 +0200 Subject: [PATCH 61/79] Remove print --- weasyprint/layout/float.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/weasyprint/layout/float.py b/weasyprint/layout/float.py index 8fc89366..75bda216 100644 --- a/weasyprint/layout/float.py +++ b/weasyprint/layout/float.py @@ -172,8 +172,6 @@ def avoid_collisions(document, box, containing_block, outer=True): position_x = max_left_bound available_width = max_right_bound - max_left_bound - if box.style.float == 'right': - print(colliding_shapes, position_x, position_y, available_width) if not outer: position_x -= box.margin_left From 6bccfe75140690430e9a9f06579e12bb255fa5dc Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Mon, 25 Jun 2012 19:48:22 +0200 Subject: [PATCH 62/79] Fix a lot of things about floats --- weasyprint/layout/blocks.py | 66 +++++++++++++++++++++++++++---------- weasyprint/layout/float.py | 1 + 2 files changed, 50 insertions(+), 17 deletions(-) diff --git a/weasyprint/layout/blocks.py b/weasyprint/layout/blocks.py index 2bc399d3..af3b1dac 100644 --- a/weasyprint/layout/blocks.py +++ b/weasyprint/layout/blocks.py @@ -227,7 +227,6 @@ def block_container_layout(document, box, max_position_y, skip_stack, adjoining_margins = [] adjoining_margins.append(box.margin_top) - this_box_adjoining_margins = adjoining_margins collapsing_with_children = not (box.border_top_width or box.padding_top or establishes_formatting_context(box) or box.is_for_root_element) @@ -248,6 +247,8 @@ def block_container_layout(document, box, max_position_y, skip_stack, new_children = [] next_page = 'any' + last_in_flow_child = None + is_start = skip_stack is None if is_start: skip = 0 @@ -352,6 +353,37 @@ def block_container_layout(document, box, max_position_y, skip_stack, else: page_break = 'auto' + resolve_percentages(child, box) + + if (child.is_in_normal_flow() and last_in_flow_child is None and + collapsing_with_children): + # TODO: add the adjoining descendants' margin top to + # [child.margin_top] + old_collapsed_margin = collapse_margin(adjoining_margins) + new_collapsed_margin = collapse_margin( + adjoining_margins + [child.margin_top]) + collapsed_margin_difference = ( + new_collapsed_margin - old_collapsed_margin) + for previous_new_child in new_children: + previous_new_child.translate( + dy=collapsed_margin_difference) + clearance = get_clearance( + document, child, new_collapsed_margin) + if clearance is None: + box.position_y += new_collapsed_margin - box.margin_top + # Don’t count box.margin_top as it is in adjoining_margins + position_y = box.position_y + else: + for previous_new_child in new_children: + previous_new_child.translate( + dy=-collapsed_margin_difference) + + collapsed_margin = collapse_margin(adjoining_margins) + box.position_y += collapsed_margin - box.margin_top + adjoining_margins = [] + # Count box.margin_top as we emptied adjoining_margins + position_y = box.content_box_y() + new_containing_block = box (new_child, resume_at, next_page, next_adjoining_margins, collapsing_through) = block_level_layout( @@ -363,7 +395,7 @@ def block_container_layout(document, box, max_position_y, skip_stack, skip_stack = None if new_child is not None: - # index in its non-laid-out parent, not in the future new parent. + # index in its non-laid-out parent, not in future new parent # May be used in find_earlier_page_break() new_child.index = index @@ -430,7 +462,6 @@ def block_container_layout(document, box, max_position_y, skip_stack, if resume_at is not None: resume_at = (index, resume_at) break - else: resume_at = None @@ -438,21 +469,17 @@ def block_container_layout(document, box, max_position_y, skip_stack, and not page_is_empty: return None, None, 'any', [], False - if collapsing_with_children: - # this_adjoining_margins contains box.margin_top - border_box_y = box.position_y + collapse_margin( - this_box_adjoining_margins) - box.position_y = border_box_y - box.margin_top - # TODO: move the float boxes inside box too - # See W3C test margin-collapse-clear-004 + for previous_child in reversed(new_children): + if previous_child.is_in_normal_flow(): + last_in_flow_child = previous_child + break + else: + last_in_flow_child = None + if collapsing_with_children and last_in_flow_child is None: + box.position_y += collapse_margin(adjoining_margins) - box.margin_top collapsing_through = False - if new_children: - # bottom margin of the last child and bottom margin of this box ... - if box.height != 'auto': - # not adjoining. (position_y is not used afterwards.) - adjoining_margins = [] - else: + if last_in_flow_child is None: collapsed_margin = collapse_margin(adjoining_margins) # top and bottom margin of this box if (box.height in ('auto', 0) and @@ -464,6 +491,11 @@ def block_container_layout(document, box, max_position_y, skip_stack, else: position_y += collapsed_margin adjoining_margins = [] + else: + # bottom margin of the last child and bottom margin of this box ... + if box.height != 'auto': + # 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): @@ -601,7 +633,7 @@ def find_earlier_page_break(children, absolute_boxes, fixed_boxes): resume_at = (new_child.index, resume_at) break elif isinstance(child, boxes.TableBox): - pass # TODO: find an earlier break between table rows. + pass # TODO: find an earlier break between table rows. if child.is_in_normal_flow(): if previous_in_flow is not None and ( block_level_page_break(child, previous_in_flow) diff --git a/weasyprint/layout/float.py b/weasyprint/layout/float.py index 75bda216..3505d5da 100644 --- a/weasyprint/layout/float.py +++ b/weasyprint/layout/float.py @@ -106,6 +106,7 @@ def get_clearance(document, box, collapsed_margin=0): """Return None if there is no clearance, otherwise the clearance value.""" clearance = None hypothetical_position = box.position_y + collapsed_margin + # Hypothetical position is the position of the top border edge for excluded_shape in document.excluded_shapes: if box.style.clear in (excluded_shape.style.float, 'both'): y, h = excluded_shape.position_y, excluded_shape.margin_height() From 4c824f73748ca471786a1d1045205247e3355684 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Mon, 25 Jun 2012 21:10:00 +0200 Subject: [PATCH 63/79] Don't create new lines for floats with height = 0 --- weasyprint/layout/float.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/weasyprint/layout/float.py b/weasyprint/layout/float.py index 3505d5da..49092eb2 100644 --- a/weasyprint/layout/float.py +++ b/weasyprint/layout/float.py @@ -123,6 +123,9 @@ def avoid_collisions(document, box, containing_block, outer=True): box_width = box.margin_width() if outer else box.border_width() box_height = box.margin_height() if outer else box.border_height() + if box_height == 0: + return 0, 0, containing_block.width + while True: colliding_shapes = [ shape for shape in excluded_shapes From b26d35cd96ebe73bc586653cf74534d56514124c Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Mon, 25 Jun 2012 21:35:45 +0200 Subject: [PATCH 64/79] Don't resolve percentages on tables --- weasyprint/layout/blocks.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/weasyprint/layout/blocks.py b/weasyprint/layout/blocks.py index af3b1dac..8ca729a1 100644 --- a/weasyprint/layout/blocks.py +++ b/weasyprint/layout/blocks.py @@ -353,10 +353,9 @@ def block_container_layout(document, box, max_position_y, skip_stack, else: page_break = 'auto' - resolve_percentages(child, box) - if (child.is_in_normal_flow() and last_in_flow_child is None and collapsing_with_children): + resolve_percentages(child, box) # TODO: add the adjoining descendants' margin top to # [child.margin_top] old_collapsed_margin = collapse_margin(adjoining_margins) From 2f68f0f519380e183b469ee2247a4c73c93be22a Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Mon, 25 Jun 2012 21:37:15 +0200 Subject: [PATCH 65/79] Again! --- weasyprint/layout/blocks.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/weasyprint/layout/blocks.py b/weasyprint/layout/blocks.py index 8ca729a1..6911a079 100644 --- a/weasyprint/layout/blocks.py +++ b/weasyprint/layout/blocks.py @@ -353,9 +353,11 @@ def block_container_layout(document, box, max_position_y, skip_stack, else: page_break = 'auto' + if not box.is_table_wrapper: + resolve_percentages(child, box) + if (child.is_in_normal_flow() and last_in_flow_child is None and collapsing_with_children): - resolve_percentages(child, box) # TODO: add the adjoining descendants' margin top to # [child.margin_top] old_collapsed_margin = collapse_margin(adjoining_margins) From 762a8db2bee98830dafee86639acedf6b87fb8f6 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Mon, 25 Jun 2012 23:47:07 +0200 Subject: [PATCH 66/79] Don't detect collisions with 0-height boxes only for float boxes --- weasyprint/layout/float.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weasyprint/layout/float.py b/weasyprint/layout/float.py index 49092eb2..b7448852 100644 --- a/weasyprint/layout/float.py +++ b/weasyprint/layout/float.py @@ -123,7 +123,7 @@ def avoid_collisions(document, box, containing_block, outer=True): box_width = box.margin_width() if outer else box.border_width() box_height = box.margin_height() if outer else box.border_height() - if box_height == 0: + if box_height == 0 and box.is_floated(): return 0, 0, containing_block.width while True: From 9afa96e98722a72bac97539b58babd1d6b8d88b9 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Mon, 25 Jun 2012 23:48:21 +0200 Subject: [PATCH 67/79] Put floats in the next line when they don't fit on current line --- weasyprint/layout/blocks.py | 4 +-- weasyprint/layout/inlines.py | 69 +++++++++++++++++++++++------------- 2 files changed, 47 insertions(+), 26 deletions(-) diff --git a/weasyprint/layout/blocks.py b/weasyprint/layout/blocks.py index 6911a079..2abccca6 100644 --- a/weasyprint/layout/blocks.py +++ b/weasyprint/layout/blocks.py @@ -353,11 +353,11 @@ def block_container_layout(document, box, max_position_y, skip_stack, else: page_break = 'auto' + # TODO: what happens here if box is a table wrapper? if not box.is_table_wrapper: resolve_percentages(child, box) - if (child.is_in_normal_flow() and last_in_flow_child is None and - collapsing_with_children): + collapsing_with_children) and not box.is_table_wrapper: # TODO: add the adjoining descendants' margin top to # [child.margin_top] old_collapsed_margin = collapse_margin(adjoining_margins) diff --git a/weasyprint/layout/inlines.py b/weasyprint/layout/inlines.py index 6b8cede8..073a55fb 100644 --- a/weasyprint/layout/inlines.py +++ b/weasyprint/layout/inlines.py @@ -42,16 +42,27 @@ def iter_line_boxes(document, box, position_y, skip_stack, containing_block, """ while 1: - line, resume_at = get_next_linebox( + line, resume_at, waiting_floats = get_next_linebox( document, box, position_y, skip_stack, containing_block, device_size, absolute_boxes, fixed_boxes) + float_children = [] + if line: + position_y = line.position_y + line.height + for waiting_float in waiting_floats: + waiting_float.position_y = position_y + waiting_float = float_layout( + document, waiting_float, containing_block, absolute_boxes, + fixed_boxes) + float_children.append(waiting_float) + if float_children: + line = line.copy_with_children( + line.children + tuple(float_children)) if line is None: return yield line, resume_at if resume_at is None: return skip_stack = resume_at - position_y = line.position_y + line.height def get_next_linebox(document, linebox, position_y, skip_stack, @@ -68,7 +79,7 @@ def get_next_linebox(document, linebox, position_y, skip_stack, skip_stack = skip_first_whitespace(linebox, skip_stack) if skip_stack == 'continue': - return None, None + return None, None, [] linebox.width = inline_preferred_minimum_width( document, linebox, skip_stack=skip_stack, first_line=True) @@ -91,9 +102,11 @@ def get_next_linebox(document, linebox, position_y, skip_stack, line_absolutes = [] line_fixed = [] - line, resume_at, preserved_line_break = split_inline_box( - document, linebox, position_x, max_x, skip_stack, containing_block, - device_size, line_absolutes, line_fixed, line_placeholders) + line, resume_at, preserved_line_break, waiting_floats = \ + split_inline_box( + document, linebox, position_x, max_x, skip_stack, + containing_block, device_size, line_absolutes, + line_fixed, line_placeholders) remove_last_whitespace(document, line) @@ -148,7 +161,7 @@ def get_next_linebox(document, linebox, position_y, skip_stack, line.position_x - placeholder.position_x, position_y + line.height - placeholder.position_y) - return line, resume_at + return line, resume_at, waiting_floats def skip_first_whitespace(box, skip_stack): @@ -494,7 +507,7 @@ def split_inline_level(document, box, position_x, max_x, skip_stack, box.margin_left = 0 if box.margin_right == 'auto': box.margin_right = 0 - new_box, resume_at, preserved_line_break = split_inline_box( + new_box, resume_at, preserved_line_break, _ = split_inline_box( document, box, position_x, max_x, skip_stack, containing_block, device_size, absolute_boxes, fixed_boxes, line_placeholders) elif isinstance(box, boxes.AtomicInlineLevelBox): @@ -534,6 +547,7 @@ def split_inline_box(document, box, position_x, max_x, skip_stack, else: skip, skip_stack = skip_stack + waiting_floats = [] for index, child in box.enumerate_skip(skip): child.position_y = box.position_y if child.is_absolutely_positioned(): @@ -549,21 +563,28 @@ def split_inline_box(document, box, position_x, max_x, skip_stack, continue elif child.is_floated(): child.position_x = position_x - child = float_layout( - document, child, containing_block, absolute_boxes, fixed_boxes) - children.append(child) - # TODO: use the main text direction of the line - for old_child in children[:index]: - if not old_child.is_in_normal_flow(): - continue - if child.style.float == 'left': # and direction is ltr - old_child.translate(dx=child.margin_width()) - # elif child.style.float == 'right' and direction is rtl: - # old_child.translate(dx=-child.margin_width()) - if child.style.float == 'left': - position_x += child.margin_width() - elif child.style.float == 'right': - max_x -= child.margin_width() + float_width = shrink_to_fit(document, child, containing_block) + if float_width > max_x - position_x: + # TODO: the absolute and fixed boxes in the floats must be + # added here, and not in iter_line_boxes + waiting_floats.append(child) + else: + child = float_layout( + document, child, containing_block, absolute_boxes, + fixed_boxes) + children.append(child) + # TODO: use the main text direction of the line + for old_child in children[:index]: + if not old_child.is_in_normal_flow(): + continue + if child.style.float == 'left': # and direction is ltr + old_child.translate(dx=child.margin_width()) + # elif child.style.float == 'right' and direction is rtl: + # old_child.translate(dx=-child.margin_width()) + if child.style.float == 'left': + position_x += child.margin_width() + elif child.style.float == 'right': + max_x -= child.margin_width() continue new_child, resume_at, preserved = split_inline_level( @@ -626,7 +647,7 @@ def split_inline_box(document, box, position_x, max_x, skip_stack, if new_box.style.position == 'relative': for absolute_box in absolute_boxes: absolute_layout(document, absolute_box, new_box, fixed_boxes) - return new_box, resume_at, preserved_line_break + return new_box, resume_at, preserved_line_break, waiting_floats def split_text_box(document, box, available_width, skip): From d3eb130fc05af85357ef1a34a9bbc639c1ab53f1 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Tue, 26 Jun 2012 00:21:50 +0200 Subject: [PATCH 68/79] Fix *again* the split_inline_box calls in the tests --- weasyprint/tests/test_layout.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/weasyprint/tests/test_layout.py b/weasyprint/tests/test_layout.py index 055ef346..84b3f3c6 100644 --- a/weasyprint/tests/test_layout.py +++ b/weasyprint/tests/test_layout.py @@ -1819,7 +1819,7 @@ def test_inlinebox_spliting(): skip = None while 1: inlinebox.position_y = 0 - box, skip, _ = split_inline_box( + box, skip, _, _ = split_inline_box( document, inlinebox, 0, width, skip, parent, None, [], [], []) yield box if skip is None: @@ -1933,7 +1933,7 @@ def test_inlinebox_text_after_spliting(): skip = None while 1: inlinebox.position_y = 0 - box, skip, _ = split_inline_box( + box, skip, _, _ = split_inline_box( document, inlinebox, 0, 100, skip, paragraph, None, [], [], []) parts.append(box) if skip is None: From 228f1e3d77b11c638d83b0e6bb0b31192b1561c5 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Thu, 28 Jun 2012 00:51:24 +0200 Subject: [PATCH 69/79] Fix most of the tests (but not Acid2) --- weasyprint/layout/blocks.py | 22 ++++++++++++++-------- weasyprint/tests/test_layout.py | 4 +++- weasyprint/tests/test_pdf.py | 10 ++++------ 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/weasyprint/layout/blocks.py b/weasyprint/layout/blocks.py index 2abccca6..8d2a98bd 100644 --- a/weasyprint/layout/blocks.py +++ b/weasyprint/layout/blocks.py @@ -227,6 +227,7 @@ def block_container_layout(document, box, max_position_y, skip_stack, adjoining_margins = [] adjoining_margins.append(box.margin_top) + this_box_adjoining_margins = adjoining_margins collapsing_with_children = not (box.border_top_width or box.padding_top or establishes_formatting_context(box) or box.is_for_root_element) @@ -353,16 +354,21 @@ def block_container_layout(document, box, max_position_y, skip_stack, else: page_break = 'auto' - # TODO: what happens here if box is a table wrapper? - if not box.is_table_wrapper: - resolve_percentages(child, box) + new_containing_block = box + if not new_containing_block.is_table_wrapper: + resolve_percentages(child, new_containing_block) + if (child.is_in_normal_flow() and last_in_flow_child is None and - collapsing_with_children) and not box.is_table_wrapper: + collapsing_with_children): # TODO: add the adjoining descendants' margin top to # [child.margin_top] old_collapsed_margin = collapse_margin(adjoining_margins) + if child.margin_top == 'auto': + child_margin_top = 0 + else: + child_margin_top = child.margin_top new_collapsed_margin = collapse_margin( - adjoining_margins + [child.margin_top]) + adjoining_margins + [child_margin_top]) collapsed_margin_difference = ( new_collapsed_margin - old_collapsed_margin) for previous_new_child in new_children: @@ -385,7 +391,6 @@ def block_container_layout(document, box, max_position_y, skip_stack, # Count box.margin_top as we emptied adjoining_margins position_y = box.content_box_y() - new_containing_block = box (new_child, resume_at, next_page, next_adjoining_margins, collapsing_through) = block_level_layout( document, child, max_position_y, skip_stack, @@ -476,8 +481,9 @@ def block_container_layout(document, box, max_position_y, skip_stack, break else: last_in_flow_child = None - if collapsing_with_children and last_in_flow_child is None: - box.position_y += collapse_margin(adjoining_margins) - box.margin_top + if collapsing_with_children: + box.position_y += ( + collapse_margin(this_box_adjoining_margins) - box.margin_top) collapsing_through = False if last_in_flow_child is None: diff --git a/weasyprint/tests/test_layout.py b/weasyprint/tests/test_layout.py index 84b3f3c6..4b88bf38 100644 --- a/weasyprint/tests/test_layout.py +++ b/weasyprint/tests/test_layout.py @@ -3716,7 +3716,9 @@ def test_margin_collapsing(): return p2_top - p1_bottom # Collapsing with children - @assert_collapsing + # TODO: this test fail because of TODO in layout/blocks.py: add the + # adjoining descendants' margin top + #@assert_collapsing def vertical_space_5(margin_1, margin_2): page, = parse('''

Title 1

Title 2

-

Title 3

+

Title 3

Title 4

-

- Title 5 - -

+

Title 5

+

Title 6

Title 7

Title 8

From 5e270133c860ca12acd46c860a862da199e0cdcd Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Thu, 28 Jun 2012 01:41:42 +0200 Subject: [PATCH 70/79] Use the border height instead of the margin height to find floats with no height Fixes Acid2 --- weasyprint/layout/float.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weasyprint/layout/float.py b/weasyprint/layout/float.py index b7448852..7035522f 100644 --- a/weasyprint/layout/float.py +++ b/weasyprint/layout/float.py @@ -123,7 +123,7 @@ def avoid_collisions(document, box, containing_block, outer=True): box_width = box.margin_width() if outer else box.border_width() box_height = box.margin_height() if outer else box.border_height() - if box_height == 0 and box.is_floated(): + if box.border_height() == 0 and box.is_floated(): return 0, 0, containing_block.width while True: From 01c451b5429b3e19d4ed94adcd80e5d0df204cde Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Thu, 28 Jun 2012 04:14:06 +0200 Subject: [PATCH 71/79] Don't collapse margins in table wrappers, remove a double translation With this version, a lot of tests are OK. The main missing feature is the unsupported margin collapsing through nested boxes. Once this is fixed, the branch can be considered as stable. --- weasyprint/layout/blocks.py | 67 ++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 35 deletions(-) diff --git a/weasyprint/layout/blocks.py b/weasyprint/layout/blocks.py index 8d2a98bd..0da94566 100644 --- a/weasyprint/layout/blocks.py +++ b/weasyprint/layout/blocks.py @@ -355,41 +355,38 @@ def block_container_layout(document, box, max_position_y, skip_stack, page_break = 'auto' new_containing_block = box - if not new_containing_block.is_table_wrapper: - resolve_percentages(child, new_containing_block) - if (child.is_in_normal_flow() and last_in_flow_child is None and - collapsing_with_children): - # TODO: add the adjoining descendants' margin top to - # [child.margin_top] - old_collapsed_margin = collapse_margin(adjoining_margins) - if child.margin_top == 'auto': - child_margin_top = 0 - else: - child_margin_top = child.margin_top - new_collapsed_margin = collapse_margin( - adjoining_margins + [child_margin_top]) - collapsed_margin_difference = ( - new_collapsed_margin - old_collapsed_margin) - for previous_new_child in new_children: - previous_new_child.translate( - dy=collapsed_margin_difference) - clearance = get_clearance( - document, child, new_collapsed_margin) - if clearance is None: - box.position_y += new_collapsed_margin - box.margin_top - # Don’t count box.margin_top as it is in adjoining_margins - position_y = box.position_y - else: + 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 collapsing_with_children): + # TODO: add the adjoining descendants' margin top to + # [child.margin_top] + old_collapsed_margin = collapse_margin(adjoining_margins) + if child.margin_top == 'auto': + child_margin_top = 0 + else: + child_margin_top = child.margin_top + new_collapsed_margin = collapse_margin( + adjoining_margins + [child_margin_top]) + collapsed_margin_difference = ( + new_collapsed_margin - old_collapsed_margin) for previous_new_child in new_children: previous_new_child.translate( - dy=-collapsed_margin_difference) + dy=collapsed_margin_difference) + clearance = get_clearance( + document, child, new_collapsed_margin) + if clearance is not None: + for previous_new_child in new_children: + previous_new_child.translate( + dy=-collapsed_margin_difference) - collapsed_margin = collapse_margin(adjoining_margins) - box.position_y += collapsed_margin - box.margin_top - adjoining_margins = [] - # Count box.margin_top as we emptied adjoining_margins - position_y = box.content_box_y() + collapsed_margin = collapse_margin(adjoining_margins) + box.position_y += collapsed_margin - box.margin_top + # Count box.margin_top as we emptied adjoining_margins + adjoining_margins = [] + position_y = box.content_box_y() (new_child, resume_at, next_page, next_adjoining_margins, collapsing_through) = block_level_layout( @@ -475,16 +472,16 @@ def block_container_layout(document, box, max_position_y, skip_stack, and not page_is_empty: return None, None, 'any', [], False + if collapsing_with_children: + box.position_y += ( + collapse_margin(this_box_adjoining_margins) - box.margin_top) + for previous_child in reversed(new_children): if previous_child.is_in_normal_flow(): last_in_flow_child = previous_child break else: last_in_flow_child = None - if collapsing_with_children: - box.position_y += ( - collapse_margin(this_box_adjoining_margins) - box.margin_top) - collapsing_through = False if last_in_flow_child is None: collapsed_margin = collapse_margin(adjoining_margins) From 85753abc7fecdae62f1dcf019511a57869090d5c Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Thu, 28 Jun 2012 09:09:15 +0200 Subject: [PATCH 72/79] Re-add a test that was failing --- weasyprint/tests/test_layout.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/weasyprint/tests/test_layout.py b/weasyprint/tests/test_layout.py index 4b88bf38..84b3f3c6 100644 --- a/weasyprint/tests/test_layout.py +++ b/weasyprint/tests/test_layout.py @@ -3716,9 +3716,7 @@ def test_margin_collapsing(): return p2_top - p1_bottom # Collapsing with children - # TODO: this test fail because of TODO in layout/blocks.py: add the - # adjoining descendants' margin top - #@assert_collapsing + @assert_collapsing def vertical_space_5(margin_1, margin_2): page, = parse(''' + + +

Hello World!

+

Follow this link to view the reference image, which should be rendered below the text "Hello World!" on the test page in the same way that this paragraph is rendered below that text on this page.

+ + \ No newline at end of file diff --git a/weasyprint/tests/resources/acid2/index.html b/weasyprint/tests/resources/acid2-test.html similarity index 79% rename from weasyprint/tests/resources/acid2/index.html rename to weasyprint/tests/resources/acid2-test.html index b6432099..97268dd3 100644 --- a/weasyprint/tests/resources/acid2/index.html +++ b/weasyprint/tests/resources/acid2-test.html @@ -117,19 +117,21 @@ +

Standards compliant?

-

Take The Acid2 Test and compare it to the reference rendering.

+

Take The Acid2 Test and compare it to the reference rendering.

Hello World!

+

                              
-
ERROR
+
ERROR
+
 
@@ -138,8 +140,9 @@
  • +
    - \ No newline at end of file + diff --git a/weasyprint/tests/resources/acid2/reference.html b/weasyprint/tests/resources/acid2/reference.html deleted file mode 100644 index d67a7386..00000000 --- a/weasyprint/tests/resources/acid2/reference.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - The Second Acid Test (Reference Rendering) - - - -

    Hello World!

    -

    Follow this link to view the reference image, which should be rendered below the text "Hello World!" on the test page in the same way that this paragraph is rendered below that text on this page.

    - - \ No newline at end of file diff --git a/weasyprint/tests/resources/acid2/reference.png b/weasyprint/tests/resources/acid2/reference.png deleted file mode 100644 index 7aee7609d6ade6e39ad53b04a9f61e55f3b00c76..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2261 zcmZvecT^L|7KbC!qzedn8x+GTMFL1!kN}}~hzJQ5qzY1$B7_hUP>7)g2uchVq&MjT zA}G>U5=5FTi$JJ?6lqDQK|mhvThDRd**P<{*5-xn{(f!$G1n+5=QpIVw2A~5vj3~L+E;0aoDm2>m^PE}dWVqeG!qLa-4I$DN6 zL?{qHJ6aAv&VbR*&;3$gE-JrXQHnk$>J@XZS|HH^NwGjkSQ+>gOs(Uy?}-;tRc)k# zS~O{}uvXq&jW40Qw7H!Q{?4@Jz@FZaS+@RM(lIb62}I0AATpy-a7lpPu@mwpaB;ma5!3ej5iOawKwbzhwoC?nSmui*SU7#)vs)V{dc)<}VW+ z1WZpK8jI-N9fX#Jr`ulCWx*tD^BpOz>7dyW?wO-!1LoFWBy>VL;<__et zDY7L*d8+5QE;hf`!c}@aoJUqHV1hjmLN(d+ z8#-OY?b+`1iF=aG#)X(0<{Do?m1 z0C2$x`uj7n?rNV{*OEvc?uRBK!-g4Kr3xEG8`4EaTKK;0AQ^!cU-Z|T&XS{&y{~$9 z_P*_GG4AYb4D?-ivlDQ%9UES_ie)ju5|a0=z!Jvt1g-IRrn^@@IMmei ztE;ow(;i$s>PTBb-4kUMG87}V1RrrHCm)|E7nRF1aNeaABjo)KfPt;x5_-AV00063 zfxzb@%bisP#UGNrN4D-!mtzjRnW5CN0BIm_XvpBCU0cg`QT0e|AX@e?lDZP0?VXio z8zFnwgafNbh-!_Znf*E7%k!La>=c=~9YM?;#fb`-&AD#Mw$AT1${Ctoh=94OB~U0b z+&$`)-0TXW>2?rjB>0mF z+s)0ZW|;7SPr+1XAK|!o-`6x69*4RDV@dGa$j+na-F^dR;Yb?@cj<-?*|sn^jrl$t zT~Wxb2_i&^#8XLU);2cm#7*-0?oL?1Yu3CNlNvhC5`%^;lM8mPu5B;2i`0N3iM}H@ zYADa95D8Pqg~~P3tqhS>HF<*QD^BieV4wgW7z&DjISqD>w|Xxw++-dB<6E;k2p>Pn z6n3jqM23c=OJpd2SiKU8E1N2J)X>T+;4=F))v~O@ZwiO|6y)aNVT2smq?7np61^AR z>_==rhBCOesGD>lBvMXRR!V4nQ$p20vzXAba3<9fnC#eLcGY6y37^`E&kd)vS<={nhfy-6EJ@)k|QgSzgbOhd(Rp>wITj6Lbz5 z=-5RjlcQTuVOm%$5%^mc!pJR9*xEY2-MuEKTu}MqV;(6ln9ksbs7>@KGQEo*X&Ryq zf7&v6QbJNyO>K#V*}c6lw{l(w6rrL>A|(mn8i7)f6$N^P{gFe+7%E5H;pJtYKyzc` zS8-2d_^ToBKBKJ*M1-yd2!$~`a69v?D%JWiCI&7nC}=8k9|g?_k3~U`Cz!;>#_DGF z2WZh~!NN?dx~C5QfzvC_UEN?QvojQ26kc1Y0Rz6*;pT0gj=#lrgTz*;qR>W_=B}<2 zY&LszbaeHvap17o7+2RUmy{lOQu6WSw8Y3xZ6q|=f>TH5o#!f_9db%IeKt#Y{B?}= z!%6hpo3N5idA_de^<(v@BXt23i|}v7F7?qfqOJ*KrL1cqVqq(9Fsc2+H9j{Xz2NGr z#U-9H+zJmdJ*5vf_#e!$(h-*{Tl8G)mmf^{!5bAFBA#PU@+?(aaj|zS2a{0N zx#S}ED%5vu(Z?CS1-p@}U_N20R zII`KV?CYJLnQXh*TD-k@kc(yhDMb@6h2iUwN=Gc|#)V{!{fi}t($*QM(vFIsR|9tx zN=(&7{?hM?`tQzdENFKJyD*IZ=tzsJE@*t8c&J)tJ0`L|8~<-*kcm0@`*!!oniUbg z#*#iG+rq~PKa;TNZ@iLA^77Xf_>qY)5`wPHyTV#p_=~y!YzmnOE)Czf+;|~+cHCMi z&md&>Rr>*$zl;8vJh)8$QaP+tSn)&twpHo2q@R+3f^$%c_r?Eb`uD2{fz#y!k`Ppk rv;5q-m*6c7dgE{6|D4^D%G5vlSW`2yTA=9Qy8^H@g_~3ux#9l{WcO5+ diff --git a/weasyprint/tests/test_draw.py b/weasyprint/tests/test_draw.py index 50e4efda..a7c52e23 100644 --- a/weasyprint/tests/test_draw.py +++ b/weasyprint/tests/test_draw.py @@ -17,7 +17,7 @@ import os.path import tempfile import shutil import itertools -import socket +import operator from io import BytesIO import pytest @@ -124,9 +124,11 @@ def document_to_pixels(document, name, expected_width, expected_height, """ Render an HTML document to PNG, checks its size and return pixel data. """ - png_bytes = document.write_png() assert len(document.pages) == nb_pages + return png_to_pixels(document.write_png(), expected_width, expected_height) + +def png_to_pixels(png_bytes, expected_width, expected_height): with contextlib.closing(pystacia.read_blob(png_bytes)) as image: assert image.size == (expected_width, expected_height) raw = image.get_raw('rgba')['raw'] @@ -1922,29 +1924,19 @@ def test_2d_transform(): @assert_no_logs def test_acid2(): """A local version of http://acid2.acidtests.org/""" - size = 640 - tolerance = 3 - - stylesheet = CSS(string=''' - @page { -weasy-size: %ipx; margin: 0 } - - /* Remove the introduction from the test, it is not in the reference */ - .intro { display: none } - ''' % size) - def get_pixels(filename): - return document_to_pixels( - HTML(resource_filename(filename))._get_document( - [stylesheet], True), - 'acid2', size, size) + def get_png_pages(filename): + return HTML(resource_filename(filename)).get_png_pages() with capture_logs(): - # http://www.damowmow.com/404/ sometimes times out on IPv6… - previous_timeout = socket.getdefaulttimeout() - socket.setdefaulttimeout(1) - try: - result = get_pixels('acid2/index.html') - finally: - socket.setdefaulttimeout(previous_timeout) + # This is a copy of http://www.webstandards.org/files/acid2/test.html + intro_page, test_page = get_png_pages('acid2-test.html') + # Ignore the intro page: it is not in the reference + width, height, test_png = test_page - reference = get_pixels('acid2/reference.html') - assert_pixels_equal('acid2', size, size, result, reference, tolerance) + # This is a copy of http://www.webstandards.org/files/acid2/reference.html + (ref_width, ref_height, ref_png), = get_png_pages('acid2-reference.html') + + assert (width, height) == (ref_width, ref_height) + assert_pixels_equal( + 'acid2', width, height, png_to_pixels(test_png, width, height), + png_to_pixels(ref_png, width, height), tolerance=2) From c36d6b6b29695a6bbe296bd1e70141babab4bc8a Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Thu, 28 Jun 2012 18:46:54 +0200 Subject: [PATCH 78/79] Intrinsic widths: account for floats in inline contexts --- weasyprint/layout/preferred.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/weasyprint/layout/preferred.py b/weasyprint/layout/preferred.py index 9c1afd15..cd4a6495 100644 --- a/weasyprint/layout/preferred.py +++ b/weasyprint/layout/preferred.py @@ -157,13 +157,13 @@ def inline_preferred_minimum_width(document, box, outer=True, skip_stack=None, else: skip, skip_stack = skip_stack for index, child in box.enumerate_skip(skip): - if not child.is_in_normal_flow(): + if child.is_absolutely_positioned(): continue # Skip if isinstance(child, boxes.InlineReplacedBox): # Images are on their own line current_line = replaced_preferred_width(child) - elif isinstance(child, boxes.InlineBlockBox): + elif isinstance(child, boxes.InlineBlockBox) or child.is_floated(): if child.is_table_wrapper: current_line = table_preferred_minimum_width(document, child) else: @@ -188,13 +188,13 @@ def inline_preferred_width(document, box, outer=True): widest_line = 0 current_line = 0 for child in box.children: - if not child.is_in_normal_flow(): + if child.is_absolutely_positioned(): continue # Skip if isinstance(child, boxes.InlineReplacedBox): # No line break around images current_line += replaced_preferred_width(child) - elif isinstance(child, boxes.InlineBlockBox): + elif isinstance(child, boxes.InlineBlockBox) or child.is_floated(): if child.is_table_wrapper: current_line += table_preferred_width(document, child) else: From 6d5e47399393c1f3e1efa4cd18ee74ad9b374c3b Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Thu, 28 Jun 2012 19:07:26 +0200 Subject: [PATCH 79/79] Fix inline floated images. --- weasyprint/layout/preferred.py | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/weasyprint/layout/preferred.py b/weasyprint/layout/preferred.py index cd4a6495..9f28e92b 100644 --- a/weasyprint/layout/preferred.py +++ b/weasyprint/layout/preferred.py @@ -160,25 +160,18 @@ def inline_preferred_minimum_width(document, box, outer=True, skip_stack=None, if child.is_absolutely_positioned(): continue # Skip - if isinstance(child, boxes.InlineReplacedBox): - # Images are on their own line - current_line = replaced_preferred_width(child) - elif isinstance(child, boxes.InlineBlockBox) or child.is_floated(): - if child.is_table_wrapper: - current_line = table_preferred_minimum_width(document, child) - else: - current_line = block_preferred_minimum_width(document, child) - elif isinstance(child, boxes.InlineBox): + if isinstance(child, boxes.InlineBox): # TODO: handle forced line breaks current_line = inline_preferred_minimum_width( document, child, skip_stack=skip_stack, first_line=first_line) - else: - assert isinstance(child, boxes.TextBox) + elif isinstance(child, boxes.TextBox): widths = text.line_widths(document, child, width=0, skip=skip) if first_line: return next(widths) else: current_line = max(widths) + else: + current_line = preferred_minimum_width(document, child) widest_line = max(widest_line, current_line) return adjust(box, outer, widest_line) @@ -191,19 +184,10 @@ def inline_preferred_width(document, box, outer=True): if child.is_absolutely_positioned(): continue # Skip - if isinstance(child, boxes.InlineReplacedBox): - # No line break around images - current_line += replaced_preferred_width(child) - elif isinstance(child, boxes.InlineBlockBox) or child.is_floated(): - if child.is_table_wrapper: - current_line += table_preferred_width(document, child) - else: - current_line += block_preferred_width(document, child) - elif isinstance(child, boxes.InlineBox): + if isinstance(child, boxes.InlineBox): # TODO: handle forced line breaks current_line += inline_preferred_width(document, child) - else: - assert isinstance(child, boxes.TextBox) + elif isinstance(child, boxes.TextBox): lines = list(text.line_widths(document, child, width=None)) assert lines # The first text line goes on the current line @@ -214,6 +198,8 @@ def inline_preferred_width(document, box, outer=True): if len(lines) > 2: widest_line = max(widest_line, max(lines[1:-1])) current_line = lines[-1] + else: + current_line += preferred_width(document, child) widest_line = max(widest_line, current_line) return adjust(box, outer, widest_line)