diff --git a/tests/css/test_expanders.py b/tests/css/test_expanders.py index e5654e615..1c2b830a6 100644 --- a/tests/css/test_expanders.py +++ b/tests/css/test_expanders.py @@ -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', { diff --git a/tests/css/test_validation.py b/tests/css/test_validation.py index cf01bba22..c91adac63 100644 --- a/tests/css/test_validation.py +++ b/tests/css/test_validation.py @@ -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'),)),)), diff --git a/tests/draw/test_box.py b/tests/draw/test_box.py index 168aeb198..dc0703742 100644 --- a/tests/draw/test_box.py +++ b/tests/draw/test_box.py @@ -576,3 +576,65 @@ def test_border_image_gradient(assert_pixels):
''') + + +@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_ + __________ + ''', ''' + +
+ ''') + + +@assert_no_logs +def test_mask_border_fill(assert_pixels): + assert_pixels(''' + __________ + __RR__RRR_ + _RRRRRRRR_ + _RRRRRRRR_ + _sRR__RRR_ + _sRR__RRR_ + _RRRRRRRR_ + _RRRRRRRR_ + __RRRRRRR_ + __________ + ''', ''' + +
+ ''') diff --git a/tests/resources/mask.svg b/tests/resources/mask.svg new file mode 100644 index 000000000..7c3469e78 --- /dev/null +++ b/tests/resources/mask.svg @@ -0,0 +1,4 @@ + + + + diff --git a/weasyprint/css/computed_values.py b/weasyprint/css/computed_values.py index 4e10f9423..d02fbf3e9 100644 --- a/weasyprint/css/computed_values.py +++ b/weasyprint/css/computed_values.py @@ -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 diff --git a/weasyprint/css/properties.py b/weasyprint/css/properties.py index 450b72350..365b5cd1f 100644 --- a/weasyprint/css/properties.py +++ b/weasyprint/css/properties.py @@ -77,6 +77,17 @@ 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/ diff --git a/weasyprint/css/validation/expanders.py b/weasyprint/css/validation/expanders.py index 51794f629..e63585711 100644 --- a/weasyprint/css/validation/expanders.py +++ b/weasyprint/css/validation/expanders.py @@ -18,8 +18,8 @@ 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. diff --git a/weasyprint/css/validation/properties.py b/weasyprint/css/validation/properties.py index 57ae504c1..27e64568b 100644 --- a/weasyprint/css/validation/properties.py +++ b/weasyprint/css/validation/properties.py @@ -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): diff --git a/weasyprint/draw.py b/weasyprint/draw.py index f619ed36e..e76e18339 100644 --- a/weasyprint/draw.py +++ b/weasyprint/draw.py @@ -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 compute_slice_dimension(dimension, intrinsic): 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 compute_outset_dimension(dimension, from_border): 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 compute_width_adjustment(dimension, original, intrinsic, # 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(x, y, width, height, slice_x, slice_y, 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)): diff --git a/weasyprint/layout/background.py b/weasyprint/layout/background.py index 09599165a..3ca357efc 100644 --- a/weasyprint/layout/background.py +++ b/weasyprint/layout/background.py @@ -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') diff --git a/weasyprint/pdf/stream.py b/weasyprint/pdf/stream.py index a1d7abb06..0c7ba66da 100644 --- a/weasyprint/pdf/stream.py +++ b/weasyprint/pdf/stream.py @@ -112,13 +112,13 @@ def set_alpha(self, alpha, stroke=False, fill=None): 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,