Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle target-* #604

Merged
merged 58 commits into from
May 1, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
4519ba5
introduce TARGET_COLLECTOR for target-counter, -counters and -text
Tontyna Feb 11, 2018
fed74d1
cleanup code to pass Travis CI check
Tontyna Feb 16, 2018
925466f
cleanup code once more to pass Travis CI check
Tontyna Feb 16, 2018
cce1f39
BugFix: Logging Errors
Tontyna Feb 17, 2018
2b2eebd
Merge branch 'master' into target-counter
Tontyna Feb 25, 2018
087cb3a
cleanup code to pass Travis CI check
Tontyna Feb 25, 2018
d35f455
once again: cleanup code to pass Travis CI check
Tontyna Feb 25, 2018
57f3234
Reduce duplicate code in css validation
Tontyna Feb 26, 2018
71cee36
consolidate (tuples) and [lists]
Tontyna Feb 26, 2018
f68a5fa
Prepare removal of duplicate code
Tontyna Mar 3, 2018
c0c706f
Remove duplicate code: target-* for all
Tontyna Mar 3, 2018
f66c8d0
Merge branch 'master' into target-counter
Tontyna Mar 13, 2018
1c2b9b1
Merge branch 'master' into target-counter
Tontyna Mar 19, 2018
3071432
Merge branch 'master' into target-counter
Tontyna Mar 20, 2018
1ebd36e
Merge branch 'target-counter' of https://github.com/Tontyna/WeasyPrin…
liZe Mar 25, 2018
b60fb46
Get content string from content list
liZe Mar 25, 2018
a6366e3
Clean some docstrings
liZe Mar 25, 2018
21743a8
Remove debug messages and fix some minor typos
liZe Mar 25, 2018
3e5e00f
Clean and remove useless functions
liZe Mar 25, 2018
59a81dc
Don't pre-compute string-set
liZe Mar 25, 2018
c49f302
Merge branch 'master' into target-collector
liZe Mar 27, 2018
9020b4b
Remove intermediate steps logging
liZe Mar 27, 2018
3cdb176
Merge branch 'master' into target-collector
liZe Mar 27, 2018
5c64c2d
Clean docstring
liZe Mar 27, 2018
1039e35
Replace enum by simple string
liZe Mar 27, 2018
07865f6
Don't use a global target collector
liZe Mar 27, 2018
6b43c83
Clean targets.py
liZe Mar 27, 2018
023f66c
Merge branch 'master' into target-collector
liZe Mar 29, 2018
293c01a
Allow optional commas to separate functions arguments
liZe Mar 30, 2018
0a4f2d9
Split validation module
liZe Mar 31, 2018
0eedd2f
Move common image validation functions into utils
liZe Mar 31, 2018
3d915c0
Move and start cleaning content property validation
liZe Mar 31, 2018
85a65d7
Clean CSS functions
liZe Apr 2, 2018
cb4fa7e
Use parentheses at the end of functions keywords
liZe Apr 3, 2018
be71ce2
Handle attr() fallback value
liZe Apr 3, 2018
770e3ec
Fix some fallback values of attr()
liZe Apr 3, 2018
7b76f96
Fix syntax
liZe Apr 3, 2018
4058894
Add perentheses to target-* keywords too
liZe Apr 3, 2018
1386007
Lint
liZe Apr 3, 2018
2be9959
Clean many things about properties validation and computed values
liZe Apr 14, 2018
f960553
Move CSS utils file
liZe Apr 15, 2018
d832e76
Fix get_target and lookup_target
liZe Apr 15, 2018
c49c14c
Clean target collection
liZe Apr 15, 2018
f3f7652
Parse an ident instead of a string as second attr() attribute
liZe Apr 15, 2018
0fc46a5
Fix attr() parsing for url type
liZe Apr 15, 2018
2694cac
Use 'url' keyword everywhere
liZe Apr 15, 2018
0814b06
Add some tests about target-*()
liZe Apr 15, 2018
d2a6833
Always include token type when dealing with URLs
liZe Apr 16, 2018
85b5b76
Fix and add tests about target-counters
liZe Apr 18, 2018
3b1f649
Improve target-counters tests
liZe Apr 18, 2018
f54ab9c
Add tests for target-text
liZe Apr 18, 2018
324ca50
rectify index
Tontyna Apr 22, 2018
72d90ce
Merge pull request #620 from Tontyna/contrib2target
liZe Apr 22, 2018
a4ddcbc
Dont crash when element/target-collector is None
Tontyna Apr 23, 2018
f57cbf3
Merge branch 'master' into target-collector
liZe Apr 30, 2018
b7c5b1c
Merge pull request #621 from Tontyna/contrib2target
liZe Apr 30, 2018
54cf467
Merge branch 'target-collector' of github.com:Kozea/WeasyPrint into t…
liZe Apr 30, 2018
7d79d90
Remove useless comments
liZe Apr 30, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
25 changes: 14 additions & 11 deletions weasyprint/css/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@
import cssselect2
import tinycss2

