Skip to content

Commit

Permalink
Merge pull request #882 from hbrunn/master-running
Browse files Browse the repository at this point in the history
[ADD] position: running(); content: element();
  • Loading branch information
liZe authored Sep 27, 2019
2 parents 495f762 + 645b946 commit fd70058
Show file tree
Hide file tree
Showing 12 changed files with 167 additions and 13 deletions.
1 change: 0 additions & 1 deletion docs/features.rst
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,6 @@ according to their position in the document:
The other features of GCPM are **not** implemented:

- running elements (``running()`` and ``element()``);
- footnotes (``float: footnote``, ``footnote-display``, ``footnote`` counter,
``::footnote-call``, ``::footnote-marker``, ``@footnote`` rule,
``footnote-policy``);
Expand Down
5 changes: 4 additions & 1 deletion weasyprint/css/computed_values.py
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,10 @@ def _content_list(computer, values):
elif value[0] == 'attr()':
assert value[1][1] == 'string'
computed_value = compute_attr_function(computer, value)
elif value[0] in ('counter()', 'counters()', 'content()', 'string()'):
elif value[0] in (
'counter()', 'counters()', 'content()', 'element()',
'string()',
):
# Other values need layout context, their computed value cannot be
# better than their specified value yet.
# See build.compute_content_list.
Expand Down
1 change: 1 addition & 0 deletions weasyprint/css/properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from tinycss2.color3 import parse_color

Dimension = collections.namedtuple('Dimension', ['value', 'unit'])
Running = collections.namedtuple('Running', ['element'])


