Skip to content

Commit

Permalink
Merge pull request #193 from meerk40t/issue170-use-additional
Browse files Browse the repository at this point in the history
Correct issue #170 expand Use slightly
  • Loading branch information
tatarize authored Sep 7, 2022
2 parents ecc5e3b + 77a6c62 commit 3fc3755
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 58 deletions.
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = svgelements
version = 1.8.0
version = 1.8.1
description = Svg Elements Parsing
long_description_content_type=text/markdown
long_description = file: README.md
Expand Down
141 changes: 84 additions & 57 deletions svgelements/svgelements.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
and the Arc can do exact arc calculations if scipy is installed.
"""

SVGELEMENTS_VERSION = "1.8.0"
SVGELEMENTS_VERSION = "1.8.1"

MIN_DEPTH = 5
ERROR = 1e-12
Expand Down Expand Up @@ -244,7 +244,7 @@
r"$"
)
REGEX_CSS_FONT_FAMILY = re.compile(
r"""(?:([^\s"';,]+|"[^";,]+"|'[^';,]+'|serif|sans-serif|cursive|fantasy|monospace)),?\s*;?"""
r"""(?:([^\s"';,]+|"[^";,]+"|'[^';,]+'|serif|sans-serif|cursive|fantasy|monospace)),?\s*;?"""
)

svg_parse = [("COMMAND", r"[MmZzLlHhVvCcSsQqTtAa]"), ("SKIP", PATTERN_COMMAWSP)]
Expand Down Expand Up @@ -2463,7 +2463,7 @@ def is_orthogonal(self):


class Matrix:
""" "
"""
Provides svg matrix interfacing.
SVG 7.15.3 defines the matrix form as:
Expand Down Expand Up @@ -6941,7 +6941,7 @@ def _ramanujan_length(self):
if b > a:
a, b = b, a
h = ((a - b) * (a - b)) / ((a + b) * (a + b))
return pi * (a + b) * (1 + (3 * h / (10 + sqrt(4 - 3 * h))))
return tau / 2 * (a + b) * (1 + (3 * h / (10 + sqrt(4 - 3 * h))))


class Ellipse(_RoundShape):
Expand Down Expand Up @@ -7561,14 +7561,14 @@ def select(self, conditional=None):
if conditional is None:
for subitem in self:
yield subitem
if isinstance(subitem, Group):
if isinstance(subitem, (Group, Use)):
for s in subitem.select(conditional):
yield s
else:
for subitem in self:
if conditional(subitem):
yield subitem
if isinstance(subitem, Group):
if isinstance(subitem, (Group, Use)):
for s in subitem.select(conditional):
yield s

Expand Down Expand Up @@ -7632,7 +7632,7 @@ def bbox(self, transformed=True, with_stroke=False):
)


class Use(SVGElement, list):
class Use(SVGElement, Transformable, list):
"""
Use elements are defined in svg 5.6
https://www.w3.org/TR/SVG11/struct.html#UseElement
Expand All @@ -7642,13 +7642,68 @@ class Use(SVGElement, list):

def __init__(self, *args, **kwargs):
list.__init__(self)
SVGElement.__init__(self, *args, **kwargs)
self.x = None
self.y = None
self.width = None
self.height = None
Transformable.__init__(self, *args, **kwargs)
SVGElement.__init__(
self, *args, **kwargs
) # Must go last, triggers, by_object, by_value, by_arg functions.

def property_by_object(self, s):
SVGElement.property_by_object(self, s)
Transformable.property_by_object(self, s)
self.x = s.x
self.y = s.y
self.width = s.width
self.height = s.height

def property_by_values(self, values):
self.x = Length(values.get(SVG_ATTR_X, 0)).value()
self.y = Length(values.get(SVG_ATTR_Y, 0)).value()
self.width = Length(values.get(SVG_ATTR_WIDTH, 1)).value()
self.height = Length(values.get(SVG_ATTR_HEIGHT, 1)).value()
if self.x != 0 or self.y != 0:
# If x or y is set, apply this to transform
try:
values[SVG_ATTR_TRANSFORM] = "%s translate(%s, %s)" % (
values[SVG_ATTR_TRANSFORM],
self.x,
self.y,
)
except KeyError:
values[SVG_ATTR_TRANSFORM] = "translate(%s, %s)" % (
self.x,
self.y,
)
SVGElement.property_by_values(self, values)
Transformable.property_by_values(self, values)

def render(self, **kwargs):
SVGElement.render(self, **kwargs)
Transformable.render(self, **kwargs)

def select(self, conditional=None):
"""
Finds all flattened subobjects of this group for which the conditional returns
true.
:param conditional: function taking element and returns True to include or False if exclude
"""
if conditional is None:
for subitem in self:
yield subitem
if isinstance(subitem, (Group, Use)):
for s in subitem.select(conditional):
yield s
else:
for subitem in self:
if conditional(subitem):
yield subitem
if isinstance(subitem, (Group, Use)):
for s in subitem.select(conditional):
yield s


class ClipPath(SVGElement, list):
Expand Down Expand Up @@ -8571,21 +8626,6 @@ def viewbox_transform(self):
return ""
return self.viewbox.transform(self)

@staticmethod
def _shadow_iter(tag, elem, children):
yield tag, "start", elem
try:
for t, e, c in children:
for shadow_tag, shadow_event, shadow_elem in SVG._shadow_iter(t, e, c):
yield shadow_tag, shadow_event, shadow_elem
except ValueError:
"""
Strictly speaking it is possible to reference use from other use objects. If this is an infinite loop
we should not block the rendering. Just say we finished. See: W3C, struct-use-12-f
"""
pass
yield tag, "end", elem

@staticmethod
def _use_structure_parse(source):
"""
Expand All @@ -8607,15 +8647,18 @@ def _use_structure_parse(source):
siblings.append(node) # siblings now includes this node.
attributes = elem.attrib
if SVG_ATTR_ID in attributes: # If we have an ID, we save the node.
event_defs[attributes[SVG_ATTR_ID]] = node # store node value in defs.
event_defs[
attributes[SVG_ATTR_ID]
] = node # store node value in defs.
elif event == "end":
parent, children = parent
else:
children.append((elem, None))
nodes = children
# End preprocess

# Semiparse the nodes. All nodes are given in iterparse ordering with start-ns, start, and end. Use values are inlined.
# Semiparse the nodes. All nodes are given in iterparse ordering with start-ns, start, and end.
# Use values are inlined.
def semiparse(nodes):
for elem, children in nodes:
if children is None:
Expand All @@ -8628,39 +8671,12 @@ def semiparse(nodes):
yield from semiparse(children)
if SVG_TAG_USE == tag:
url = None
attributes = elem.attrib
if XLINK_HREF in attributes:
url = attributes[XLINK_HREF]
if SVG_HREF in attributes:
url = attributes[SVG_HREF]
semiattr = elem.attrib
if XLINK_HREF in semiattr:
url = semiattr[XLINK_HREF]
if SVG_HREF in semiattr:
url = semiattr[SVG_HREF]
if url is not None:
transform = False
try:
x = attributes[SVG_ATTR_X]
del attributes[SVG_ATTR_X]
transform = True
except KeyError:
x = "0"
try:
y = attributes[SVG_ATTR_Y]
del attributes[SVG_ATTR_Y]
transform = True
except KeyError:
y = "0"
if transform:
try:
attributes[
SVG_ATTR_TRANSFORM
] = "%s translate(%s, %s)" % (
attributes[SVG_ATTR_TRANSFORM],
x,
y,
)
except KeyError:
attributes[SVG_ATTR_TRANSFORM] = "translate(%s, %s)" % (
x,
y,
)
try:
yield from semiparse([event_defs[url[1:]]])
except KeyError:
Expand Down Expand Up @@ -8876,6 +8892,17 @@ def parse(
clip += 1
elif SVG_TAG_USE == tag:
s = Use(values)
if SVG_ATTR_TRANSFORM in s.values:
# Update value in case x or y applied.
values[SVG_ATTR_TRANSFORM] = s.values[SVG_ATTR_TRANSFORM]
if SVG_ATTR_X in values:
del values[SVG_ATTR_X]
if SVG_ATTR_Y in values:
del values[SVG_ATTR_Y]
if SVG_ATTR_WIDTH in values:
del values[SVG_ATTR_WIDTH]
if SVG_ATTR_HEIGHT in values:
del values[SVG_ATTR_HEIGHT]
context.append(s)
context = s
use += 1
Expand Down
37 changes: 37 additions & 0 deletions test/test_use.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,3 +233,40 @@ def test_issue_192(self):
self.assertEqual(path1.values["transform"], path2.values["transform"])
self.assertEqual(path1.transform, path2.transform)
self.assertEqual(path1, path2)

def test_issue_170(self):
"""
Rendered wrongly since the x and y values do not get applied correctly to the use in question.
"""

q1 = io.StringIO(u'''<?xml version='1.0' encoding='UTF-8'?>
<!-- This file was generated by dvisvgm 2.11.1 -->
<svg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='30.033152pt' height='6.789661pt' viewBox='85.143591 -71.954169 30.033152 6.789661'>
<defs>
<path id='g4-65' d='M5.378384 0L5.427878-.239223L5.114414-.26397C4.858693-.288717 4.825697-.404204 4.784452-.742415L4.174022-5.708346H3.588339L2.202498-3.274875C1.781796-2.540709 1.097124-1.3116 .791909-.816656C.52794-.387706 .387706-.296966 .131985-.272219L-.140234-.239223L-.189728 0H1.666309L1.715803-.239223L1.262105-.280468C1.097124-.296966 1.080626-.412453 1.154868-.585683C1.427087-1.113622 1.699305-1.649811 2.00452-2.202498H3.852309L4.042037-.602181C4.066784-.362958 4.000792-.296966 3.835811-.280468L3.398611-.239223L3.349116 0H5.378384ZM3.811063-2.515962H2.169501C2.606701-3.332618 3.060399-4.141026 3.505848-4.941184H3.522346L3.811063-2.515962Z'/>
<use id='g6-65' xlink:href='#g3-65' transform='scale(1.166668)'/>
<path id='g3-65' d='M5.361886 0V-.239223C4.883441-.280468 4.792701-.305215 4.644218-.734166L2.895418-5.708346H2.284988L1.418837-3.266626C1.163117-2.548958 .816656-1.559071 .52794-.808407C.354709-.362958 .280468-.26397-.239223-.239223V0H1.57557V-.239223L1.146619-.280468C.899147-.305215 .8744-.387706 .940392-.61043C1.080626-1.105373 1.253856-1.616815 1.443585-2.202498H3.291373L3.84406-.626928C3.92655-.387706 3.885305-.296966 3.621335-.272219L3.250128-.239223V0H5.361886ZM3.192384-2.515962H1.550822C1.814792-3.340867 2.103509-4.149275 2.350981-4.875191H2.375728L3.192384-2.515962Z'/>
</defs>
<g id='page1'>
<use x='85.143591' y='-65.164508' xlink:href='#g4-65'/>
<use x='90.669586' y='-65.164508' xlink:href='#g6-65'/>
<use x='96.801578' y='-65.164508' xlink:href='#g6-65'/>
<use x='102.93357' y='-65.164508' xlink:href='#g6-65'/>
<use x='109.065562' y='-65.164508' xlink:href='#g6-65'/>
</g>
</svg>''')
layout = SVG.parse(
source=q1,
reify=False,
ppi=DEFAULT_PPI,
width=1,
height=1,
color="black",
transform=None,
context=None
)
elements = list(layout.elements(lambda e: isinstance(e, Path)))
for i in range(2, len(elements)):
self.assertEqual(elements[i-1].d(transformed=False), elements[i].d(transformed=False))
self.assertNotEqual(elements[i - 1].transform, elements[i].transform)
self.assertNotEqual(elements[i-1], elements[i])

0 comments on commit 3fc3755

Please sign in to comment.