from . import properties
from . import computed_values
from .descriptors import preprocess_descriptors
from . import properties
from .properties import INITIAL_NOT_COMPUTED
from .validation import (preprocess_declarations, remove_whitespace,
split_on_comma)
from .utils import remove_whitespace, split_on_comma
from .validation import preprocess_declarations
from .validation.descriptors import preprocess_descriptors
from ..logger import LOGGER
from ..urls import get_url_attribute, url_join, URLFetchingError
from .. import CSS
Expand Down Expand Up @@ -448,7 +448,8 @@ def add_declaration(cascaded_styles, prop_name, prop_values, weight, element,


def set_computed_styles(cascaded_styles, computed_styles, element, parent,
root=None, pseudo_type=None, base_url=None):
root=None, pseudo_type=None, base_url=None,
target_collector=None):
"""Set the computed values of styles to ``element``.

Take the properties left by ``apply_style_rule`` on an element or
Expand All @@ -471,11 +472,13 @@ def set_computed_styles(cascaded_styles, computed_styles, element, parent,

cascaded = cascaded_styles.get((element, pseudo_type), {})
computed_styles[element, pseudo_type] = computed_from_cascaded(
element, cascaded, parent_style, pseudo_type, root_style, base_url)
element, cascaded, parent_style, pseudo_type, root_style, base_url,
target_collector)


def computed_from_cascaded(element, cascaded, parent_style, pseudo_type=None,
root_style=None, base_url=None):
root_style=None, base_url=None,
target_collector=None):
"""Get a dict of computed style mixed from parent and cascaded styles."""
if not cascaded and parent_style is not None:
# Fast path for anonymous boxes:
Expand Down Expand Up @@ -532,7 +535,7 @@ def computed_from_cascaded(element, cascaded, parent_style, pseudo_type=None,

return computed_values.compute(
element, pseudo_type, specified, computed, parent_style, root_style,
base_url)
base_url, target_collector)


def parse_page_selectors(rule):
Expand Down Expand Up @@ -773,7 +776,7 @@ def parse_media_query(tokens):

def get_all_computed_styles(html, user_stylesheets=None,
presentational_hints=False, font_config=None,
page_rules=None):
page_rules=None, target_collector=None):
"""Compute all the computed styles of all elements in ``html`` document.

Do everything from finding author stylesheets to parsing and applying them.
Expand Down Expand Up @@ -845,7 +848,7 @@ def get_all_computed_styles(html, user_stylesheets=None,
cascaded_styles, computed_styles, element.etree_element,
root=html.etree_element,
parent=(element.parent.etree_element if element.parent else None),
base_url=html.base_url)
base_url=html.base_url, target_collector=target_collector)

page_names = set(style['page'] for style in computed_styles.values())

Expand Down Expand Up @@ -878,7 +881,7 @@ def get_all_computed_styles(html, user_stylesheets=None,
pseudo_type=pseudo_type,
# The pseudo-element inherits from the element.
root=html.etree_element, parent=element,
base_url=html.base_url)
base_url=html.base_url, target_collector=target_collector)

# This is mostly useful to make pseudo_type optional.
def style_for(element, pseudo_type=None, __get=computed_styles.get):
Expand Down
147 changes: 117 additions & 30 deletions weasyprint/css/computed_values.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,25 @@
Convert *specified* property values (the result of the cascade and
inhertance) into *computed* values (that are inherited).

:copyright: Copyright 2011-2014 Simon Sapin and contributors, see AUTHORS.
:copyright: Copyright 2011-2018 Simon Sapin and contributors, see AUTHORS.
:license: BSD, see LICENSE for details.

