Skip to content

Commit d5dab6e

Browse files
authored
Merge pull request #2675 from Kozea/fix-calc
Fix calc for many properties
2 parents 4dfd05f + e752cb3 commit d5dab6e

File tree

6 files changed

+211
-40
lines changed

6 files changed

+211
-40
lines changed

tests/css/test_math.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ def test_math_functions(width):
9494
'calc(100px, 100px)',
9595
'calc(100px * 100px)',
9696
'calc(100 * 100)',
97+
'calc(calc(100vw))',
9798
'calc(0.1)',
9899
'calc(-1)',
99100
'min()',
@@ -106,6 +107,7 @@ def test_math_functions(width):
106107
'max("10px")',
107108
'max(10, 50px)',
108109
'calc(max(100, 5px) * 10px)',
110+
'calc(100* - max(56px, 1rem)',
109111
'clamp()',
110112
'clamp(10px)',
111113
'clamp(10px, 50px)',
@@ -236,6 +238,37 @@ def test_math_functions_percentage_and_font_unit(css_property):
236238
assert len(math_logs) == len(logs)
237239

238240

241+
@pytest.mark.parametrize('display', [
242+
'block', 'inline', 'flex', 'grid',
243+
'list', 'list-item',
244+
'table', 'table-row-group', 'table-cell',
245+
'inline-block', 'inline-table', 'inline-flex', 'inline-grid',
246+
])
247+
def test_math_functions_display_size(display):
248+
# Regression test for #2673.
249+
render_pages(f'''
250+
<div style="display: {display};
251+
min-width: calc(50% + 1em); max-width: calc(50% + 1em); width: calc(50% + 1em);
252+
min-height: calc(50% + 1em); max-height: calc(50% + 1em); height: calc(50% + 1em)
253+
">
254+
<div style="
255+
min-width: calc(50% + 1em); max-width: calc(50% + 1em); width: calc(50% + 1em);
256+
min-height: calc(50% + 1em); max-height: calc(50% + 1em); height: calc(50% + 1em)
257+
"></div>
258+
</div>
259+
''')
260+
261+
262+
@assert_no_logs
263+
def test_math_functions_hyphenate():
264+
render_pages('''
265+
<div lang="en"
266+
style="hyphens: auto; hyphenate-limit-zone: calc(1em + 100%); width: 2em">
267+
absolute
268+
</div>
269+
''')
270+
271+
239272
@assert_no_logs
240273
def test_math_functions_gradient():
241274
render_pages('''
@@ -262,3 +295,81 @@ def test_math_functions_gradient_color():
262295
rgba(10, 20, calc(30), calc(80%)) 10%,
263296
hsl(calc(10 + 10), 20%, 20%) 80%"></div>
264297
''')
298+
299+
300+
@assert_no_logs
301+
def test_math_image_min_content_calc():
302+
render_pages('''
303+
<table>
304+
<td>
305+
<img src="pattern.png" style="
306+
height: calc(10% + 1em);
307+
width: calc(10% + 1em);
308+
max-height: calc(10% + 1em);
309+
max-width: calc(10% + 1em);
310+
min-height: calc(10% + 1em);
311+
min-width: calc(10% + 1em);
312+
">
313+
''')
314+
315+
316+
@assert_no_logs
317+
def test_math_image_min_content_auto_width_calc():
318+
render_pages('''
319+
<table>
320+
<td>
321+
<img src="pattern.png" style="
322+
height: calc(10% + 1em);
323+
max-height: calc(10% + 1em);
324+
max-width: calc(10% + 1em);
325+
min-height: calc(10% + 1em);
326+
min-width: calc(10% + 1em);
327+
">
328+
''')
329+
330+
331+
@assert_no_logs
332+
def test_math_image_min_content_auto_width_height_calc():
333+
render_pages('''
334+
<table>
335+
<td>
336+
<img src="pattern.png" style="
337+
max-height: calc(10% + 1em);
338+
max-width: calc(10% + 1em);
339+
min-height: calc(10% + 1em);
340+
min-width: calc(10% + 1em);
341+
">
342+
''')
343+
344+
345+
@assert_no_logs
346+
def test_math_table_margin():
347+
render_pages('<table style="margin: calc(1em + 10%)">')
348+
349+
350+
@assert_no_logs
351+
def test_math_grid_padding():
352+
render_pages('''
353+
<article style="display: grid">
354+
<div style="box-sizing: border-box; border: 1px solid;
355+
padding: calc(2px + 10%); width: 7px">a</div>
356+
</article>
357+
''')
358+
359+
360+
@assert_no_logs
361+
def test_math_table_column():
362+
render_pages('''
363+
<table style="width: 200px">
364+
<colgroup style="width: calc(1em + 10%)">
365+
<col />
366+
</colgroup>
367+
<col style="width: calc(1em + 10%)" />
368+
<tbody>
369+
<tr>
370+
<td>a</td>
371+
<td>a</td>
372+
</tr>
373+
</tbody>
374+
</table>
375+
''')

