Skip to content

Commit

Permalink
Allow variables in properties with multiple values
Browse files Browse the repository at this point in the history
Related to #1219.
  • Loading branch information
liZe committed Dec 27, 2023
1 parent c666291 commit 04ca0bc
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 60 deletions.
50 changes: 49 additions & 1 deletion tests/test_variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ def test_variable_fallback(prop):


@assert_no_logs
def test_variable_list():
def test_variable_list_content():
# Regression test for https://github.com/Kozea/WeasyPrint/issues/1287
page, = render_pages('''
<style>
Expand All @@ -331,3 +331,51 @@ def test_variable_list():
before, = line.children
text, = before.children
assert text.text == 'Page 1/1'


@assert_no_logs
@pytest.mark.parametrize('var, display', (
('inline', 'var(--var)'),
('inline-block', 'var(--var)'),
('inline flow', 'var(--var)'),
('inline', 'var(--var) flow'),
('flow', 'inline var(--var)'),
))
def test_variable_list_display(var, display):
page, = render_pages('''
<style>
html { --var: %s }
div { display: %s }
</style>
<section><div></div></section>
''' % (var, display))
html, = page.children
body, = html.children
section, = body.children
child, = section.children
assert type(child).__name__ == 'LineBox'


@assert_no_logs
@pytest.mark.parametrize('var, font', (
('weasyprint', 'var(--var)'),
('"weasyprint"', 'var(--var)'),
('weasyprint', 'var(--var), monospace'),
('weasyprint, monospace', 'var(--var)'),
('monospace', 'weasyprint, var(--var)'),
))
def test_variable_list_font(var, font):
page, = render_pages('''
<style>
@font-face {src: url(weasyprint.otf); font-family: weasyprint}
html { font-size: 2px; --var: %s }
div { font-family: %s }
</style>
<div>aa</div>
''' % (var, font))
html, = page.children
body, = html.children
div, = body.children
line, = div.children
text, = line.children
assert text.width == 4
66 changes: 31 additions & 35 deletions weasyprint/css/computed_values.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from .utils import (
ANGLE_TO_RADIANS, LENGTH_UNITS, LENGTHS_TO_PIXELS, check_var_function,
safe_urljoin)
from .validation.expanders import PendingExpander
from .validation.properties import MULTIVAL_PROPERTIES, PROPERTIES

ZERO_PIXELS = Dimension(0, 'px')
Expand Down Expand Up @@ -216,9 +217,12 @@ def compute_var(name, computed_style, parent_style):
validation_name = name.replace('_', '-')
multiple_values = validation_name in MULTIVAL_PROPERTIES

if isinstance(original_values, PendingExpander):
return original_values, False

if multiple_values:
# Property with multiple values.
values = original_values
values = list(original_values)
else:
# Property with single value, put in a list.
values = [original_values]
Expand All @@ -231,54 +235,46 @@ def compute_var(name, computed_style, parent_style):
# No variable, return early.
return original_values, False

if not multiple_values:
# Don’t modify original list of values.
values = values.copy()

if name in INHERITED and parent_style:
inherited = True
computed = True
default_values = parent_style[name]
else:
inherited = False
computed = name not in INITIAL_NOT_COMPUTED
default_values = INITIAL_VALUES[name]

# Replace variables by real values.
validator = PROPERTIES[validation_name]
for i, variable in variables.items():
variable_name, default = variable
resolved_value = value = resolve_var(
value = resolve_var(
computed_style, variable_name, default, parent_style)

if value is not None:
# Validate value.
if validator.wants_base_url:
value = validator(value, computed_style.base_url)
else:
value = validator(value)

if value is None:
# Invalid variable value, see
# https://www.w3.org/TR/css-variables-1/#invalid-variables.
with suppress(BaseException):
value = ''.join(token.serialize() for token in value)
LOGGER.warning(
'Unsupported computed value "%s" set in variable %r '
'for property %r.', resolved_value,
'Unknown variable %r set for property %r.',
variable_name.replace('_', '-'), validation_name)
values[i] = (parent_style if inherited else INITIAL_VALUES)[name]
elif multiple_values:
# Replace original variable by possibly multiple validated values.
values[i:i+1] = value
computed = False
else:
# Save variable by single validated value.
values[i] = value
computed = False

if not multiple_values:
# Property with single value, unpack list.
values, = values
return default_values, computed
values[i:i+1] = value

# Validate value.
original_values = values
if validator.wants_base_url:
values = validator(values, computed_style.base_url)
else:
values = validator(values)

if values is None:
# Invalid variable value, see
# https://www.w3.org/TR/css-variables-1/#invalid-variables.
with suppress(BaseException):
original_values = ''.join(
token.serialize() for token in original_values)
LOGGER.warning(
'Unsupported computed value "%s" set in variable %r '
'for property %r.', original_values,
variable_name.replace('_', '-'), validation_name)
return default_values, computed

computed = False
return values, computed


Expand Down
61 changes: 37 additions & 24 deletions weasyprint/css/validation/properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ def property(property_name=None, proprietary=False, unstable=False,
the Candidate Recommandation stage. They can be used both
vendor-prefixed or unprefixed.
See https://www.w3.org/TR/CSS/#unstable-syntax
:param multiple_values:
Mark property as returning multiple values.
:param wants_base_url:
The function takes the stylesheet’s base URL as an additional
parameter.
Expand Down Expand Up @@ -91,7 +93,18 @@ def validate_non_shorthand(tokens, name, base_url=None, required=False):
if not required and name not in PROPERTIES:
raise InvalidValues('property not supported yet')

if name not in MULTIVAL_PROPERTIES:
if name in MULTIVAL_PROPERTIES:
var_found = False
new_tokens = []
for token in tokens:
if var_function := check_var_function(token):
new_tokens.append(var_function)
var_found = True
else:
new_tokens.append(token)
if var_found:
return ((name, tuple(new_tokens)),)
else:
for token in tokens:
var_function = check_var_function(token)
if var_function:
Expand All @@ -111,7 +124,7 @@ def validate_non_shorthand(tokens, name, base_url=None, required=False):
return ((name, value),)


@property()
@property(multiple_values=True)
@comma_separated_list
@single_keyword
def background_attachment(keyword):
Expand Down Expand Up @@ -164,7 +177,7 @@ def color(token):
return result


@property('background-image', wants_base_url=True)
@property('background-image', multiple_values=True, wants_base_url=True)
@comma_separated_list
@single_token
def background_image(token, base_url):
Expand All @@ -186,7 +199,7 @@ def list_style_image(token, base_url):
return 'url', parsed_url[1][1]


@property()
@property(multiple_values=True)
def transform_origin(tokens):
"""``transform-origin`` property validation."""
if len(tokens) == 3:
Expand All @@ -195,21 +208,21 @@ def transform_origin(tokens):
return parse_2d_position(tokens)


@property()
@property(multiple_values=True)
@comma_separated_list
def background_position(tokens):
"""``background-position`` property validation."""
return parse_position(tokens)


@property()
@property(multiple_values=True)
@comma_separated_list
def object_position(tokens):
"""``object-position`` property validation."""
return parse_position(tokens)


@property()
@property(multiple_values=True)
@comma_separated_list
def background_repeat(tokens):
"""``background-repeat`` property validation."""
Expand All @@ -226,7 +239,7 @@ def background_repeat(tokens):
return keywords


@property()
@property(multiple_values=True)
@comma_separated_list
def background_size(tokens):
"""Validation for ``background-size``."""
Expand All @@ -252,8 +265,8 @@ def background_size(tokens):
return tuple(values)


@property('background-clip')
@property('background-origin')
@property('background-clip', multiple_values=True)
@property('background-origin', multiple_values=True)
@comma_separated_list
@single_keyword
def box(keyword):
Expand All @@ -262,7 +275,7 @@ def box(keyword):
return keyword in ('border-box', 'padding-box', 'content-box')


@property()
@property(multiple_values=True)
def border_spacing(tokens):
"""Validator for the `border-spacing` property."""
lengths = [get_length(token, negative=False) for token in tokens]
Expand All @@ -273,10 +286,10 @@ def border_spacing(tokens):
return tuple(lengths)


@property('border-top-right-radius')
@property('border-bottom-right-radius')
@property('border-bottom-left-radius')
@property('border-top-left-radius')
@property('border-top-right-radius', multiple_values=True)
@property('border-bottom-right-radius', multiple_values=True)
@property('border-bottom-left-radius', multiple_values=True)
@property('border-top-left-radius', multiple_values=True)
def border_corner_radius(tokens):
"""Validator for the `border-*-radius` properties."""
lengths = [
Expand Down Expand Up @@ -382,7 +395,7 @@ def bleed(token):
return get_length(token)


@property(unstable=True)
@property(unstable=True, multiple_values=True)
def marks(tokens):
"""``marks`` property validation."""
if len(tokens) == 2:
Expand Down Expand Up @@ -616,7 +629,7 @@ def direction(keyword):
return keyword in ('ltr', 'rtl')


@property()
@property(multiple_values=True)
def display(tokens):
"""``display`` property validation."""
for token in tokens:
Expand Down Expand Up @@ -669,7 +682,7 @@ def float_(keyword): # XXX do not hide the "float" builtin
return keyword in ('left', 'right', 'footnote', 'none')


@property()
@property(multiple_values=True)
@comma_separated_list
def font_family(tokens):
"""``font-family`` property validation."""
Expand Down Expand Up @@ -771,7 +784,7 @@ def font_variant_numeric(tokens):
return tuple(values)


@property()
@property(multiple_values=True)
def font_feature_settings(tokens):
"""``font-feature-settings`` property validation."""
if len(tokens) == 1 and get_keyword(tokens[0]) == 'normal':
Expand Down Expand Up @@ -841,7 +854,7 @@ def font_variant_east_asian(tokens):
return tuple(values)


@property()
@property(multiple_values=True)
def font_variation_settings(tokens):
"""``font-variation-settings`` property validation."""
if len(tokens) == 1 and get_keyword(tokens[0]) == 'normal':
Expand Down Expand Up @@ -1088,7 +1101,7 @@ def position(token):
return keyword


@property()
@property(multiple_values=True)
def quotes(tokens):
"""``quotes`` property validation."""
if (tokens and len(tokens) % 2 == 0 and
Expand Down Expand Up @@ -1417,7 +1430,7 @@ def hyphenate_limit_zone(token):
return get_length(token, negative=False, percentage=True)


@property(unstable=True)
@property(unstable=True, multiple_values=True)
def hyphenate_limit_chars(tokens):
"""Validation for ``hyphenate-limit-chars``."""
if len(tokens) == 1:
Expand Down Expand Up @@ -1473,7 +1486,7 @@ def lang(token):
return ('string', token.value)


@property(unstable=True, wants_base_url=True)
@property(unstable=True, multiple_values=True, wants_base_url=True)
def bookmark_label(tokens, base_url):
"""Validation for ``bookmark-label``."""
parsed_tokens = tuple(
Expand Down Expand Up @@ -1515,7 +1528,7 @@ def footnote_policy(keyword):
return keyword in ('auto', 'line', 'block')


@property(unstable=True, wants_base_url=True)
@property(unstable=True, multiple_values=True, wants_base_url=True)
@comma_separated_list
def string_set(tokens, base_url):
"""Validation for ``string-set``."""
Expand Down

0 comments on commit 04ca0bc

Please sign in to comment.