"""

from urllib.parse import unquote

from tinycss2.color3 import parse_color

from .. import text
from ..logger import LOGGER
from ..urls import get_link_attribute
from .properties import INITIAL_VALUES, Dimension
from .utils import (
ANGLE_TO_RADIANS, LENGTH_UNITS, LENGTHS_TO_PIXELS, safe_urljoin)

ZERO_PIXELS = Dimension(0, 'px')


# How many CSS pixels is one <unit>?
# http://www.w3.org/TR/CSS21/syndata.html#length-units
LENGTHS_TO_PIXELS = {
'px': 1,
'pt': 1. / 0.75,
'pc': 16., # LENGTHS_TO_PIXELS['pt'] * 12
'in': 96., # LENGTHS_TO_PIXELS['pt'] * 72
'cm': 96. / 2.54, # LENGTHS_TO_PIXELS['in'] / 2.54
'mm': 96. / 25.4, # LENGTHS_TO_PIXELS['in'] / 25.4
'q': 96. / 25.4 / 4., # LENGTHS_TO_PIXELS['mm'] / 4
}


# Value in pixels of font-size for <absolute-size> keywords: 12pt (16px) for
# medium, and scaling factors given in CSS3 for others:
# http://www.w3.org/TR/css3-fonts/#font-size-prop
Expand Down Expand Up @@ -157,20 +151,20 @@ def decorator(function):


def compute(element, pseudo_type, specified, computed, parent_style,
root_style, base_url):
"""
Return a dict of computed values.
root_style, base_url, target_collector):
"""Create a dict of computed values.

:param element: The HTML element these style apply to
:param pseudo_type: The type of pseudo-element, eg 'before', None
:param specified: a dict of specified values. Should contain
:param specified: A dict of specified values. Should contain
values for all properties.
:param computed: a dict of already known computed values.
:param computed: A dict of already known computed values.
Only contains some properties (or none).
:param parent_style: a dict of computed values of the parent
:param parent_style: A dict of computed values of the parent
element (should contain values for all properties),
or ``None`` if ``element`` is the root element.
:param base_url: The base URL used to resolve relative URLs.
:param target_collector: A target collector used to get computed targets.

"""

Expand All @@ -189,6 +183,7 @@ def computer():
computer.parent_style = parent_style
computer.root_style = root_style
computer.base_url = base_url
computer.target_collector = target_collector

getter = COMPUTER_FUNCTIONS.get

Expand Down Expand Up @@ -400,16 +395,106 @@ def column_gap(computer, name, value):
return length(computer, name, value, pixels_only=True)


def compute_attr_function(computer, values):
# TODO: use real token parsing instead of casting with Python types
func_name, value = values
assert func_name == 'attr()'
attr_name, type_or_unit, fallback = value
# computer.element sometimes is None
# computer.element sometimes is a 'PageType' object without .get()
# so wrapt the .get() into try and return None instead of crashing
try:
attr_value = computer.element.get(attr_name, fallback)
if type_or_unit == 'string':
pass # Keep the string
elif type_or_unit == 'url':
if attr_value.startswith('#'):
attr_value = ('internal', unquote(attr_value[1:]))
else:
attr_value = (
'external', safe_urljoin(computer.base_url, attr_value))
elif type_or_unit == 'color':
attr_value = parse_color(attr_value.strip())
elif type_or_unit == 'integer':
attr_value = int(attr_value.strip())
elif type_or_unit == 'number':
attr_value = float(attr_value.strip())
elif type_or_unit == '%':
attr_value = Dimension(float(attr_value.strip()), '%')
type_or_unit = 'length'
elif type_or_unit in LENGTH_UNITS:
attr_value = Dimension(float(attr_value.strip()), type_or_unit)
type_or_unit = 'length'
elif type_or_unit in ANGLE_TO_RADIANS:
attr_value = Dimension(float(attr_value.strip()), type_or_unit)
type_or_unit = 'angle'
except Exception:
return
return (type_or_unit, attr_value)