# See http://www.w3.org/TR/CSS21/propidx.html
Expand Down
4 changes: 4 additions & 0 deletions weasyprint/css/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -743,3 +743,7 @@ def get_content_list_token(token, base_url):
elif arg.type == 'string':
string = arg.value
return ('leader()', ('string', string))
elif name == 'element':
if len(args) != 1 or args[0].type != 'ident':
return
return ('element()', args[0])
15 changes: 11 additions & 4 deletions weasyprint/css/validation/properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from ...formatting_structure import counters
from .. import computed_values
from ..properties import KNOWN_PROPERTIES, Dimension
from ..properties import KNOWN_PROPERTIES, Dimension, Running
from ..utils import (
InvalidValues, check_var_function, comma_separated_list, get_angle,
get_content_list, get_content_list_token, get_custom_ident, get_image,
Expand Down Expand Up @@ -967,10 +967,17 @@ def text_overflow(keyword):


@property()
@single_keyword
def position(keyword):
@single_token
def position(token):
"""``position`` property validation."""
return keyword in ('static', 'relative', 'absolute', 'fixed')
if (
token.type == 'function' and token.name == 'running' and
len(token.arguments) == 1 and token.arguments[0].type == 'ident'
):
return Running(token.arguments[0].value)
keyword = get_single_keyword([token])
if keyword in ('static', 'relative', 'absolute', 'fixed'):
return keyword


@property()
Expand Down
49 changes: 47 additions & 2 deletions weasyprint/formatting_structure/boxes.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
import itertools

from ..css import computed_from_cascaded
from ..css.properties import Dimension
from ..css.properties import Dimension, Running


class Box(object):
Expand Down Expand Up @@ -274,9 +274,17 @@ def is_absolutely_positioned(self):
"""Return whether this box is in the absolute positioning scheme."""
return self.style['position'] in ('absolute', 'fixed')

def is_running(self):
"""Return whether this box is a running element.
(https://www.w3.org/TR/css-gcpm-3/#running-elements)"""
return isinstance(self.style['position'], Running)

def is_in_normal_flow(self):
"""Return whether this box is in normal flow."""
return not (self.is_floated() or self.is_absolutely_positioned())
return not (
self.is_floated() or self.is_absolutely_positioned() or
self.is_running()
)

# Start and end page values for named pages

Expand Down Expand Up @@ -333,6 +341,14 @@ def copy_with_children(self, new_children, is_start=True, is_end=True):
new_box._remove_decoration(not is_start, not is_end)
return new_box

def __deepcopy__(self):
result = self.copy()
result.children = tuple([
getattr(child, '__deepcopy__', child.copy)()
for child in self.children
])
return result

def descendants(self):
"""A flat generator for a box, its children and descendants."""
yield self
Expand Down Expand Up @@ -704,3 +720,32 @@ class InlineFlexBox(FlexContainerBox, InlineLevelBox):
It behaves as inline on the outside and as a flex container on the inside.
"""


class RunningPlaceholder(BlockBox):
"""A box to anchor a running element within the document flow"""
def __init__(self, identifier, style):
self.position_x = 0
self.position_y = 0
self.width = 0
self.height = 0
self.padding_left = 0
self.padding_right = 0
self.padding_top = 0
self.padding_bottom = 0
self.margin_left = 0
self.margin_right = 0
self.margin_top = 0
self.margin_bottom = 0
self.border_left_width = 0
self.border_right_width = 0
self.border_top_width = 0
self.border_bottom_width = 0
self.identifier = identifier
super(RunningPlaceholder, self).__init__(
None, dict(style, display='block', content=[('running', None)]),
children=[],
)

def translate(self, dx=0, dy=0, ignore_floats=False):
pass
24 changes: 22 additions & 2 deletions weasyprint/formatting_structure/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,26 @@ def compute_content_list(content_list, parent_box, counter_values, css_token,
texts.append(quotes[min(quote_depth[0], len(quotes) - 1)])
if is_open:
quote_depth[0] += 1
elif type_ == 'element()':
if value.value not in context.running_elements:
# TODO: emit warning
continue
new_box = None
for i in range(context.current_page - 1, -1, -1):
if i not in context.running_elements[value.value]:
continue
running_box = context.running_elements[value.value][i]
new_box = running_box.__deepcopy__()
break
new_box.style['position'] = 'static'
for child in new_box.descendants():
if child.style['content'] in ('normal', 'none'):
continue
child.children = content_to_boxes(
child.style, child, quote_depth, counter_values,
get_image_from_uri, target_collector, context=context,
page=page)
boxlist.append(new_box)
text = ''.join(texts)
if text:
boxlist.append(boxes.TextBox.anonymous_from(parent_box, text))
Expand Down Expand Up @@ -1247,7 +1267,7 @@ def inline_in_block(box):
]
"""
if not isinstance(box, boxes.ParentBox):
if not isinstance(box, boxes.ParentBox) or box.is_running():
return box

box_children = list(box.children)
Expand Down Expand Up @@ -1382,7 +1402,7 @@ def block_in_inline(box):
]
"""
if not isinstance(box, boxes.ParentBox):
if not isinstance(box, boxes.ParentBox) or box.is_running():
return box

new_children = []
Expand Down
3 changes: 3 additions & 0 deletions weasyprint/layout/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,9 @@ def __init__(self, enable_hinting, style_for, get_image_from_uri,
self.tables = {}
self.dictionaries = {}

# TODO: this is probably the wrong place to put this
self.running_elements = {}

def create_block_formatting_context(self):
self.excluded_shapes = []
self._excluded_shapes_lists.append(self.excluded_shapes)
Expand Down
9 changes: 9 additions & 0 deletions weasyprint/layout/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,15 @@ def block_container_layout(context, box, max_position_y, skip_stack,
break
resume_at = (index, None)
break
elif child.is_running():
placeholder = boxes.RunningPlaceholder(
child.style['position'].element, child.style,
)
placeholder.index = index
new_children.append(placeholder)
context.running_elements.setdefault(
child.style['position'].element, {}
)[placeholder] = child
continue

if isinstance(child, boxes.LineBox):
Expand Down
10 changes: 7 additions & 3 deletions weasyprint/layout/pages.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,10 +345,11 @@ def make_box(at_keyword, containing_block):
box.style, box, quote_depth, counter_values,
context.get_image_from_uri, context.target_collector, context,
page)
# content_to_boxes() only produces inline-level boxes, no need to
# run other post-processors from build.build_formatting_structure()
box = build.inline_in_block(box)
build.process_whitespace(box)
box = build.anonymous_table_boxes(box)
box = build.flex_boxes(box)
box = build.inline_in_block(box)
box = build.block_in_inline(box)
resolve_percentages(box, containing_block)
if not box.is_generated:
box.width = box.height = 0
Expand Down Expand Up @@ -664,6 +665,9 @@ def make_page(context, root_box, page_type, resume_at, page_number,
if call_parse_again:
remake_state['content_changed'] = True
counter_lookup.parse_again(page_counter_values)
if isinstance(child, boxes.RunningPlaceholder):
elements = context.running_elements[child.identifier]
elements[page.page_type.index] = elements[child]

if page_type.blank:
resume_at = previous_resume_at
Expand Down
59 changes: 59 additions & 0 deletions weasyprint/tests/test_layout/test_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -1214,3 +1214,62 @@ def test_margin_boxes_vertical_align():
assert line_1.position_y == 3
assert line_2.position_y == 43
assert line_3.position_y == 83


@assert_no_logs
def test_margin_boxes_element():
pages = render_pages('''
<html>
<head>
<style type="text/css">
.footer {
position: running(footer);
}
@page {
@bottom-center {
content: element(footer);
}
}
h1 {
margin-bottom: 15cm;
}
.page:before {
content: counter(page);
}
.pages:after {
content: counter(pages);
}
</style>
</head>
<body>
<div class="footer">
<span class="page" /> of <span class="pages" />
</div>
<h1>test1</h1>
<h1>test2</h1>
<h1>test3</h1>
<h1>test4</h1>
<h1>test5</h1>
<h1>test6</h1>
<div class="footer">
Last page will be a static footer
</div>
</body>
</html>
''')
# first footer
footer1_text = ''.join(
getattr(node, 'text', '')
for node in pages[0].children[1].descendants())
assert footer1_text == '1 of 3'

# second footer
footer2_text = ''.join(
getattr(node, 'text', '')
for node in pages[1].children[1].descendants())
assert footer2_text == '2 of 3'
# last footer
footer3_text = ''.join(
getattr(node, 'text', '')
for node in pages[2].children[1].descendants())
assert footer3_text == 'Last page will be a static footer'
Empty file modified weasyprint/tools/renderer.py
100755 → 100644
Empty file.

0 comments on commit fd70058

Please sign in to comment.