aa''')
+
+
+@assert_no_logs
+def test_current_color_variable_border(assert_pixels):
+ # Regression test for https://github.com/Kozea/WeasyPrint/issues/2010
+ assert_pixels('GG\nGG', '''
+
+
''')
diff --git a/tests/test_variables.py b/tests/test_variables.py
index af9dfa77a..3db12ca55 100644
--- a/tests/test_variables.py
+++ b/tests/test_variables.py
@@ -149,3 +149,22 @@ def test_variable_fallback(prop):
''' % prop)
+
+
+@assert_no_logs
+def test_variable_list():
+ # Regression test for https://github.com/Kozea/WeasyPrint/issues/1287
+ page, = parse('''
+
+
+ ''')
+ html, = page.children
+ body, = html.children
+ div, = body.children
+ line, = div.children
+ before, = line.children
+ text, = before.children
+ assert text.text == 'Page 1/1'
diff --git a/weasyprint/css/__init__.py b/weasyprint/css/__init__.py
index 788a7c9ef..4c708991f 100644
--- a/weasyprint/css/__init__.py
+++ b/weasyprint/css/__init__.py
@@ -22,7 +22,8 @@
from .. import CSS
from ..logger import LOGGER, PROGRESS_LOGGER
from ..urls import URLFetchingError, get_url_attribute, url_join
-from . import computed_values, counters, media_queries
+from . import counters, media_queries
+from .computed_values import compute_variables, COMPUTER_FUNCTIONS, ZERO_PIXELS
from .properties import INHERITED, INITIAL_NOT_COMPUTED, INITIAL_VALUES
from .utils import get_url, remove_whitespace
from .validation import preprocess_declarations
@@ -124,13 +125,13 @@ def __call__(self, element, pseudo_type=None):
style['border_collapse'] == 'collapse'):
# Padding do not apply
for side in ('top', 'bottom', 'left', 'right'):
- style[f'padding_{side}'] = computed_values.ZERO_PIXELS
+ style[f'padding_{side}'] = 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
+ style[f'margin_{side}'] = ZERO_PIXELS
return style
@@ -667,75 +668,69 @@ def copy(self):
def __missing__(self, key):
if key == 'float':
- # Set specified value for position, needed for computed value
+ # Set specified value for position, needed for computed value.
self['position']
elif key == 'display':
- # Set specified value for float, needed for computed value
+ # Set specified value for float, needed for computed value.
self['float']
+ parent_style = self.parent_style
+
if key in self.cascaded:
- value = keyword = self.cascaded[key][0]
+ # Property defined in cascaded properties.
+ keyword, computed = compute_variables(key, self, parent_style)
+ value = keyword
else:
+ # Property not defined in cascaded properties, define as inherited
+ # or initial value.
+ computed = False
if key in INHERITED or key[:2] == '__':
keyword = 'inherit'
else:
keyword = 'initial'
- if keyword == 'inherit' and self.parent_style is None:
+ if keyword == 'inherit' and parent_style is None:
# On the root element, 'inherit' from initial values
keyword = 'initial'
if keyword == 'initial':
value = None if key[:2] == '__' else INITIAL_VALUES[key]
if key not in INITIAL_NOT_COMPUTED:
- # The value is the same as when computed
+ # The value is the same as when computed.
self[key] = value
elif keyword == 'inherit':
# Values in parent_style are already computed.
- self[key] = value = self.parent_style[key]
+ self[key] = value = parent_style[key]
- if key[:16] == 'text_decoration_' and self.parent_style:
+ if key[:16] == 'text_decoration_' and parent_style is not None:
+ # Text decorations are not inherited but propagated. See
+ # https://www.w3.org/TR/css-text-decor-3/#line-decoration.
value = text_decoration(
- key, value, self.parent_style[key], key in self.cascaded)
+ key, value, parent_style[key], key in self.cascaded)
if key in self:
del self[key]
elif key == 'page' and value == 'auto':
- # The page property does not inherit. However, if the page
- # value on an element is auto, then its used value is the value
- # specified on its nearest ancestor with a non-auto value. When
- # specified on the root element, the used value for auto is the
- # empty string.
- value = (
- '' if self.parent_style is None else self.parent_style['page'])
+ # The page property does not inherit. However, if the page value on
+ # an element is auto, then its used value is the value specified on
+ # its nearest ancestor with a non-auto value. When specified on the
+ # root element, the used value for auto is the empty string. See
+ # https://www.w3.org/TR/css-page-3/#using-named-pages.
+ value = '' if parent_style is None else parent_style['page']
if key in self:
del self[key]
elif key in ('position', 'float', 'display'):
+ # Save specified values to define computed values for these
+ # specific properties. See
+ # https://www.w3.org/TR/CSS21/visuren.html#dis-pos-flo.
self.specified[key] = value
if key in self:
+ # Value already computed and saved: return.
return self[key]
- function = computed_values.COMPUTER_FUNCTIONS.get(key)
- already_computed_value = False
-
- if value:
- converted_to_list = False
-
- if not isinstance(value, list):
- converted_to_list = True
- value = [value]
-
- for i, v in enumerate(value):
- value[i], already_computed_value = (
- computed_values.compute_variable(
- v, key, self, self.base_url, self.parent_style))
-
- if converted_to_list:
- value, = value
-
- if function is not None and not already_computed_value:
- value = function(self, key, value)
- # else: same as specified
+ if not computed and key in COMPUTER_FUNCTIONS:
+ # Value not computed yet: compute.
+ value = COMPUTER_FUNCTIONS[key](self, key, value)
self[key] = value
return value
diff --git a/weasyprint/css/computed_values.py b/weasyprint/css/computed_values.py
index 707ee5331..fade56e0b 100644
--- a/weasyprint/css/computed_values.py
+++ b/weasyprint/css/computed_values.py
@@ -211,42 +211,74 @@ def decorator(function):
return decorator
-def compute_variable(value, name, computed, base_url, parent_style):
- already_computed_value = False
+def compute_variables(name, computed_style, parent_style):
+ original_values = computed_style.cascaded[name][0]
- if value and isinstance(value, tuple) and value[0] == 'var()':
- variable_name, default = value[1]
- computed_value = _resolve_var(
- computed, variable_name, default, parent_style)
- if computed_value is None:
- new_value = None
- else:
- prop = PROPERTIES[name.replace('_', '-')]
- if prop.wants_base_url:
- new_value = prop(computed_value, base_url)
+ if isinstance(original_values, list):
+ # Property with multiple values.
+ transformed_to_list = False
+ values = original_values
+ else:
+ # Property with single value, put in a list.
+ transformed_to_list = True
+ values = [original_values]
+
+ # Find variables.
+ variables = {
+ i: value[1] for i, value in enumerate(values)
+ if value and isinstance(value, tuple) and value[0] == 'var()'}
+ if not variables:
+ # No variable, return early.
+ return original_values, False
+
+ if not transformed_to_list:
+ # Don’t modify original list of values.
+ values = values.copy()
+
+ if name in INHERITED and parent_style:
+ inherited = True
+ computed = True
+ else:
+ inherited = False
+ computed = name not in INITIAL_NOT_COMPUTED
+
+ # Replace variables by real values.
+ for i, variable in variables.items():
+ variable_name, default = variable
+ value = _resolve_var(
+ computed_style, variable_name, default, parent_style)
+
+ if value is not None:
+ # Validate value.
+ validator = PROPERTIES[name.replace('_', '-')]
+ if validator.wants_base_url:
+ value = validator(value, computed_style.base_url)
else:
- new_value = prop(computed_value)
+ value = validator(value)
- # See https://drafts.csswg.org/css-variables/#invalid-variables
- if new_value is None:
+ if value is None:
+ # Invalid variable value, see
+ # https://www.w3.org/TR/css-variables-1/#invalid-variables.
with suppress(BaseException):
- computed_value = ''.join(
- token.serialize() for token in computed_value)
+ value = ''.join(token.serialize() for token in value)
LOGGER.warning(
'Unsupported computed value "%s" set in variable %r '
- 'for property %r.', computed_value,
+ 'for property %r.', value,
variable_name.replace('_', '-'), name.replace('_', '-'))
- if name in INHERITED and parent_style:
- already_computed_value = True
- value = parent_style[name]
- else:
- already_computed_value = name not in INITIAL_NOT_COMPUTED
- value = INITIAL_VALUES[name]
- elif isinstance(new_value, list):
- value, = new_value
+ values[i] = (parent_style if inherited else INITIAL_VALUES)[name]
+ elif not transformed_to_list:
+ # Validator returns multiple values. Replace original variable by
+ # possibly multiple computed values.
+ values[i:i+1] = value
else:
- value = new_value
- return value, already_computed_value
+ # Save variable by single computed value.
+ values[i] = value
+
+ if transformed_to_list:
+ # Property with single value, unpack list.
+ values, = values
+
+ return values, computed
@register_computer('background-image')