1
1
mirror of https://github.com/Kozea/WeasyPrint.git synced 2024-09-11 20:47:56 +03:00

Handle inheritance for defined elements

This commit is contained in:
Guillaume Ayoub 2022-11-13 12:25:00 +01:00
parent 419c771331
commit 2b94b922f9
3 changed files with 346 additions and 1 deletions

View File

@ -291,6 +291,108 @@ def test_linear_gradient_reflect(assert_pixels):
''')
@assert_no_logs
def test_linear_gradient_inherit_attributes(assert_pixels):
assert_pixels('''
BBBBBBBBBB
BBBBBBBBBB
BBBBBBBBBB
BBBBBBBBBB
BBBBBBBBBB
RRRRRRRRRR
RRRRRRRRRR
RRRRRRRRRR
RRRRRRRRRR
RRRRRRRRRR
''', '''
<style>
@page { size: 10px }
svg { display: block }
</style>
<svg width="10px" height="10px" xmlns="https://www.w3.org/2000/svg">
<defs>
<linearGradient id="parent" x1="0" y1="0" x2="0" y2="1"
gradientUnits="objectBoundingBox">
</linearGradient>
<linearGradient id="grad" href="#parent">
<stop stop-color="blue" offset="50%"></stop>
<stop stop-color="red" offset="50%"></stop>
</linearGradient>
</defs>
<rect x="0" y="0" width="10" height="10" fill="url(#grad)" />
</svg>
''')
@assert_no_logs
def test_linear_gradient_inherit_children(assert_pixels):
assert_pixels('''
BBBBBBBBBB
BBBBBBBBBB
BBBBBBBBBB
BBBBBBBBBB
BBBBBBBBBB
RRRRRRRRRR
RRRRRRRRRR
RRRRRRRRRR
RRRRRRRRRR
RRRRRRRRRR
''', '''
<style>
@page { size: 10px }
svg { display: block }
</style>
<svg width="10px" height="10px" xmlns="https://www.w3.org/2000/svg">
<defs>
<linearGradient id="parent">
<stop stop-color="blue" offset="50%"></stop>
<stop stop-color="red" offset="50%"></stop>
</linearGradient>
<linearGradient id="grad" href="#parent"
x1="0" y1="0" x2="0" y2="1" gradientUnits="objectBoundingBox">
</linearGradient>
</defs>
<rect x="0" y="0" width="10" height="10" fill="url(#grad)" />
</svg>
''')
@assert_no_logs
def test_linear_gradient_inherit_no_override(assert_pixels):
assert_pixels('''
BBBBBBBBBB
BBBBBBBBBB
BBBBBBBBBB
BBBBBBBBBB
BBBBBBBBBB
RRRRRRRRRR
RRRRRRRRRR
RRRRRRRRRR
RRRRRRRRRR
RRRRRRRRRR
''', '''
<style>
@page { size: 10px }
svg { display: block }
</style>
<svg width="10px" height="10px" xmlns="https://www.w3.org/2000/svg">
<defs>
<linearGradient id="parent"
x1="1" y1="1" x2="1" y2="0" gradientUnits="userSpaceOnUse">
<stop stop-color="red" offset="50%"></stop>
<stop stop-color="blue" offset="50%"></stop>
</linearGradient>
<linearGradient id="grad" href="#parent"
x1="0" y1="0" x2="0" y2="1" gradientUnits="objectBoundingBox">
<stop stop-color="blue" offset="50%"></stop>
<stop stop-color="red" offset="50%"></stop>
</linearGradient>
</defs>
<rect x="0" y="0" width="10" height="10" fill="url(#grad)" />
</svg>
''')
@assert_no_logs
def test_radial_gradient(assert_pixels):
assert_pixels('''
@ -493,7 +595,112 @@ def test_radial_gradient_reflect(assert_pixels):
@assert_no_logs
def test_linear_gradient_opacity(assert_pixels):
def test_radial_gradient_inherit_attributes(assert_pixels):
assert_pixels('''
rrrrrrrrrr
rrrrrrrrrr
rrrrBBrrrr
rrrBBBBrrr
rrBBBBBBrr
rrBBBBBBrr
rrrBBBBrrr
rrrrBBrrrr
rrrrrrrrrr
rrrrrrrrrr
''', '''
<style>
@page { size: 10px }
svg { display: block }
</style>
<svg width="10px" height="10px" xmlns="https://www.w3.org/2000/svg">
<defs>
<radialGradient id="parent" cx="0.5" cy="0.5" r="0.5"
fx="0.5" fy="0.5" fr="0.2"
gradientUnits="objectBoundingBox">
</radialGradient>
<radialGradient id="grad" href="#parent">
<stop stop-color="blue" offset="25%"></stop>
<stop stop-color="red" offset="25%"></stop>
</radialGradient>
</defs>
<rect x="0" y="0" width="10" height="10" fill="url(#grad)" />
</svg>
''')
@assert_no_logs
def test_radial_gradient_inherit_children(assert_pixels):
assert_pixels('''
rrrrrrrrrr
rrrrrrrrrr
rrrrBBrrrr
rrrBBBBrrr
rrBBBBBBrr
rrBBBBBBrr
rrrBBBBrrr
rrrrBBrrrr
rrrrrrrrrr
rrrrrrrrrr
''', '''
<style>
@page { size: 10px }
svg { display: block }
</style>
<svg width="10px" height="10px" xmlns="https://www.w3.org/2000/svg">
<defs>
<radialGradient id="parent">
<stop stop-color="blue" offset="25%"></stop>
<stop stop-color="red" offset="25%"></stop>
</radialGradient>
<radialGradient id="grad" href="#parent"
cx="0.5" cy="0.5" r="0.5" fx="0.5" fy="0.5" fr="0.2"
gradientUnits="objectBoundingBox">
</radialGradient>
</defs>
<rect x="0" y="0" width="10" height="10" fill="url(#grad)" />
</svg>
''')
@assert_no_logs
def test_radial_gradient_inherit_no_override(assert_pixels):
assert_pixels('''
rrrrrrrrrr
rrrrrrrrrr
rrrrBBrrrr
rrrBBBBrrr
rrBBBBBBrr
rrBBBBBBrr
rrrBBBBrrr
rrrrBBrrrr
rrrrrrrrrr
rrrrrrrrrr
''', '''
<style>
@page { size: 10px }
svg { display: block }
</style>
<svg width="10px" height="10px" xmlns="https://www.w3.org/2000/svg">
<defs>
<radialGradient id="parent" cx="5" cy="5" r="5" fx="5" fy="5" fr="2"
gradientUnits="userSpaceOnUse">
<stop stop-color="red" offset="25%"></stop>
<stop stop-color="blue" offset="25%"></stop>
</radialGradient>
<radialGradient id="grad" href="#parent" cx="0.5" cy="0.5" r="0.5"
fx="0.5" fy="0.5" fr="0.2"
gradientUnits="objectBoundingBox">
<stop stop-color="blue" offset="25%"></stop>
<stop stop-color="red" offset="25%"></stop>
</radialGradient>
</defs>
<rect x="0" y="0" width="10" height="10" fill="url(#grad)" />
</svg>
''')
@assert_no_logs
def test_gradient_opacity(assert_pixels):
assert_pixels('''
BBBBBBBBBB
BBBBBBBBBB

View File

@ -129,3 +129,109 @@ def test_pattern_4(assert_pixels):
<rect x="0" y="0" width="8" height="8" fill="url(#pat)" />
</svg>
''')
@assert_no_logs
def test_pattern_inherit_attributes(assert_pixels):
assert_pixels('''
BBrrBBrr
BBrrBBrr
rrBBrrBB
rrBBrrBB
BBrrBBrr
BBrrBBrr
rrBBrrBB
rrBBrrBB
''', '''
<style>
@page { size: 8px }
svg { display: block }
</style>
<svg width="8px" height="8px" xmlns="https://www.w3.org/2000/svg">
<defs>
<pattern id="parent" x="0" y="0" width="4" height="4"
patternUnits="userSpaceOnUse"
patternContentUnits="userSpaceOnUse">
</pattern>
<pattern id="pat" href="#parent">
<rect x="0" y="0" width="2" height="2" fill="blue" />
<rect x="0" y="2" width="2" height="2" fill="red" />
<rect x="2" y="0" width="2" height="2" fill="red" />
<rect x="2" y="2" width="2" height="2" fill="blue" />
</pattern>
</defs>
<rect x="0" y="0" width="8" height="8" fill="url(#pat)" />
</svg>
''')
@assert_no_logs
def test_pattern_inherit_children(assert_pixels):
assert_pixels('''
BBrrBBrr
BBrrBBrr
rrBBrrBB
rrBBrrBB
BBrrBBrr
BBrrBBrr
rrBBrrBB
rrBBrrBB
''', '''
<style>
@page { size: 8px }
svg { display: block }
</style>
<svg width="8px" height="8px" xmlns="https://www.w3.org/2000/svg">
<defs>
<pattern id="parent">
<rect x="0" y="0" width="2" height="2" fill="blue" />
<rect x="0" y="2" width="2" height="2" fill="red" />
<rect x="2" y="0" width="2" height="2" fill="red" />
<rect x="2" y="2" width="2" height="2" fill="blue" />
</pattern>
<pattern id="pat" href="#parent" x="0" y="0" width="4" height="4"
patternUnits="userSpaceOnUse" patternContentUnits="userSpaceOnUse">
</pattern>
</defs>
<rect x="0" y="0" width="8" height="8" fill="url(#pat)" />
</svg>
''')
@assert_no_logs
def test_pattern_inherit_no_override(assert_pixels):
assert_pixels('''
BBrrBBrr
BBrrBBrr
rrBBrrBB
rrBBrrBB
BBrrBBrr
BBrrBBrr
rrBBrrBB
rrBBrrBB
''', '''
<style>
@page { size: 8px }
svg { display: block }
</style>
<svg width="8px" height="8px" xmlns="https://www.w3.org/2000/svg">
<defs>
<pattern id="parent" x="1" y="1" width="3" height="3"
patternUnits="objectBoundingBox"
patternContentUnits="objectBoundingBox">
<rect x="0" y="0" width="2" height="2" fill="green" />
<rect x="0" y="2" width="2" height="2" fill="green" />
<rect x="2" y="0" width="2" height="2" fill="yellow" />
<rect x="2" y="2" width="2" height="2" fill="yellow" />
</pattern>
<pattern id="pat" href="#parent" x="0" y="0" width="4" height="4"
patternUnits="userSpaceOnUse" patternContentUnits="userSpaceOnUse">
<rect x="0" y="0" width="2" height="2" fill="blue" />
<rect x="0" y="2" width="2" height="2" fill="red" />
<rect x="2" y="0" width="2" height="2" fill="red" />
<rect x="2" y="2" width="2" height="2" fill="blue" />
</pattern>
</defs>
<rect x="0" y="0" width="8" height="8" fill="url(#pat)" />
</svg>
''')