def _content_list(computer, values):
computed_values = []
for value in values:
if value[0] in ('string', 'content', 'url', 'quote', 'leader()'):
computed_value = value
elif value[0] == 'attr()':
assert value[1][1] == 'string'
computed_value = compute_attr_function(computer, value)
elif value[0] in ('counter()', 'counters()', 'content()', 'string()'):
# Other values need layout context, their computed value cannot be
# better than their specified value yet.
# See build.compute_content_list.
computed_value = value
elif value[0] in (
'target-counter()', 'target-counters()', 'target-text()'):
anchor_token = value[1][0]
if anchor_token[0] == 'attr()':
attr = compute_attr_function(computer, anchor_token)
if attr is None:
computed_value = None
else:
computed_value = (value[0], (
(attr,) + value[1][1:]))
else:
computed_value = value
if computer.target_collector and computed_value:
computer.target_collector.collect_computed_target(
computed_value[1][0])
if computed_value is None:
LOGGER.warning('Unable to compute %s\'s value for content: %s' % (
computer.element, ', '.join(str(item) for item in value)))
else:
computed_values.append(computed_value)

return tuple(computed_values)


@register_computer('bookmark-label')
def bookmark_label(computer, name, values):
"""Compute the ``bookmark-label`` property."""
return _content_list(computer, values)


@register_computer('string-set')
def string_set(computer, name, values):
"""Compute the ``string-set`` property."""
# Spec asks for strings after custom keywords, but we allow content-lists
return tuple(
(string_set[0], _content_list(computer, string_set[1]))
for string_set in values)


@register_computer('content')
def content(computer, name, values):
"""Compute the ``content`` property."""
if values in ('normal', 'none'):
return values
else:
return tuple(
('STRING', computer.element.get(value, ''))
if type_ == 'attr' else (type_, value)
for type_, value in values)
if len(values) == 1:
value, = values
if value == 'normal':
return 'inhibit' if computer.pseudo_type else 'contents'
elif value == 'none':
return 'inhibit'
return _content_list(computer, values)


@register_computer('display')
Expand Down Expand Up @@ -496,7 +581,9 @@ def anchor(computer, name, values):
"""Compute the ``anchor`` property."""
if values != 'none':
_, key = values
return computer.element.get(key) or None
anchor_name = computer.element.get(key) or None
computer.target_collector.collect_anchor(anchor_name)
return anchor_name


@register_computer('link')
Expand All @@ -506,7 +593,7 @@ def link(computer, name, values):
return None
else:
type_, value = values
if type_ == 'attr':
if type_ == 'attr()':
return get_link_attribute(
computer.element, value, computer.base_url)
else:
Expand All @@ -520,7 +607,7 @@ def lang(computer, name, values):
return None
else:
type_, key = values
if type_ == 'attr':
if type_ == 'attr()':
return computer.element.get(key) or None
elif type_ == 'string':
return key
Expand Down
33 changes: 3 additions & 30 deletions weasyprint/css/properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
'clear': 'none',
'clip': (), # computed value for 'auto'
'color': parse_color('black'), # chosen by the user agent
'content': 'normal',
# Means 'none', but allow `display: list-item` to increment the
# list-item counter. If we ever have a way for authors to query
# computed values (JavaScript?), this value should serialize to 'none'.
Expand Down Expand Up @@ -51,7 +50,6 @@
'padding_right': Dimension(0, 'px'),
'padding_bottom': Dimension(0, 'px'),
'padding_left': Dimension(0, 'px'),
'quotes': list('“”‘’'), # chosen by the user agent
'position': 'static',
'right': 'auto',
'table_layout': 'auto',
Expand Down Expand Up @@ -129,9 +127,11 @@
'orphans': 2,
'widows': 2,

# Generated Content for Paged Media (WD): https://www.w3.org/TR/css-gcpm-3/
# Generated Content 3 (WD): https://www.w3.org/TR/css-content-3/
'bookmark_label': (('content', 'text'),),
'bookmark_level': 'none',
'content': 'normal',
'quotes': list('“”‘’'), # chosen by the user agent
'string_set': 'none',

# Images 3/4 (CR/WD): https://www.w3.org/TR/css4-images/
Expand Down Expand Up @@ -197,33 +197,6 @@

KNOWN_PROPERTIES = set(name.replace('_', '-') for name in INITIAL_VALUES)

# Not applicable to the print media
NOT_PRINT_MEDIA = {
# Aural media:
'azimuth',
'cue',
'cue-after',
'cue-before',
'cursor',
'elevation',
'pause',
'pause-after',
'pause-before',
'pitch-range',
'pitch',
'play-during',
'richness',
'speak-header',
'speak-numeral',
'speak-punctuation',
'speak',
'speech-rate',
'stress',
'voice-family',
'volume',
}


# Do not list shorthand properties here as we handle them before inheritance.
#
# text_decoration is not a really inherited, see
Expand Down