Skip to content

Commit

Permalink
Handle full "display" property
Browse files Browse the repository at this point in the history
In CSS Display Module Level 3, the "display" property gets a long
representation allowing:

- a clear separation between inner and outer display type,
- new supported types (contents, run-in, flow-root…),
- inline list items.

This commit allows the (retrocompatible) new long syntax for "display". It also
supports the "flow-root" value. It doesn’t support values related to ruby, and
it doesn’t support the new "contents" and "run-in" display types.

This work gives the possibility to simplify the code in the block_*_layout
functions, and to improve the overall layout.

Related to #36.
  • Loading branch information
liZe committed Aug 23, 2021
1 parent c3aebee commit 61ff7f0
Show file tree
Hide file tree
Showing 11 changed files with 107 additions and 75 deletions.
2 changes: 1 addition & 1 deletion tests/test_boxes.py
Expand Up @@ -1215,5 +1215,5 @@ def test_border_collapse_5():
))
def test_display_none_root(html):
box = parse_all(html)
assert box.style['display'] == 'block'
assert box.style['display'] == ('block', 'flow')
assert not box.children
9 changes: 0 additions & 9 deletions tests/test_css.py
Expand Up @@ -21,15 +21,6 @@
BASE_URL, FakeHTML, assert_no_logs, capture_logs, resource_filename)


@assert_no_logs
def test_style_dict():
style = {'margin_left': 12, 'display': 'block'}
assert style['display'] == 'block'
assert style['margin_left'] == 12
with pytest.raises(KeyError):
style['position']


@assert_no_logs
def test_find_stylesheets():
html = FakeHTML(resource_filename('doc1.html'))
Expand Down
24 changes: 12 additions & 12 deletions weasyprint/css/__init__.py
Expand Up @@ -122,17 +122,17 @@ def __call__(self, element, pseudo_type=None):
style = self._computed_styles.get((element, pseudo_type))

if style:
if 'table' in style['display']:
if (style['display'] in ('table', 'inline-table') and
style['border_collapse'] == 'collapse'):
# Padding do not apply
for side in ['top', 'bottom', 'left', 'right']:
style[f'padding_{side}'] = computed_values.ZERO_PIXELS
if (style['display'].startswith('table-') and
style['display'] != 'table-caption'):
# Margins do not apply
for side in ['top', 'bottom', 'left', 'right']:
style[f'margin_{side}'] = computed_values.ZERO_PIXELS
if ('table' in style['display'] and
style['border_collapse'] == 'collapse'):
# Padding do not apply
for side in ['top', 'bottom', 'left', 'right']:
style[f'padding_{side}'] = computed_values.ZERO_PIXELS
if (len(style['display']) == 1 and
style['display'][0].startswith('table-') and
style['display'][0] != 'table-caption'):
# Margins do not apply
for side in ['top', 'bottom', 'left', 'right']:
style[f'margin_{side}'] = computed_values.ZERO_PIXELS

return style