View File

@ -182,6 +182,11 @@ class Node:
if url:
return url
def del_href(self):
"""Remove the href attributes, with or without a namespace."""
for attr_name in ('{http://www.w3.org/1999/xlink}href', 'href'):
self.attrib.pop(attr_name, None)
@staticmethod
def process_whitespace(string, preserve):
"""Replace newlines by spaces, and merge spaces if not preserved."""
@ -308,6 +313,7 @@ class SVG:
self.text_path_width = 0
self.parse_defs(self.tree)
self.inherit_defs()
def get_intrinsic_size(self, font_size):
"""Get intrinsic size of the image."""
@ -710,6 +716,32 @@ class SVG:
for child in node:
self.parse_defs(child)
def inherit_defs(self):
"""Handle inheritance of different defined elements lists."""
for defs in (self.gradients, self.patterns):
for element in defs.values():
self.inherit_element(element, defs)
def inherit_element(self, element, defs):
"""Recursively handle inheritance of defined element."""
href = element.get_href(self.url)
if not href:
return
element.del_href()
parent = defs.get(parse_url(href).fragment)
if not parent:
return
self.inherit_element(parent, defs)
for key, value in parent.attrib.items():
if key not in element.attrib:
element.attrib[key] = value
if next(iter(element), None) is None:
# Override elements __iter__ with parents __iter__. As special
# methods are bound to classes and not instances, we have to create
# and assign a new type.
element.__class__ = type(
'Node', (Node,), {'__iter__': lambda self: parent.__iter__()})
def calculate_bounding_box(self, node, font_size, stroke=True):
"""Calculate the bounding box of a node."""
if stroke or node.bounding_box is None: