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_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
|
||||
@pytest.mark.parametrize('rule, result', (
|
||||
('12px My Fancy Font, serif', {
|
||||
|
@ -470,6 +470,135 @@ def test_border_image_repeat_invalid(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
|
||||
@pytest.mark.parametrize('rule, value', (
|
||||
('test content(text)', (('test', (('content()', 'text'),)),)),
|
||||
|
@ -576,3 +576,65 @@ def test_border_image_gradient(assert_pixels):
|
||||
</style>
|
||||
<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('mask-border-slice')
|
||||
def border_image_slice(style, name, values):
|
||||
"""Compute the ``border-image-slice`` property."""
|
||||
computed_values = []
|
||||
@ -385,6 +386,7 @@ def border_image_slice(style, name, values):
|
||||
|
||||
|
||||
@register_computer('border-image-width')
|
||||
@register_computer('mask-border-width')
|
||||
def border_image_width(style, name, values):
|
||||
"""Compute the ``border-image-width`` property."""
|
||||
computed_values = []
|
||||
@ -404,6 +406,7 @@ def border_image_width(style, name, values):
|
||||
|
||||
|
||||
@register_computer('border-image-outset')
|
||||
@register_computer('mask-border-outset')
|
||||
def border_image_outset(style, name, values):
|
||||
"""Compute the ``border-image-outset`` property."""
|
||||
computed_values = [
|
||||
@ -419,6 +422,7 @@ def border_image_outset(style, name, values):
|
||||
|
||||
|
||||
@register_computer('border-image-repeat')
|
||||
@register_computer('mask-border-repeat')
|
||||
def border_image_repeat(style, name, values):
|
||||
"""Compute the ``border-image-repeat`` property."""
|
||||
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)),
|
||||
'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/
|
||||
|
@ -18,8 +18,8 @@ from .properties import ( # isort:skip
|
||||
border_width, box, column_count, column_width, flex_basis, flex_direction,
|
||||
flex_grow_shrink, flex_wrap, font_family, font_size, font_stretch,
|
||||
font_style, font_weight, gap, grid_line, grid_template, line_height,
|
||||
list_style_image, list_style_position, list_style_type, other_colors,
|
||||
overflow_wrap, validate_non_shorthand)
|
||||
list_style_image, list_style_position, list_style_type, mask_border_mode,
|
||||
other_colors, overflow_wrap, validate_non_shorthand)
|
||||
|
||||
EXPANDERS = {}
|
||||
|
||||
@ -350,6 +350,72 @@ def expand_border_image(tokens, name, base_url):
|
||||
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')
|
||||
def expand_background(tokens, name, base_url):
|
||||
"""Expand the ``background`` shorthand property.
|
||||
|
@ -421,7 +421,8 @@ def border_width(token):
|
||||
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
|
||||
def border_image_source(token, base_url):
|
||||
if get_keyword(token) == 'none':
|
||||
@ -429,7 +430,8 @@ def border_image_source(token, base_url):
|
||||
return get_image(token, base_url)
|
||||
|
||||
|
||||
@property()
|
||||
@property('border-image-slice')
|
||||
@property('mask-border-slice')
|
||||
def border_image_slice(tokens):
|
||||
values = []
|
||||
fill = False
|
||||
@ -449,7 +451,8 @@ def border_image_slice(tokens):
|
||||
return tuple(values)
|
||||
|
||||
|
||||
@property()
|
||||
@property('border-image-width')
|
||||
@property('mask-border-width')
|
||||
def border_image_width(tokens):
|
||||
values = []
|
||||
for token in tokens:
|
||||
@ -467,7 +470,8 @@ def border_image_width(tokens):
|
||||
return tuple(values)
|
||||
|
||||
|
||||
@property()
|
||||
@property('border-image-outset')
|
||||
@property('mask-border-outset')
|
||||
def border_image_outset(tokens):
|
||||
values = []
|
||||
for token in tokens:
|
||||
@ -483,7 +487,8 @@ def border_image_outset(tokens):
|
||||
return tuple(values)
|
||||
|
||||
|
||||
@property()
|
||||
@property('border-image-repeat')
|
||||
@property('mask-border-repeat')
|
||||
def border_image_repeat(tokens):
|
||||
if 1 <= len(tokens) <= 2:
|
||||
keywords = tuple(get_keyword(token) for token in tokens)
|
||||
@ -491,6 +496,12 @@ def border_image_repeat(tokens):
|
||||
return keywords
|
||||
|
||||
|
||||
@property()
|
||||
@single_keyword
|
||||
def mask_border_mode(keyword):
|
||||
return keyword in ('luminance', 'alpha')
|
||||
|
||||
|
||||
@property(unstable=True)
|
||||
@single_token
|
||||
def column_width(token):
|
||||
|
@ -62,6 +62,7 @@ def draw_page(page, stream):
|
||||
draw_background(
|
||||
stream, stacking_context.box.background, clip_box=False,
|
||||
bleed=page.bleed, marks=marks)
|
||||
set_mask_border(stream, page)
|
||||
draw_background(stream, page.canvas_background, clip_box=False)
|
||||
draw_border(stream, page)
|
||||
draw_stacking_context(stream, stacking_context)
|
||||
@ -116,6 +117,7 @@ def draw_stacking_context(stream, stacking_context):
|
||||
if isinstance(box, (boxes.BlockBox, boxes.MarginBox,
|
||||
boxes.InlineBlockBox, boxes.TableCellBox,
|
||||
boxes.FlexContainerBox, boxes.ReplacedBox)):
|
||||
set_mask_border(stream, box)
|
||||
# The canvas background was removed by layout_backgrounds
|
||||
draw_background(stream, box.background)
|
||||
draw_border(stream, box)
|
||||
@ -137,6 +139,8 @@ def draw_stacking_context(stream, stacking_context):
|
||||
|
||||
# Point 4
|
||||
for block in stacking_context.block_level_boxes:
|
||||
set_mask_border(stream, block)
|
||||
|
||||
if isinstance(block, boxes.TableBox):
|
||||
draw_table(stream, block)
|
||||
else:
|
||||
@ -441,7 +445,10 @@ def draw_border(stream, box):
|
||||
|
||||
# If there's a border image, that takes precedence.
|
||||
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
|
||||
|
||||
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))
|
||||
|
||||
|
||||
def draw_border_image(box, stream):
|
||||
"""Draw ``box`` border image on ``stream``."""
|
||||
# See https://drafts.csswg.org/css-backgrounds-3/#border-images
|
||||
image = box.border_image
|
||||
def draw_border_image(box, stream, image, border_slice, border_repeat, border_outset,
|
||||
border_width):
|
||||
"""Draw ``image`` as a border image for ``box`` on ``stream`` as specified."""
|
||||
# Shared by border-image-* and mask-border-*
|
||||
width, height, ratio = image.get_intrinsic_size(
|
||||
box.style['image_resolution'], box.style['font_size'])
|
||||
intrinsic_width, intrinsic_height = replaced.default_image_sizing(
|
||||
width, height, ratio, specified_width=None, specified_height=None,
|
||||
default_width=box.border_width(), default_height=box.border_height())
|
||||
|
||||
image_slice = box.style['border_image_slice'][:4]
|
||||
should_fill = box.style['border_image_slice'][4]
|
||||
image_slice = border_slice[:4]
|
||||
should_fill = border_slice[4]
|
||||
|
||||
def compute_slice_dimension(dimension, intrinsic):
|
||||
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_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()
|
||||
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'
|
||||
return dimension.value
|
||||
|
||||
outsets = box.style['border_image_outset']
|
||||
outset_top = compute_outset_dimension(outsets[0], border_top)
|
||||
outset_right = compute_outset_dimension(outsets[1], border_right)
|
||||
outset_bottom = compute_outset_dimension(outsets[2], border_bottom)
|
||||
outset_left = compute_outset_dimension(outsets[3], border_left)
|
||||
outset_top = compute_outset_dimension(border_outset[0], border_top)
|
||||
outset_right = compute_outset_dimension(border_outset[1], border_right)
|
||||
outset_bottom = compute_outset_dimension(border_outset[2], border_bottom)
|
||||
outset_left = compute_outset_dimension(border_outset[3], border_left)
|
||||
|
||||
x -= outset_left
|
||||
y -= outset_top
|
||||
@ -548,20 +554,18 @@ def draw_border_image(box, stream):
|
||||
# border-image-width. Also, the border image area that is used
|
||||
# for percentage-based border-image-width values includes any expanded
|
||||
# area due to border-image-outset.
|
||||
widths = box.style['border_image_width']
|
||||
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(
|
||||
widths[1], border_right, slice_right, w)
|
||||
border_width[1], border_right, slice_right, w)
|
||||
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(
|
||||
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,
|
||||
slice_width, slice_height,
|
||||
repeat_x='stretch', repeat_y='stretch',
|
||||
scale_x=None, scale_y=None):
|
||||
def draw_border_image_region(x, y, width, height, slice_x, slice_y, slice_width,
|
||||
slice_height, repeat_x='stretch', repeat_y='stretch',
|
||||
scale_x=None, scale_y=None):
|
||||
if 0 in (intrinsic_width, width, slice_width):
|
||||
scale_x = 0
|
||||
else:
|
||||
@ -636,60 +640,73 @@ def draw_border_image(box, stream):
|
||||
return scale_x, scale_y
|
||||
|
||||
# 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)
|
||||
# Top right.
|
||||
draw_border_image(
|
||||
draw_border_image_region(
|
||||
x + w - border_right, y, border_right, border_top,
|
||||
intrinsic_width - slice_right, 0, slice_right, slice_top)
|
||||
# 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,
|
||||
intrinsic_width - slice_right, intrinsic_height - slice_bottom,
|
||||
slice_right, slice_bottom)
|
||||
# Bottom left.
|
||||
draw_border_image(
|
||||
draw_border_image_region(
|
||||
x, y + h - border_bottom, border_left, border_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.
|
||||
draw_border_image(
|
||||
draw_border_image_region(
|
||||
x + border_left, y, w - border_left - border_right, border_top,
|
||||
slice_left, 0, intrinsic_width - slice_left - slice_right,
|
||||
slice_top, repeat_x=style_repeat_x)
|
||||
slice_top, repeat_x=repeat_x)
|
||||
# Bottom middle.
|
||||
draw_border_image(
|
||||
draw_border_image_region(
|
||||
x + border_left, y + h - border_bottom,
|
||||
w - border_left - border_right, border_bottom,
|
||||
slice_left, intrinsic_height - slice_bottom,
|
||||
intrinsic_width - slice_left - slice_right, slice_bottom,
|
||||
repeat_x=style_repeat_x)
|
||||
if slice_top + slice_bottom < intrinsic_height:
|
||||
repeat_x=repeat_x)
|
||||
if y_middle := slice_top + slice_bottom < intrinsic_height:
|
||||
# Right middle.
|
||||
draw_border_image(
|
||||
draw_border_image_region(
|
||||
x + w - border_right, y + border_top,
|
||||
border_right, h - border_top - border_bottom,
|
||||
intrinsic_width - slice_right, slice_top,
|
||||
slice_right, intrinsic_height - slice_top - slice_bottom,
|
||||
repeat_y=style_repeat_y)
|
||||
repeat_y=repeat_y)
|
||||
# Left middle.
|
||||
draw_border_image(
|
||||
draw_border_image_region(
|
||||
x, y + border_top, border_left, h - border_top - border_bottom,
|
||||
0, slice_top, slice_left,
|
||||
intrinsic_height - slice_top - slice_bottom,
|
||||
repeat_y=style_repeat_y)
|
||||
if (should_fill and slice_left + slice_right < intrinsic_width
|
||||
and slice_top + slice_bottom < intrinsic_height):
|
||||
repeat_y=repeat_y)
|
||||
if should_fill and x_middle and y_middle:
|
||||
# Fill middle.
|
||||
draw_border_image(
|
||||
draw_border_image_region(
|
||||
x + border_left, y + border_top, w - border_left - border_right,
|
||||
h - border_top - border_bottom, slice_left, slice_top,
|
||||
intrinsic_width - slice_left - slice_right,
|
||||
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)
|
||||
|
||||
|
||||
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,
|
||||
border_widths=None, radii=None):
|
||||
"""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))
|
||||
draw_stacking_context(stream, stacking_context)
|
||||
else:
|
||||
set_mask_border(stream, box)
|
||||
draw_background(stream, box.background)
|
||||
draw_border(stream, box)
|
||||
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:
|
||||
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':
|
||||
images = []
|
||||
color = parse_color('transparent')
|
||||
|
@ -112,13 +112,13 @@ class Stream(pydyf.Stream):
|
||||
self._states[key] = pydyf.Dictionary({'ca': alpha})
|
||||
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_state = pydyf.Dictionary({
|
||||
'Type': '/ExtGState',
|
||||
'SMask': pydyf.Dictionary({
|
||||
'Type': '/Mask',
|
||||
'S': '/Luminosity',
|
||||
'S': f'/{mode.capitalize()}',
|
||||
'G': alpha_stream,
|
||||
}),
|
||||
'ca': 1,
|
||||
|
Loading…
Reference in New Issue
Block a user