Expand Down Expand Up @@ -171,7 +171,7 @@ def set_computed_styles(self, element, parent, root=None, pseudo_type=None,
for pseudo in (None, 'before', 'after'):
pseudo_style = cascaded_styles.get((element, pseudo), {})
if 'display' in pseudo_style:
if pseudo_style['display'][0] == 'list-item':
if 'list-item' in pseudo_style['display'][0]:
break
else:
if (element, 'marker') in cascaded_styles:
Expand Down
16 changes: 9 additions & 7 deletions weasyprint/css/computed_values.py
Expand Up @@ -537,13 +537,15 @@ def display(style, name, value):
position = style.specified['position']
if position in ('absolute', 'fixed') or float_ != 'none' or (
style.is_root_element):
if value == 'inline-table':
return'table'
elif value in ('inline', 'table-row-group', 'table-column',
'table-column-group', 'table-header-group',
'table-footer-group', 'table-row', 'table-cell',
'table-caption', 'inline-block'):
return 'block'
if value == ('inline-table',):
return ('block', 'table')
elif len(value) == 1 and value[0].startswith('table-'):
return ('block', 'flow')
elif value[0] == 'inline':
if 'list-item' in value:
return ('block', 'flow', 'list-item')
else:
return ('block', 'flow')
return value


Expand Down
2 changes: 1 addition & 1 deletion weasyprint/css/properties.py
Expand Up @@ -21,7 +21,7 @@
'clip': (), # computed value for 'auto'
'color': parse_color('black'), # chosen by the user agent
'direction': 'ltr',
'display': 'inline',
'display': ('inline', 'flow'),
'empty_cells': 'show',
'float': 'none',
'height': 'auto',
Expand Down
50 changes: 42 additions & 8 deletions weasyprint/css/validation/properties.py
Expand Up @@ -620,15 +620,49 @@ def direction(keyword):


@property()
@single_keyword
def display(keyword):
def display(tokens):
"""``display`` property validation."""
return keyword in (
'inline', 'block', 'inline-block', 'list-item', 'none',
'table', 'inline-table', 'table-caption',
'table-row-group', 'table-header-group', 'table-footer-group',
'table-row', 'table-column-group', 'table-column', 'table-cell',
'flex', 'inline-flex')
for token in tokens:
if token.type != 'ident':
return

if len(tokens) == 1:
value = tokens[0].value
if value in (
'none', 'table-caption', 'table-row-group', 'table-cell',
'table-header-group', 'table-footer-group', 'table-row',
'table-column-group', 'table-column'):
return (value,)
elif value in ('inline-table', 'inline-flex', 'inline-grid'):
return tuple(value.split('-'))
elif value == 'inline-block':
return ('inline', 'flow-root')

outside = inside = list_item = None
for token in tokens:
value = token.value
if value in ('block', 'inline'):
if outside:
return
outside = value
elif value in ('flow', 'flow-root', 'table', 'flex', 'grid'):
if inside:
return
inside = value
elif value == 'list-item':
if list_item:
return
list_item = value
else:
return

outside = outside or 'block'
inside = inside or 'flow'
if list_item:
if inside in ('flow', 'flow-root'):
return (outside, inside, list_item)
else:
return (outside, inside)


@property('float')
Expand Down
61 changes: 33 additions & 28 deletions weasyprint/formatting_structure/build.py
Expand Up @@ -23,22 +23,26 @@

# Maps values of the ``display`` CSS property to box types.
BOX_TYPE_FROM_DISPLAY = {
'block': boxes.BlockBox,
'list-item': boxes.BlockBox,
'inline': boxes.InlineBox,
'inline-block': boxes.InlineBlockBox,
'table': boxes.TableBox,
'inline-table': boxes.InlineTableBox,
'table-row': boxes.TableRowBox,
'table-row-group': boxes.TableRowGroupBox,
'table-header-group': boxes.TableRowGroupBox,
'table-footer-group': boxes.TableRowGroupBox,
'table-column': boxes.TableColumnBox,
'table-column-group': boxes.TableColumnGroupBox,
'table-cell': boxes.TableCellBox,
'table-caption': boxes.TableCaptionBox,
'flex': boxes.FlexBox,
'inline-flex': boxes.InlineFlexBox,
('block', 'flow'): boxes.BlockBox,
('inline', 'flow'): boxes.InlineBox,

('block', 'flow-root'): boxes.BlockBox,
('inline', 'flow-root'): boxes.InlineBlockBox,

('block', 'table'): boxes.TableBox,
('inline', 'table'): boxes.InlineTableBox,

('block', 'flex'): boxes.FlexBox,
('inline', 'flex'): boxes.InlineFlexBox,

('table-row',): boxes.TableRowBox,
('table-row-group',): boxes.TableRowGroupBox,
('table-header-group',): boxes.TableRowGroupBox,
('table-footer-group',): boxes.TableRowGroupBox,
('table-column',): boxes.TableColumnBox,
('table-column-group',): boxes.TableColumnGroupBox,
('table-cell',): boxes.TableCellBox,
('table-caption',): boxes.TableCaptionBox,
}


Expand All @@ -56,9 +60,9 @@ def root_style_for(element, pseudo_type=None):
style = style_for(element, pseudo_type)
if style is not None:
if element == element_tree:
style['display'] = 'block'
style['display'] = ('block', 'flow')
else:
style['display'] = 'none'
style['display'] = ('none',)
return style
box, = element_to_box(
element_tree, root_style_for, get_image_from_uri, base_url,
Expand All @@ -78,7 +82,7 @@ def root_style_for(element, pseudo_type=None):


def make_box(element_tag, style, content, element):
box = BOX_TYPE_FROM_DISPLAY[style['display']](
box = BOX_TYPE_FROM_DISPLAY[style['display'][:2]](
element_tag, style, element, content)
return box

Expand Down Expand Up @@ -117,7 +121,7 @@ def element_to_box(element, style_for, get_image_from_uri, base_url,
# TODO: should be the used value. When does the used value for `display`
# differ from the computer value?
display = style['display']
if display == 'none':
if display == ('none',):
return []

box = make_box(element.tag, style, [], element)
Expand All @@ -144,7 +148,7 @@ def element_to_box(element, style_for, get_image_from_uri, base_url,
box.first_line_style = style_for(element, 'first-line')

marker_boxes = []
if style['display'] == 'list-item':
if 'list-item' in style['display']:
marker_boxes = list(marker_to_box(
element, state, style, style_for, get_image_from_uri,
target_collector, counter_style))
Expand Down Expand Up @@ -222,18 +226,19 @@ def before_after_to_box(element, pseudo_type, state, style_for,
# `display` differ from the computer value? It's at least wrong for
# `content` where 'normal' computes as 'inhibit' for pseudo elements.
display = style['display']
if display == ('none',):
return []
content = style['content']
if 'none' in (display, content) or content in ('normal', 'inhibit'):
if content in ('normal', 'inhibit', 'none'):
return []

box = make_box(f'{element.tag}::{pseudo_type}', style, [], element)

quote_depth, counter_values, _counter_scopes = state
update_counters(state, style)

children = []

if display == 'list-item':
if 'list-item' in display:
marker_boxes = list(marker_to_box(
element, state, style, style_for, get_image_from_uri,
target_collector, counter_style))
Expand Down Expand Up @@ -274,7 +279,7 @@ def marker_to_box(element, state, parent_style, style_for, get_image_from_uri,

box = make_box(f'{element.tag}::marker', style, children, element)

if style['display'] == 'none':
if style['display'] == ('none',):
return

image_type, image = style['list_style_image']
Expand Down Expand Up @@ -700,7 +705,7 @@ def update_counters(state, style):
# there was no counter-increment declaration for this element.
# (Or the winning value was 'initial'.)
# http://dev.w3.org/csswg/css3-lists/#declaring-a-list-item
if style['display'] == 'list-item':
if 'list-item' in style['display']:
counter_increment = [('list-item', 1)]
else:
counter_increment = []
Expand Down Expand Up @@ -917,10 +922,10 @@ def wrap_table(box, children):
footer = None
for group in row_groups:
display = group.style['display']
if display == 'table-header-group' and header is None:
if display == ('table-header-group',) and header is None:
group.is_header = True
header = group
elif display == 'table-footer-group' and footer is None:
elif display == ('table-footer-group',) and footer is None:
group.is_footer = True
footer = group
else:
Expand Down
8 changes: 3 additions & 5 deletions weasyprint/html.py
Expand Up @@ -107,11 +107,9 @@ def make_replaced_box(element, box, image):
element should be.
"""
if box.style['display'] in ('block', 'list-item', 'table'):
type_ = boxes.BlockReplacedBox
else:
# TODO: support images with 'display: table-cell'?
type_ = boxes.InlineReplacedBox
type_ = (
boxes.BlockReplacedBox if 'block' in box.style['display']
else boxes.InlineReplacedBox)
new_box = type_(element.tag, box.style, element, image)
# TODO: check other attributes that need to be copied
# TODO: find another solution
Expand Down
4 changes: 3 additions & 1 deletion weasyprint/layout/blocks.py
Expand Up @@ -265,8 +265,10 @@ def block_container_layout(context, box, max_position_y, skip_stack,
# The 1e-9 value comes from PEP 485.
allowed_max_position_y = max_position_y * (1 + 1e-9)

# See http://www.w3.org/TR/CSS21/visuren.html#block-formatting
if not isinstance(box, boxes.BlockBox):
# See http://www.w3.org/TR/CSS21/visuren.html#block-formatting
context.create_block_formatting_context()
elif 'flow-root' in box.style['display']:
context.create_block_formatting_context()

is_start = skip_stack is None
Expand Down
2 changes: 1 addition & 1 deletion weasyprint/layout/inlines.py
Expand Up @@ -232,7 +232,7 @@ def get_next_linebox(context, linebox, position_y, skip_stack,
fixed_boxes.extend(line_fixed)

for placeholder in line_placeholders:
if placeholder.style.specified['display'].startswith('inline'):
if 'inline' in placeholder.style.specified['display']:
# Inline-level static position:
placeholder.translate(0, position_y - placeholder.position_y)
else:
Expand Down
4 changes: 2 additions & 2 deletions weasyprint/svg/__init__.py
Expand Up @@ -407,8 +407,8 @@ def draw_node(self, node, font_size, fill_stroke=True):
self.stream.transform(*(old_ctm @ new_ctm.invert).values)

# Manage display and visibility
display = node.get('display', 'inline') != 'none'
visible = display and (node.get('visibility', 'visible') != 'hidden')
display = node.get('display') != 'none'
visible = display and (node.get('visibility') != 'hidden')

# Draw node
if visible and node.tag in TAGS:
Expand Down

0 comments on commit 61ff7f0

Please sign in to comment.