mirror of
https://github.com/Kozea/WeasyPrint.git
synced 2024-09-11 20:47:56 +03:00
Merge pull request #2139 from xavidotron/main
Add support for mask-border-* properties
This commit is contained in:
commit
dd57e17632
@ -454,6 +454,74 @@ def test_border_image_invalid(rule, reason):
|
|||||||
assert_invalid(f'border-image: {rule}', reason)
|
assert_invalid(f'border-image: {rule}', reason)
|
||||||
|
|
||||||
|
|
||||||
|
@assert_no_logs
|
||||||
|
@pytest.mark.parametrize('rule, result', (
|
||||||
|
('url(border.png) 27', {
|
||||||
|
'mask_border_source': ('url', 'https://weasyprint.org/foo/border.png'),
|
||||||
|
'mask_border_slice': ((27, None),),
|
||||||
|
}),
|
||||||
|
('url(border.png) 10 / 4 / 2 round stretch', {
|
||||||
|
'mask_border_source': ('url', 'https://weasyprint.org/foo/border.png'),
|
||||||
|
'mask_border_slice': ((10, None),),
|
||||||
|
'mask_border_width': ((4, None),),
|
||||||
|
'mask_border_outset': ((2, None),),
|
||||||
|
'mask_border_repeat': (('round', 'stretch')),
|
||||||
|
}),
|
||||||
|
('10 // 2', {
|
||||||
|
'mask_border_slice': ((10, None),),
|
||||||
|
'mask_border_outset': ((2, None),),
|
||||||
|
}),
|
||||||
|
('5.5%', {
|
||||||
|
'mask_border_slice': ((5.5, '%'),),
|
||||||
|
}),
|
||||||
|
('stretch 2 url("border.png")', {
|
||||||
|
'mask_border_source': ('url', 'https://weasyprint.org/foo/border.png'),
|
||||||
|
'mask_border_slice': ((2, None),),
|
||||||
|
'mask_border_repeat': (('stretch',)),
|
||||||
|
}),
|
||||||
|
('1/2 round', {
|
||||||
|
'mask_border_slice': ((1, None),),
|
||||||
|
'mask_border_width': ((2, None),),
|
||||||
|
'mask_border_repeat': (('round',)),
|
||||||
|
}),
|
||||||
|
('none', {
|
||||||
|
'mask_border_source': ('none', None),
|
||||||
|
}),
|
||||||
|
('url(border.png) 27 alpha', {
|
||||||
|
'mask_border_source': ('url', 'https://weasyprint.org/foo/border.png'),
|
||||||
|
'mask_border_slice': ((27, None),),
|
||||||
|
'mask_border_mode': 'alpha',
|
||||||
|
}),
|
||||||
|
('url(border.png) 27 luminance', {
|
||||||
|
'mask_border_source': ('url', 'https://weasyprint.org/foo/border.png'),
|
||||||
|
'mask_border_slice': ((27, None),),
|
||||||
|
'mask_border_mode': 'luminance',
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
def test_mask_border(rule, result):
|
||||||
|
assert expand_to_dict(f'mask-border: {rule}') == result
|
||||||
|
|
||||||
|
|
||||||
|
@assert_no_logs
|
||||||
|
@pytest.mark.parametrize('rule, reason', (
|
||||||
|
('url(border.png) url(border.png)', 'multiple source'),
|
||||||
|
('10 10 10 10 10', 'multiple slice'),
|
||||||
|
('1 / 2 / 3 / 4', 'invalid'),
|
||||||
|
('/1', 'invalid'),
|
||||||
|
('/1', 'invalid'),
|
||||||
|
('round round round', 'invalid'),
|
||||||
|
('-1', 'invalid'),
|
||||||
|
('1 repeat 2', 'multiple slice'),
|
||||||
|
('1% // 1%', 'invalid'),
|
||||||
|
('1 / repeat', 'invalid'),
|
||||||
|
('', 'no value'),
|
||||||
|
('alpha alpha', 'multiple mode'),
|
||||||
|
('alpha luminance', 'multiple mode'),
|
||||||
|
))
|
||||||
|
def test_mask_border_invalid(rule, reason):
|
||||||
|
assert_invalid(f'mask-border: {rule}', reason)
|
||||||
|
|
||||||
|
|
||||||
@assert_no_logs
|
@assert_no_logs
|
||||||
@pytest.mark.parametrize('rule, result', (
|
@pytest.mark.parametrize('rule, result', (
|
||||||
('12px My Fancy Font, serif', {
|
('12px My Fancy Font, serif', {
|
||||||
|
@ -470,6 +470,135 @@ def test_border_image_repeat_invalid(rule):
|
|||||||
assert_invalid(f'border-image-repeat: {rule}')
|
assert_invalid(f'border-image-repeat: {rule}')
|
||||||
|
|
||||||
|
|
||||||
|
@assert_no_logs
|
||||||
|
@pytest.mark.parametrize('rule, value', (
|
||||||
|
('1', ((1, None),)),
|
||||||
|
('1 2 3 4', ((1, None), (2, None), (3, None), (4, None))),
|
||||||
|
('50% 1000.1 0', ((50, '%'), (1000.1, None), (0, None))),
|
||||||
|
('1% 2% 3% 4%', ((1, '%'), (2, '%'), (3, '%'), (4, '%'))),
|
||||||
|
('fill 10% 20', ('fill', (10, '%'), (20, None))),
|
||||||
|
('0 1 0.5 fill', ((0, None), (1, None), (0.5, None), 'fill')),
|
||||||
|
))
|
||||||
|
def test_mask_border_slice(rule, value):
|
||||||
|
assert get_value(f'mask-border-slice: {rule}') == value
|
||||||
|
|
||||||
|
|
||||||
|
@assert_no_logs
|
||||||
|
@pytest.mark.parametrize('rule', (
|
||||||
|
'none',
|
||||||
|
'1, 2',
|
||||||
|
'-10',
|
||||||
|
'-10%',
|
||||||
|
'1 2 3 -10%',
|
||||||
|
'-0.3',
|
||||||
|
'1 fill 2',
|
||||||
|
'fill 1 2 3 fill',
|
||||||
|
))
|
||||||
|
def test_mask_border_slice_invalid(rule):
|
||||||
|
assert_invalid(f'mask-border-slice: {rule}')
|
||||||
|
|
||||||
|
|
||||||
|
@assert_no_logs
|
||||||
|
@pytest.mark.parametrize('rule, value', (
|
||||||
|
('1', ((1, None),)),
|
||||||
|
('1 2 3 4', ((1, None), (2, None), (3, None), (4, None))),
|
||||||
|
('50% 1000.1 0', ((50, '%'), (1000.1, None), (0, None))),
|
||||||
|
('1% 2px 3em 4', ((1, '%'), (2, 'px'), (3, 'em'), (4, None))),
|
||||||
|
('auto', ('auto',)),
|
||||||
|
('1 auto', ((1, None), 'auto')),
|
||||||
|
('auto auto', ('auto', 'auto')),
|
||||||
|
('auto auto auto 2', ('auto', 'auto', 'auto', (2, None))),
|
||||||
|
))
|
||||||
|
def test_mask_border_width(rule, value):
|
||||||
|
assert get_value(f'mask-border-width: {rule}') == value
|
||||||
|
|
||||||
|
|
||||||
|
@assert_no_logs
|
||||||
|
@pytest.mark.parametrize('rule', (
|
||||||
|
'none',
|
||||||
|
'1, 2',
|
||||||
|
'1 -2',
|
||||||
|
'-10',
|
||||||
|
'-10%',
|
||||||
|
'1px 2px 3px -10%',
|
||||||
|
'-3px',
|
||||||
|
'auto auto auto auto auto',
|
||||||
|
'1 2 3 4 5',
|
||||||
|
))
|
||||||
|
def test_mask_border_width_invalid(rule):
|
||||||
|
assert_invalid(f'mask-border-width: {rule}')
|
||||||
|
|
||||||
|
|
||||||
|
@assert_no_logs
|
||||||
|
@pytest.mark.parametrize('rule, value', (
|
||||||
|
('1', ((1, None),)),
|
||||||
|
('1 2 3 4', ((1, None), (2, None), (3, None), (4, None))),
|
||||||
|
('50px 1000.1 0', ((50, 'px'), (1000.1, None), (0, None))),
|
||||||
|
('1in 2px 3em 4', ((1, 'in'), (2, 'px'), (3, 'em'), (4, None))),
|
||||||
|
))
|
||||||
|
def test_mask_border_outset(rule, value):
|
||||||
|
assert get_value(f'mask-border-outset: {rule}') == value
|
||||||
|
|
||||||
|
|
||||||
|
@assert_no_logs
|
||||||
|
@pytest.mark.parametrize('rule', (
|
||||||
|
'none',
|
||||||
|
'auto',
|
||||||
|
'1, 2',
|
||||||
|
'-10',
|
||||||
|
'1 -2',
|
||||||
|
'10%',
|
||||||
|
'1px 2px 3px -10px',
|
||||||
|
'-3px',
|
||||||
|
'1 2 3 4 5',
|
||||||
|
))
|
||||||
|
def test_mask_border_outset_invalid(rule):
|
||||||
|
assert_invalid(f'mask-border-outset: {rule}')
|
||||||
|
|
||||||
|
|
||||||
|
@assert_no_logs
|
||||||
|
@pytest.mark.parametrize('rule, value', (
|
||||||
|
('stretch', ('stretch',)),
|
||||||
|
('repeat repeat', ('repeat', 'repeat')),
|
||||||
|
('round space', ('round', 'space')),
|
||||||
|
))
|
||||||
|
def test_mask_border_repeat(rule, value):
|
||||||
|
assert get_value(f'mask-border-repeat: {rule}') == value
|
||||||
|
|
||||||
|
|
||||||
|
@assert_no_logs
|
||||||
|
@pytest.mark.parametrize('rule', (
|
||||||
|
'none',
|
||||||
|
'test',
|
||||||
|
'round round round',
|
||||||
|
'stretch space round',
|
||||||
|
'repeat test',
|
||||||
|
))
|
||||||
|
def test_mask_border_repeat_invalid(rule):
|
||||||
|
assert_invalid(f'mask-border-repeat: {rule}')
|
||||||
|
|
||||||
|
|
||||||
|
@assert_no_logs
|
||||||
|
@pytest.mark.parametrize('rule, value', (
|
||||||
|
('alpha', 'alpha'),
|
||||||
|
('luminance', 'luminance'),
|
||||||
|
('alpha ', 'alpha'),
|
||||||
|
))
|
||||||
|
def test_mask_border_mode(rule, value):
|
||||||
|
assert get_value(f'mask-border-mode: {rule}') == value
|
||||||
|
|
||||||
|
|
||||||
|
@assert_no_logs
|
||||||
|
@pytest.mark.parametrize('rule', (
|
||||||
|
'none',
|
||||||
|
'test',
|
||||||
|
'alpha alpha',
|
||||||
|
'alpha luminance',
|
||||||
|
))
|
||||||
|
def test_mask_border_mode_invalid(rule):
|
||||||
|
assert_invalid(f'mask-border-mode: {rule}')
|
||||||
|
|
||||||
|
|
||||||
@assert_no_logs
|
@assert_no_logs
|
||||||
@pytest.mark.parametrize('rule, value', (
|
@pytest.mark.parametrize('rule, value', (
|
||||||
('test content(text)', (('test', (('content()', 'text'),)),)),
|
('test content(text)', (('test', (('content()', 'text'),)),)),
|
||||||
|
@ -576,3 +576,65 @@ def test_border_image_gradient(assert_pixels):
|
|||||||
</style>
|
</style>
|
||||||
<div></div>
|
<div></div>
|
||||||
''')
|
''')
|
||||||
|
|
||||||
|
|
||||||
|
@assert_no_logs
|
||||||
|
def test_mask_border(assert_pixels):
|
||||||
|
assert_pixels('''
|
||||||
|
__________
|
||||||
|
__RR__RRR_
|
||||||
|
_R______R_
|
||||||
|
_R______R_
|
||||||
|
_s______R_
|
||||||
|
_s______R_
|
||||||
|
_R______R_
|
||||||
|
_R______R_
|
||||||
|
__RRRRRRR_
|
||||||
|
__________
|
||||||
|
''', '''
|
||||||
|
<style>
|
||||||
|
@page {
|
||||||
|
size: 10px 10px;
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
background: red;
|
||||||
|
mask-border-source: url(mask.svg);
|
||||||
|
mask-border-slice: 20%;
|
||||||
|
height: 8px;
|
||||||
|
width: 8px;
|
||||||
|
margin: 1px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div></div>
|
||||||
|
''')
|
||||||
|
|
||||||
|
|
||||||
|
@assert_no_logs
|
||||||
|
def test_mask_border_fill(assert_pixels):
|
||||||
|
assert_pixels('''
|
||||||
|
__________
|
||||||
|
__RR__RRR_
|
||||||
|
_RRRRRRRR_
|
||||||
|
_RRRRRRRR_
|
||||||
|
_sRR__RRR_
|
||||||
|
_sRR__RRR_
|
||||||
|
_RRRRRRRR_
|
||||||
|
_RRRRRRRR_
|
||||||
|
__RRRRRRR_
|
||||||
|
__________
|
||||||
|
''', '''
|
||||||
|
<style>
|
||||||
|
@page {
|
||||||
|
size: 10px 10px;
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
background: red;
|
||||||
|
mask-border-source: url(mask.svg);
|
||||||
|
mask-border-slice: 20% fill;
|
||||||
|
height: 8px;
|
||||||
|
width: 8px;
|
||||||
|
margin: 1px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div></div>
|
||||||
|
''')
|
||||||
|
4
tests/resources/mask.svg
Normal file
4
tests/resources/mask.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<svg height="5" width="5" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M 1 0 L 1 1 L 0 1 L 0 2 L 1 2 L 1 3 L 0 3 L 0 4 L 1 4 L 1 5 L 2 5 L 3 5 L 4 5 L 5 5 L 5 4 L 5 3 L 5 2 L 5 1 L 5 0 L 4 0 L 3 0 L 3 1 L 2 1 L 2 0 L 1 0 z M 2 2 L 3 2 L 3 3 L 2 3 L 2 2 z " />
|
||||||
|
<rect fill="black" opacity="0.5" width="1" height="1" x="0" y="2" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 340 B |
@ -362,6 +362,7 @@ def border_width(style, name, value):
|
|||||||
|
|
||||||
|
|
||||||
@register_computer('border-image-slice')
|
@register_computer('border-image-slice')
|
||||||
|
@register_computer('mask-border-slice')
|
||||||
def border_image_slice(style, name, values):
|
def border_image_slice(style, name, values):
|
||||||
"""Compute the ``border-image-slice`` property."""
|
"""Compute the ``border-image-slice`` property."""
|
||||||
computed_values = []
|
computed_values = []
|
||||||
@ -385,6 +386,7 @@ def border_image_slice(style, name, values):
|
|||||||
|
|
||||||
|
|
||||||
@register_computer('border-image-width')
|
@register_computer('border-image-width')
|
||||||
|
@register_computer('mask-border-width')
|
||||||
def border_image_width(style, name, values):
|
def border_image_width(style, name, values):
|
||||||
"""Compute the ``border-image-width`` property."""
|
"""Compute the ``border-image-width`` property."""
|
||||||
computed_values = []
|
computed_values = []
|
||||||
@ -404,6 +406,7 @@ def border_image_width(style, name, values):
|
|||||||
|
|
||||||
|
|
||||||
@register_computer('border-image-outset')
|
@register_computer('border-image-outset')
|
||||||
|
@register_computer('mask-border-outset')
|
||||||
def border_image_outset(style, name, values):
|
def border_image_outset(style, name, values):
|
||||||
"""Compute the ``border-image-outset`` property."""
|
"""Compute the ``border-image-outset`` property."""
|
||||||
computed_values = [
|
computed_values = [
|
||||||
@ -419,6 +422,7 @@ def border_image_outset(style, name, values):
|
|||||||
|
|
||||||
|
|
||||||
@register_computer('border-image-repeat')
|
@register_computer('border-image-repeat')
|
||||||
|
@register_computer('mask-border-repeat')
|
||||||
def border_image_repeat(style, name, values):
|
def border_image_repeat(style, name, values):
|
||||||
"""Compute the ``border-image-repeat`` property."""
|
"""Compute the ``border-image-repeat`` property."""
|
||||||
return (values * 2) if len(values) == 1 else values
|
return (values * 2) if len(values) == 1 else values
|
||||||
|
@ -77,6 +77,17 @@ INITIAL_VALUES = {
|
|||||||
Dimension(0, None), Dimension(0, None),
|
Dimension(0, None), Dimension(0, None),
|
||||||
Dimension(0, None), Dimension(0, None)),
|
Dimension(0, None), Dimension(0, None)),
|
||||||
'border_image_repeat': ('stretch', 'stretch'),
|
'border_image_repeat': ('stretch', 'stretch'),
|
||||||
|
'mask_border_source': ('none', None),
|
||||||
|
'mask_border_slice': (
|
||||||
|
Dimension(100, '%'), Dimension(100, '%'),
|
||||||
|
Dimension(100, '%'), Dimension(100, '%'),
|
||||||
|
None),
|
||||||
|
'mask_border_width': ('auto', 'auto', 'auto', 'auto'),
|
||||||
|
'mask_border_outset': (
|
||||||
|
Dimension(0, None), Dimension(0, None),
|
||||||
|
Dimension(0, None), Dimension(0, None)),
|
||||||
|
'mask_border_repeat': ('stretch', 'stretch'),
|
||||||
|
'mask_border_mode': 'alpha',
|
||||||
|
|
||||||
|
|
||||||
# Color 3 (REC): https://www.w3.org/TR/css-color-3/
|
# Color 3 (REC): https://www.w3.org/TR/css-color-3/
|
||||||
|
@ -18,8 +18,8 @@ from .properties import ( # isort:skip
|
|||||||
border_width, box, column_count, column_width, flex_basis, flex_direction,
|
border_width, box, column_count, column_width, flex_basis, flex_direction,
|
||||||
flex_grow_shrink, flex_wrap, font_family, font_size, font_stretch,
|
flex_grow_shrink, flex_wrap, font_family, font_size, font_stretch,
|
||||||
font_style, font_weight, gap, grid_line, grid_template, line_height,
|
font_style, font_weight, gap, grid_line, grid_template, line_height,
|
||||||
list_style_image, list_style_position, list_style_type, other_colors,
|
list_style_image, list_style_position, list_style_type, mask_border_mode,
|
||||||
overflow_wrap, validate_non_shorthand)
|
other_colors, overflow_wrap, validate_non_shorthand)
|
||||||
|
|
||||||
EXPANDERS = {}
|
EXPANDERS = {}
|
||||||
|
|
||||||
@ -350,6 +350,72 @@ def expand_border_image(tokens, name, base_url):
|
|||||||
raise InvalidValues
|
raise InvalidValues
|
||||||
|
|
||||||
|
|
||||||
|
@expander('mask-border')
|
||||||
|
@generic_expander('-outset', '-repeat', '-slice', '-source', '-width', '-mode',
|
||||||
|
wants_base_url=True)
|
||||||
|
def expand_mask_border(tokens, name, base_url):
|
||||||
|
"""Expand the ``mask-border-*`` shorthand properties.
|
||||||
|
|
||||||
|
See https://drafts.fxtf.org/css-masking/#the-mask-border
|
||||||
|
|
||||||
|
"""
|
||||||
|
tokens = list(tokens)
|
||||||
|
while tokens:
|
||||||
|
if border_image_source(tokens[:1], base_url):
|
||||||
|
yield '-source', [tokens.pop(0)]
|
||||||
|
elif mask_border_mode(tokens[:1]):
|
||||||
|
yield '-mode', [tokens.pop(0)]
|
||||||
|
elif border_image_repeat(tokens[:1]):
|
||||||
|
repeats = [tokens.pop(0)]
|
||||||
|
while tokens and border_image_repeat(tokens[:1]):
|
||||||
|
repeats.append(tokens.pop(0))
|
||||||
|
yield '-repeat', repeats
|
||||||
|
elif border_image_slice(tokens[:1]) or get_keyword(tokens[0]) == 'fill':
|
||||||
|
slices = [tokens.pop(0)]
|
||||||
|
while tokens and border_image_slice(slices + tokens[:1]):
|
||||||
|
slices.append(tokens.pop(0))
|
||||||
|
yield '-slice', slices
|
||||||
|
if tokens and tokens[0].type == 'literal' and tokens[0].value == '/':
|
||||||
|
# slices / *
|
||||||
|
tokens.pop(0)
|
||||||
|
else:
|
||||||
|
# slices other
|
||||||
|
continue
|
||||||
|
if not tokens:
|
||||||
|
# slices /
|
||||||
|
raise InvalidValues
|
||||||
|
if border_image_width(tokens[:1]):
|
||||||
|
widths = [tokens.pop(0)]
|
||||||
|
while tokens and border_image_width(widths + tokens[:1]):
|
||||||
|
widths.append(tokens.pop(0))
|
||||||
|
yield '-width', widths
|
||||||
|
if tokens and tokens[0].type == 'literal' and tokens[0].value == '/':
|
||||||
|
# slices / widths / slash *
|
||||||
|
tokens.pop(0)
|
||||||
|
else:
|
||||||
|
# slices / widths other
|
||||||
|
continue
|
||||||
|
elif tokens and tokens[0].type == 'literal' and tokens[0].value == '/':
|
||||||
|
# slices / / *
|
||||||
|
tokens.pop(0)
|
||||||
|
else:
|
||||||
|
# slices / other
|
||||||
|
raise InvalidValues
|
||||||
|
if not tokens:
|
||||||
|
# slices / * /
|
||||||
|
raise InvalidValues
|
||||||
|
if border_image_outset(tokens[:1]):
|
||||||
|
outsets = [tokens.pop(0)]
|
||||||
|
while tokens and border_image_outset(outsets + tokens[:1]):
|
||||||
|
outsets.append(tokens.pop(0))
|
||||||
|
yield '-outset', outsets
|
||||||
|
else:
|
||||||
|
# slash / * / other
|
||||||
|
raise InvalidValues
|
||||||
|
else:
|
||||||
|
raise InvalidValues
|
||||||
|
|
||||||
|
|
||||||
@expander('background')
|
@expander('background')
|
||||||
def expand_background(tokens, name, base_url):
|
def expand_background(tokens, name, base_url):
|
||||||
"""Expand the ``background`` shorthand property.
|
"""Expand the ``background`` shorthand property.
|
||||||
|
@ -421,7 +421,8 @@ def border_width(token):
|
|||||||
return keyword
|
return keyword
|
||||||
|
|
||||||
|
|
||||||
@property(wants_base_url=True)
|
@property('border-image-source', wants_base_url=True)
|
||||||
|
@property('mask-border-source', wants_base_url=True)
|
||||||
@single_token
|
@single_token
|
||||||
def border_image_source(token, base_url):
|
def border_image_source(token, base_url):
|
||||||
if get_keyword(token) == 'none':
|
if get_keyword(token) == 'none':
|
||||||
@ -429,7 +430,8 @@ def border_image_source(token, base_url):
|
|||||||
return get_image(token, base_url)
|
return get_image(token, base_url)
|
||||||
|
|
||||||
|
|
||||||
@property()
|
@property('border-image-slice')
|
||||||
|
@property('mask-border-slice')
|
||||||
def border_image_slice(tokens):
|
def border_image_slice(tokens):
|
||||||
values = []
|
values = []
|
||||||
fill = False
|
fill = False
|
||||||
@ -449,7 +451,8 @@ def border_image_slice(tokens):
|
|||||||
return tuple(values)
|
return tuple(values)
|
||||||
|
|
||||||
|
|
||||||
@property()
|
@property('border-image-width')
|
||||||
|
@property('mask-border-width')
|
||||||
def border_image_width(tokens):
|
def border_image_width(tokens):
|
||||||
values = []
|
values = []
|
||||||
for token in tokens:
|
for token in tokens:
|
||||||
@ -467,7 +470,8 @@ def border_image_width(tokens):
|
|||||||
return tuple(values)
|
return tuple(values)
|
||||||
|
|
||||||
|
|
||||||
@property()
|
@property('border-image-outset')
|
||||||
|
@property('mask-border-outset')
|
||||||
def border_image_outset(tokens):
|
def border_image_outset(tokens):
|
||||||
values = []
|
values = []
|
||||||
for token in tokens:
|
for token in tokens:
|
||||||
@ -483,7 +487,8 @@ def border_image_outset(tokens):
|
|||||||
return tuple(values)
|
return tuple(values)
|
||||||
|
|
||||||
|
|
||||||
@property()
|
@property('border-image-repeat')
|
||||||
|
@property('mask-border-repeat')
|
||||||
def border_image_repeat(tokens):
|
def border_image_repeat(tokens):
|
||||||
if 1 <= len(tokens) <= 2:
|
if 1 <= len(tokens) <= 2:
|
||||||
keywords = tuple(get_keyword(token) for token in tokens)
|
keywords = tuple(get_keyword(token) for token in tokens)
|
||||||
@ -491,6 +496,12 @@ def border_image_repeat(tokens):
|
|||||||
return keywords
|
return keywords
|
||||||
|
|
||||||
|
|
||||||
|
@property()
|
||||||
|
@single_keyword
|
||||||
|
def mask_border_mode(keyword):
|
||||||
|
return keyword in ('luminance', 'alpha')
|
||||||
|
|
||||||
|
|
||||||
@property(unstable=True)
|
@property(unstable=True)
|
||||||
@single_token
|
@single_token
|
||||||
def column_width(token):
|
def column_width(token):
|
||||||
|
@ -62,6 +62,7 @@ def draw_page(page, stream):
|
|||||||
draw_background(
|
draw_background(
|
||||||
stream, stacking_context.box.background, clip_box=False,
|
stream, stacking_context.box.background, clip_box=False,
|
||||||
bleed=page.bleed, marks=marks)
|
bleed=page.bleed, marks=marks)
|
||||||
|
set_mask_border(stream, page)
|
||||||
draw_background(stream, page.canvas_background, clip_box=False)
|
draw_background(stream, page.canvas_background, clip_box=False)
|
||||||
draw_border(stream, page)
|
draw_border(stream, page)
|
||||||
draw_stacking_context(stream, stacking_context)
|
draw_stacking_context(stream, stacking_context)
|
||||||
@ -116,6 +117,7 @@ def draw_stacking_context(stream, stacking_context):
|
|||||||
if isinstance(box, (boxes.BlockBox, boxes.MarginBox,
|
if isinstance(box, (boxes.BlockBox, boxes.MarginBox,
|
||||||
boxes.InlineBlockBox, boxes.TableCellBox,
|
boxes.InlineBlockBox, boxes.TableCellBox,
|
||||||
boxes.FlexContainerBox, boxes.ReplacedBox)):
|
boxes.FlexContainerBox, boxes.ReplacedBox)):
|
||||||
|
set_mask_border(stream, box)
|
||||||
# The canvas background was removed by layout_backgrounds
|
# The canvas background was removed by layout_backgrounds
|
||||||
draw_background(stream, box.background)
|
draw_background(stream, box.background)
|
||||||
draw_border(stream, box)
|
draw_border(stream, box)
|
||||||
@ -137,6 +139,8 @@ def draw_stacking_context(stream, stacking_context):
|
|||||||
|
|
||||||
# Point 4
|
# Point 4
|
||||||
for block in stacking_context.block_level_boxes:
|
for block in stacking_context.block_level_boxes:
|
||||||
|
set_mask_border(stream, block)
|
||||||
|
|
||||||
if isinstance(block, boxes.TableBox):
|
if isinstance(block, boxes.TableBox):
|
||||||
draw_table(stream, block)
|
draw_table(stream, block)
|
||||||
else:
|
else:
|
||||||
@ -441,7 +445,10 @@ def draw_border(stream, box):
|
|||||||
|
|
||||||
# If there's a border image, that takes precedence.
|
# If there's a border image, that takes precedence.
|
||||||
if box.style['border_image_source'][0] != 'none' and box.border_image is not None:
|
if box.style['border_image_source'][0] != 'none' and box.border_image is not None:
|
||||||
draw_border_image(box, stream)
|
draw_border_image(
|
||||||
|
box, stream, box.border_image, box.style['border_image_slice'],
|
||||||
|
box.style['border_image_repeat'], box.style['border_image_outset'],
|
||||||
|
box.style['border_image_width'])
|
||||||
return
|
return
|
||||||
|
|
||||||
widths = [getattr(box, f'border_{side}_width') for side in SIDES]
|
widths = [getattr(box, f'border_{side}_width') for side in SIDES]
|
||||||
@ -479,18 +486,18 @@ def draw_border(stream, box):
|
|||||||
stream, box, style, styled_color(style, color, side))
|
stream, box, style, styled_color(style, color, side))
|
||||||
|
|
||||||
|
|
||||||
def draw_border_image(box, stream):
|
def draw_border_image(box, stream, image, border_slice, border_repeat, border_outset,
|
||||||
"""Draw ``box`` border image on ``stream``."""
|
border_width):
|
||||||
# See https://drafts.csswg.org/css-backgrounds-3/#border-images
|
"""Draw ``image`` as a border image for ``box`` on ``stream`` as specified."""
|
||||||
image = box.border_image
|
# Shared by border-image-* and mask-border-*
|
||||||
width, height, ratio = image.get_intrinsic_size(
|
width, height, ratio = image.get_intrinsic_size(
|
||||||
box.style['image_resolution'], box.style['font_size'])
|
box.style['image_resolution'], box.style['font_size'])
|
||||||
intrinsic_width, intrinsic_height = replaced.default_image_sizing(
|
intrinsic_width, intrinsic_height = replaced.default_image_sizing(
|
||||||
width, height, ratio, specified_width=None, specified_height=None,
|
width, height, ratio, specified_width=None, specified_height=None,
|
||||||
default_width=box.border_width(), default_height=box.border_height())
|
default_width=box.border_width(), default_height=box.border_height())
|
||||||
|
|
||||||
image_slice = box.style['border_image_slice'][:4]
|
image_slice = border_slice[:4]
|
||||||
should_fill = box.style['border_image_slice'][4]
|
should_fill = border_slice[4]
|
||||||
|
|
||||||
def compute_slice_dimension(dimension, intrinsic):
|
def compute_slice_dimension(dimension, intrinsic):
|
||||||
if isinstance(dimension, (int, float)):
|
if isinstance(dimension, (int, float)):
|
||||||
@ -504,7 +511,7 @@ def draw_border_image(box, stream):
|
|||||||
slice_bottom = compute_slice_dimension(image_slice[2], intrinsic_height)
|
slice_bottom = compute_slice_dimension(image_slice[2], intrinsic_height)
|
||||||
slice_left = compute_slice_dimension(image_slice[3], intrinsic_width)
|
slice_left = compute_slice_dimension(image_slice[3], intrinsic_width)
|
||||||
|
|
||||||
style_repeat_x, style_repeat_y = box.style['border_image_repeat']
|
repeat_x, repeat_y = border_repeat
|
||||||
|
|
||||||
x, y, w, h, tl, tr, br, bl = box.rounded_border_box()
|
x, y, w, h, tl, tr, br, bl = box.rounded_border_box()
|
||||||
px, py, pw, ph, ptl, ptr, pbr, pbl = box.rounded_padding_box()
|
px, py, pw, ph, ptl, ptr, pbr, pbl = box.rounded_padding_box()
|
||||||
@ -520,11 +527,10 @@ def draw_border_image(box, stream):
|
|||||||
assert dimension.unit == 'px'
|
assert dimension.unit == 'px'
|
||||||
return dimension.value
|
return dimension.value
|
||||||
|
|
||||||
outsets = box.style['border_image_outset']
|
outset_top = compute_outset_dimension(border_outset[0], border_top)
|
||||||
outset_top = compute_outset_dimension(outsets[0], border_top)
|
outset_right = compute_outset_dimension(border_outset[1], border_right)
|
||||||
outset_right = compute_outset_dimension(outsets[1], border_right)
|
outset_bottom = compute_outset_dimension(border_outset[2], border_bottom)
|
||||||
outset_bottom = compute_outset_dimension(outsets[2], border_bottom)
|
outset_left = compute_outset_dimension(border_outset[3], border_left)
|
||||||
outset_left = compute_outset_dimension(outsets[3], border_left)
|
|
||||||
|
|
||||||
x -= outset_left
|
x -= outset_left
|
||||||
y -= outset_top
|
y -= outset_top
|
||||||
@ -548,20 +554,18 @@ def draw_border_image(box, stream):
|
|||||||
# border-image-width. Also, the border image area that is used
|
# border-image-width. Also, the border image area that is used
|
||||||
# for percentage-based border-image-width values includes any expanded
|
# for percentage-based border-image-width values includes any expanded
|
||||||
# area due to border-image-outset.
|
# area due to border-image-outset.
|
||||||
widths = box.style['border_image_width']
|
|
||||||
border_top = compute_width_adjustment(
|
border_top = compute_width_adjustment(
|
||||||
widths[0], border_top, slice_top, h)
|
border_width[0], border_top, slice_top, h)
|
||||||
border_right = compute_width_adjustment(
|
border_right = compute_width_adjustment(
|
||||||
widths[1], border_right, slice_right, w)
|
border_width[1], border_right, slice_right, w)
|
||||||
border_bottom = compute_width_adjustment(
|
border_bottom = compute_width_adjustment(
|
||||||
widths[2], border_bottom, slice_bottom, h)
|
border_width[2], border_bottom, slice_bottom, h)
|
||||||
border_left = compute_width_adjustment(
|
border_left = compute_width_adjustment(
|
||||||
widths[3], border_left, slice_left, w)
|
border_width[3], border_left, slice_left, w)
|
||||||
|
|
||||||
def draw_border_image(x, y, width, height, slice_x, slice_y,
|
def draw_border_image_region(x, y, width, height, slice_x, slice_y, slice_width,
|
||||||
slice_width, slice_height,
|
slice_height, repeat_x='stretch', repeat_y='stretch',
|
||||||
repeat_x='stretch', repeat_y='stretch',
|
scale_x=None, scale_y=None):
|
||||||
scale_x=None, scale_y=None):
|
|
||||||
if 0 in (intrinsic_width, width, slice_width):
|
if 0 in (intrinsic_width, width, slice_width):
|
||||||
scale_x = 0
|
scale_x = 0
|
||||||
else:
|
else:
|
||||||
@ -636,60 +640,73 @@ def draw_border_image(box, stream):
|
|||||||
return scale_x, scale_y
|
return scale_x, scale_y
|
||||||
|
|
||||||
# Top left.
|
# Top left.
|
||||||
scale_left, scale_top = draw_border_image(
|
scale_left, scale_top = draw_border_image_region(
|
||||||
x, y, border_left, border_top, 0, 0, slice_left, slice_top)
|
x, y, border_left, border_top, 0, 0, slice_left, slice_top)
|
||||||
# Top right.
|
# Top right.
|
||||||
draw_border_image(
|
draw_border_image_region(
|
||||||
x + w - border_right, y, border_right, border_top,
|
x + w - border_right, y, border_right, border_top,
|
||||||
intrinsic_width - slice_right, 0, slice_right, slice_top)
|
intrinsic_width - slice_right, 0, slice_right, slice_top)
|
||||||
# Bottom right.
|
# Bottom right.
|
||||||
scale_right, scale_bottom = draw_border_image(
|
scale_right, scale_bottom = draw_border_image_region(
|
||||||
x + w - border_right, y + h - border_bottom, border_right, border_bottom,
|
x + w - border_right, y + h - border_bottom, border_right, border_bottom,
|
||||||
intrinsic_width - slice_right, intrinsic_height - slice_bottom,
|
intrinsic_width - slice_right, intrinsic_height - slice_bottom,
|
||||||
slice_right, slice_bottom)
|
slice_right, slice_bottom)
|
||||||
# Bottom left.
|
# Bottom left.
|
||||||
draw_border_image(
|
draw_border_image_region(
|
||||||
x, y + h - border_bottom, border_left, border_bottom,
|
x, y + h - border_bottom, border_left, border_bottom,
|
||||||
0, intrinsic_height - slice_bottom, slice_left, slice_bottom)
|
0, intrinsic_height - slice_bottom, slice_left, slice_bottom)
|
||||||
if slice_left + slice_right < intrinsic_width:
|
if x_middle := slice_left + slice_right < intrinsic_width:
|
||||||
# Top middle.
|
# Top middle.
|
||||||
draw_border_image(
|
draw_border_image_region(
|
||||||
x + border_left, y, w - border_left - border_right, border_top,
|
x + border_left, y, w - border_left - border_right, border_top,
|
||||||
slice_left, 0, intrinsic_width - slice_left - slice_right,
|
slice_left, 0, intrinsic_width - slice_left - slice_right,
|
||||||
slice_top, repeat_x=style_repeat_x)
|
slice_top, repeat_x=repeat_x)
|
||||||
# Bottom middle.
|
# Bottom middle.
|
||||||
draw_border_image(
|
draw_border_image_region(
|
||||||
x + border_left, y + h - border_bottom,
|
x + border_left, y + h - border_bottom,
|
||||||
w - border_left - border_right, border_bottom,
|
w - border_left - border_right, border_bottom,
|
||||||
slice_left, intrinsic_height - slice_bottom,
|
slice_left, intrinsic_height - slice_bottom,
|
||||||
intrinsic_width - slice_left - slice_right, slice_bottom,
|
intrinsic_width - slice_left - slice_right, slice_bottom,
|
||||||
repeat_x=style_repeat_x)
|
repeat_x=repeat_x)
|
||||||
if slice_top + slice_bottom < intrinsic_height:
|
if y_middle := slice_top + slice_bottom < intrinsic_height:
|
||||||
# Right middle.
|
# Right middle.
|
||||||
draw_border_image(
|
draw_border_image_region(
|
||||||
x + w - border_right, y + border_top,
|
x + w - border_right, y + border_top,
|
||||||
border_right, h - border_top - border_bottom,
|
border_right, h - border_top - border_bottom,
|
||||||
intrinsic_width - slice_right, slice_top,
|
intrinsic_width - slice_right, slice_top,
|
||||||
slice_right, intrinsic_height - slice_top - slice_bottom,
|
slice_right, intrinsic_height - slice_top - slice_bottom,
|
||||||
repeat_y=style_repeat_y)
|
repeat_y=repeat_y)
|
||||||
# Left middle.
|
# Left middle.
|
||||||
draw_border_image(
|
draw_border_image_region(
|
||||||
x, y + border_top, border_left, h - border_top - border_bottom,
|
x, y + border_top, border_left, h - border_top - border_bottom,
|
||||||
0, slice_top, slice_left,
|
0, slice_top, slice_left,
|
||||||
intrinsic_height - slice_top - slice_bottom,
|
intrinsic_height - slice_top - slice_bottom,
|
||||||
repeat_y=style_repeat_y)
|
repeat_y=repeat_y)
|
||||||
if (should_fill and slice_left + slice_right < intrinsic_width
|
if should_fill and x_middle and y_middle:
|
||||||
and slice_top + slice_bottom < intrinsic_height):
|
|
||||||
# Fill middle.
|
# Fill middle.
|
||||||
draw_border_image(
|
draw_border_image_region(
|
||||||
x + border_left, y + border_top, w - border_left - border_right,
|
x + border_left, y + border_top, w - border_left - border_right,
|
||||||
h - border_top - border_bottom, slice_left, slice_top,
|
h - border_top - border_bottom, slice_left, slice_top,
|
||||||
intrinsic_width - slice_left - slice_right,
|
intrinsic_width - slice_left - slice_right,
|
||||||
intrinsic_height - slice_top - slice_bottom,
|
intrinsic_height - slice_top - slice_bottom,
|
||||||
repeat_x=style_repeat_x, repeat_y=style_repeat_y,
|
repeat_x=repeat_x, repeat_y=repeat_y,
|
||||||
scale_x=scale_left or scale_right, scale_y=scale_top or scale_bottom)
|
scale_x=scale_left or scale_right, scale_y=scale_top or scale_bottom)
|
||||||
|
|
||||||
|
|
||||||
|
def set_mask_border(stream, box):
|
||||||
|
"""Set ``box`` mask border as alpha state on ``stream``."""
|
||||||
|
if box.style['mask_border_source'][0] == 'none' or box.mask_border_image is None:
|
||||||
|
return
|
||||||
|
x, y, w, h, tl, tr, br, bl = box.rounded_border_box()
|
||||||
|
matrix = Matrix(e=x, f=y)
|
||||||
|
matrix @= stream.ctm
|
||||||
|
mask_stream = stream.set_alpha_state(x, y, w, h, box.style['mask_border_mode'])
|
||||||
|
draw_border_image(
|
||||||
|
box, mask_stream, box.mask_border_image, box.style['mask_border_slice'],
|
||||||
|
box.style['mask_border_repeat'], box.style['mask_border_outset'],
|
||||||
|
box.style['mask_border_width'])
|
||||||
|
|
||||||
|
|
||||||
def clip_border_segment(stream, style, width, side, border_box,
|
def clip_border_segment(stream, style, width, side, border_box,
|
||||||
border_widths=None, radii=None):
|
border_widths=None, radii=None):
|
||||||
"""Clip one segment of box border.
|
"""Clip one segment of box border.
|
||||||
@ -1205,6 +1222,7 @@ def draw_inline_level(stream, page, box, offset_x=0, text_overflow='clip',
|
|||||||
boxes.InlineBlockBox, boxes.InlineFlexBox, boxes.InlineGridBox))
|
boxes.InlineBlockBox, boxes.InlineFlexBox, boxes.InlineGridBox))
|
||||||
draw_stacking_context(stream, stacking_context)
|
draw_stacking_context(stream, stacking_context)
|
||||||
else:
|
else:
|
||||||
|
set_mask_border(stream, box)
|
||||||
draw_background(stream, box.background)
|
draw_background(stream, box.background)
|
||||||
draw_border(stream, box)
|
draw_border(stream, box)
|
||||||
if isinstance(box, (boxes.InlineBox, boxes.LineBox)):
|
if isinstance(box, (boxes.InlineBox, boxes.LineBox)):
|
||||||
|
@ -56,6 +56,13 @@ def layout_box_backgrounds(page, box, get_image_from_uri, layout_children=True,
|
|||||||
else:
|
else:
|
||||||
box.border_image = value
|
box.border_image = value
|
||||||
|
|
||||||
|
if style['mask_border_source'][0] != 'none':
|
||||||
|
type_, value = style['mask_border_source']
|
||||||
|
if type_ == 'url':
|
||||||
|
box.mask_border_image = get_image_from_uri(url=value)
|
||||||
|
else:
|
||||||
|
box.mask_border_image = value
|
||||||
|
|
||||||
if style['visibility'] == 'hidden':
|
if style['visibility'] == 'hidden':
|
||||||
images = []
|
images = []
|
||||||
color = parse_color('transparent')
|
color = parse_color('transparent')
|
||||||
|
@ -112,13 +112,13 @@ class Stream(pydyf.Stream):
|
|||||||
self._states[key] = pydyf.Dictionary({'ca': alpha})
|
self._states[key] = pydyf.Dictionary({'ca': alpha})
|
||||||
super().set_state(key)
|
super().set_state(key)
|
||||||
|
|
||||||
def set_alpha_state(self, x, y, width, height):
|
def set_alpha_state(self, x, y, width, height, mode='luminosity'):
|
||||||
alpha_stream = self.add_group(x, y, width, height)
|
alpha_stream = self.add_group(x, y, width, height)
|
||||||
alpha_state = pydyf.Dictionary({
|
alpha_state = pydyf.Dictionary({
|
||||||
'Type': '/ExtGState',
|
'Type': '/ExtGState',
|
||||||
'SMask': pydyf.Dictionary({
|
'SMask': pydyf.Dictionary({
|
||||||
'Type': '/Mask',
|
'Type': '/Mask',
|
||||||
'S': '/Luminosity',
|
'S': f'/{mode.capitalize()}',
|
||||||
'G': alpha_stream,
|
'G': alpha_stream,
|
||||||
}),
|
}),
|
||||||
'ca': 1,
|
'ca': 1,
|
||||||
|
Loading…
Reference in New Issue
Block a user