tests/draw/test_text.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,34 @@ def test_text_underline_offset_percentage(assert_pixels):
667667
<div>abc</div>''')
668668

669669

670+
def test_text_underline_offset_calc(assert_pixels):
671+
assert_pixels('''
672+
_____________
673+
_zzzzzzzzzzz_
674+
_zRRRRRRRRRz_
675+
_zRRRRRRRRRz_
676+
_zzzzzzzzzzz_
677+
_zzzzzzzzzzz_
678+
_zBBBBBBBBBz_
679+
_zzzzzzzzzzz_
680+
_____________
681+
''', '''
682+
<style>
683+
@page {
684+
size: 13px 9px;
685+
margin: 2px;
686+
}
687+
body {
688+
color: red;
689+
font-family: weasyprint;
690+
font-size: 3px;
691+
text-decoration: underline blue;
692+
text-underline-offset: calc(0.5em + 20%);
693+
}
694+
</style>
695+
<div>abc</div>''')
696+
697+
670698
def test_text_underline_thickness(assert_pixels):
671699
assert_pixels('''
672700
_____________
@@ -723,6 +751,34 @@ def test_text_underline_thickness_percentage(assert_pixels):
723751
<div>abc</div>''')
724752

725753

754+
def test_text_underline_thickness_calc(assert_pixels):
755+
assert_pixels('''
756+
_____________
757+
_zzzzzzzzzzz_
758+
_zRRRRRRRRRz_
759+
_zRRRRRRRRRz_
760+
_zzzzzzzzzzz_
761+
_zzzzzzzzzzz_
762+
_zBBBBBBBBBz_
763+
_zBBBBBBBBBz_
764+
_zzzzzzzzzzz_
765+
''', '''
766+
<style>
767+
@page {
768+
size: 13px 9px;
769+
margin: 2px;
770+
}
771+
body {
772+
color: red;
773+
font-family: weasyprint;
774+
font-size: 3px;
775+
text-decoration: underline blue calc(0.5em + 50%);
776+
text-underline-offset: 2px;
777+
}
778+
</style>
779+
<div>abc</div>''')
780+
781+
726782
def test_text_overline(assert_pixels):
727783
# Ascent value seems to be a bit random, don’t try to get the exact
728784
# position of the line

weasyprint/css/__init__.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -779,7 +779,6 @@ def resolve_math(token, computed=None, property_name=None, refer_to=None):
779779
return
780780

781781
args = []
782-
original_token = token
783782
function = Function(token)
784783
if function.name is None:
785784
return
@@ -795,7 +794,7 @@ def resolve_math(token, computed=None, property_name=None, refer_to=None):
795794
if function.name == 'calc':
796795
result = _resolve_calc_sum(computed, args[0], property_name, refer_to)
797796
if result is None:
798-
return original_token
797+
return
799798
else:
800799
return tokenize(result)
801800

@@ -1194,10 +1193,10 @@ def __missing__(self, key):
11941193
try:
11951194
token = resolve_math(function, self, key)
11961195
except PercentageInMath:
1197-
token = None
1198-
if token is None:
11991196
solved_tokens.append(function)
12001197
else:
1198+
if token is None:
1199+
raise Exception
12011200
solved_tokens.append(token)
12021201
original_key = key.replace('_', '-')
12031202
value = validate_non_shorthand(solved_tokens, original_key)[0][1]

weasyprint/draw/text.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
def draw_text(stream, textbox, offset_x, text_overflow, block_ellipsis):
1919
"""Draw a textbox to a pydyf stream."""
20+
from ..layout.percent import percentage
21+
2022
# Pango crashes with font-size: 0.
2123
assert textbox.style['font_size']
2224

@@ -30,11 +32,10 @@ def draw_text(stream, textbox, offset_x, text_overflow, block_ellipsis):
3032
if 'underline' in text_decoration_values or 'overline' in text_decoration_values:
3133
if textbox.style['text_decoration_thickness'] in ('auto', 'from-font'):
3234
thickness = textbox.pango_layout.underline_thickness
33-
elif textbox.style['text_decoration_thickness'].unit == '%':
34-
ratio = textbox.style['text_decoration_thickness'].value / 100
35-
thickness = textbox.style['font_size'] * ratio
3635
else:
37-
thickness = textbox.style['text_decoration_thickness'].value
36+
thickness = percentage(
37+
textbox.style['text_decoration_thickness'], textbox.style,
38+
textbox.style['font_size'])
3839
if 'overline' in text_decoration_values:
3940
offset_y = (
4041
textbox.baseline - textbox.pango_layout.ascent + thickness / 2)
@@ -44,11 +45,10 @@ def draw_text(stream, textbox, offset_x, text_overflow, block_ellipsis):
4445
if 'underline' in text_decoration_values:
4546
if textbox.style['text_underline_offset'] == 'auto':
4647
underline_offset = - textbox.pango_layout.underline_position
47-
elif textbox.style['text_underline_offset'].unit == '%':
48-
ratio = textbox.style['text_underline_offset'].value / 100
49-
underline_offset = textbox.style['font_size'] * ratio
5048
else:
51-
underline_offset = textbox.style['text_underline_offset'].value
49+
underline_offset = percentage(
50+
textbox.style['text_underline_offset'], textbox.style,
51+
textbox.style['font_size'])
5252
offset_y = textbox.baseline + underline_offset + thickness / 2
5353
draw_text_decoration(
5454
stream, textbox, offset_x, offset_y, thickness,

weasyprint/layout/preferred.py

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ def _block_content_width(context, box, function, outer):
109109

110110
for value in ('padding_left', 'padding_right'):
111111
style_value = box.style[value]
112-
if style_value != 'auto':
112+
if style_value != 'auto' and not check_math(style_value):
113113
if style_value.unit.lower() == 'px':
114114
width -= style_value.value
115115
else:
@@ -175,7 +175,7 @@ def margin_width(box, width, left=True, right=True):
175175
(['margin_right', 'padding_right'] if right else [])
176176
):
177177
style_value = box.style[value]
178-
if style_value != 'auto':
178+
if style_value != 'auto' and not check_math(style_value):
179179
if style_value.unit.lower() == 'px':
180180
width += style_value.value
181181
else:
@@ -263,7 +263,7 @@ def inline_max_content_width(context, box, outer=True, is_line_start=False):
263263
def column_group_content_width(context, box):
264264
"""Return the *-content width for a ``TableColumnGroupBox``."""
265265
width = box.style['width']
266-
if width == 'auto' or width.unit == '%':
266+
if width == 'auto' or check_math(width) or width.unit == '%':
267267
width = 0
268268
else:
269269
assert width.unit.lower() == 'px'
@@ -597,21 +597,22 @@ def get_percentage_contribution(origin_cell, origin, max_content_width):
597597
# Define constrainedness
598598
constrainedness = [False for i in range(grid_width)]
599599
for i in range(grid_width):
600-
if (column_groups[i] and column_groups[i].style['width'] != 'auto' and
601-
column_groups[i].style['width'].unit != '%'):
602-
constrainedness[i] = True
603-
continue
604-
if (columns[i] and columns[i].style['width'] != 'auto' and
605-
columns[i].style['width'].unit != '%'):
606-
constrainedness[i] = True
607-
continue
608-
for cell in zipped_grid[i]:
609-
if (cell and cell.colspan == 1 and
610-
cell.style['width'] != 'auto' and
611-
not check_math(cell.style['width']) and
612-
cell.style['width'].unit != '%'):
600+
if column_groups[i]:
601+
width = column_groups[i].style['width']
602+
if width != 'auto' and not check_math(width) and width.unit != '%':
613603
constrainedness[i] = True
614-
break
604+
continue
605+
if columns[i]:
606+
width = columns[i].style['width']
607+
if width != 'auto' and not check_math(width) and width.unit != '%':
608+
constrainedness[i] = True
609+
continue
610+
for cell in zipped_grid[i]:
611+
if cell and cell.colspan == 1:
612+
width = cell.style['width']
613+
if width != 'auto' and not check_math(width) and width.unit != '%':
614+
constrainedness[i] = True
615+
break
615616

616617
intrinsic_percentages = [
617618
min(percentage, 100 - sum(intrinsic_percentages[:i]))
@@ -679,7 +680,8 @@ def get_percentage_contribution(origin_cell, origin, max_content_width):
679680
sum(max_content_widths), large_percentage_contribution,
680681
*small_percentage_contributions]))
681682

682-
if table.style['width'] != 'auto' and table.style['width'].unit.lower() == 'px':
683+
width = table.style['width']
684+
if width != 'auto' and not check_math(width) and width.unit.lower() == 'px':
683685
# "percentages on the following properties are treated instead as
684686
# though they were the following: width: auto"
685687
# https://dbaron.org/css/intrinsic/#outer-intrinsic
@@ -714,12 +716,16 @@ def replaced_min_content_width(box, outer=True):
714716
width = box.style['width']
715717
if width == 'auto':
716718
height = box.style['height']
717-
if height == 'auto' or height.unit == '%':
719+
if height == 'auto' or check_math(height) or height.unit == '%':
718720
height = 'auto'
719721
else:
720722
assert height.unit.lower() == 'px'
721723
height = height.value
722-
if box.style['max_width'] != 'auto' and box.style['max_width'].unit == '%':
724+
unknown_max_width = (
725+
box.style['max_width'] != 'auto' and
726+
not check_math(box.style['max_width']) and
727+
box.style['max_width'].unit == '%')
728+
if unknown_max_width:
723729
# See https://drafts.csswg.org/css-sizing/#intrinsic-contribution
724730
width = 0
725731
else:
@@ -730,7 +736,7 @@ def replaced_min_content_width(box, outer=True):
730736
width, _ = default_image_sizing(
731737
intrinsic_width, intrinsic_height, intrinsic_ratio, 'auto',
732738
height, default_width=0, default_height=0)
733-
elif box.style['width'].unit == '%':
739+
elif check_math(box.style['width']) or box.style['width'].unit == '%':
734740
# See https://drafts.csswg.org/css-sizing/#intrinsic-contribution
735741
width = 0
736742
else:
@@ -744,7 +750,7 @@ def replaced_max_content_width(box, outer=True):
744750
width = box.style['width']
745751
if width == 'auto':
746752
height = box.style['height']
747-
if height == 'auto' or height.unit == '%':
753+
if height == 'auto' or check_math(height) or height.unit == '%':
748754
height = 'auto'
749755
else:
750756
assert height.unit.lower() == 'px'
@@ -756,7 +762,7 @@ def replaced_max_content_width(box, outer=True):
756762
width, _ = default_image_sizing(
757763
intrinsic_width, intrinsic_height, intrinsic_ratio, 'auto', height,
758764
default_width=300, default_height=150)
759-
elif box.style['width'].unit == '%':
765+
elif check_math(box.style['width']) or box.style['width'].unit == '%':
760766
# See https://drafts.csswg.org/css-sizing/#intrinsic-contribution
761767
width = 0
762768
else:

0 commit comments

Comments
 (0)