Skip to content

Commit

Permalink
Fix #319 by nudging the first coordinate of some shapes. (#320)
Browse files Browse the repository at this point in the history
* Fix #319 by nudging the first coordinate of some shapes.

* Improve efficiency of the fix for #319.
  • Loading branch information
donkirkby authored Feb 11, 2022
1 parent 6afce60 commit 43476a5
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 3 deletions.
27 changes: 25 additions & 2 deletions svglib/svglib.py
Original file line number Diff line number Diff line change
Expand Up @@ -967,8 +967,9 @@ def convert_length_attrs(self, node, *attrs, em_base=DEFAULT_FONT_SIZE, **kwargs
]

def convertLine(self, node):
x1, y1, x2, y2 = self.convert_length_attrs(node, 'x1', 'y1', 'x2', 'y2')
return Line(x1, y1, x2, y2)
points = self.convert_length_attrs(node, 'x1', 'y1', 'x2', 'y2')
nudge_points(points)
return Line(*points)

def convertRect(self, node):
x, y, width, height, rx, ry = self.convert_length_attrs(
Expand Down Expand Up @@ -999,6 +1000,7 @@ def convertPolyline(self, node):
# Odd number of coordinates or no coordinates, invalid polyline
return None

nudge_points(points)
polyline = PolyLine(points)
self.applyStyleOnShape(polyline, node)
has_fill = self.attrConverter.findAttr(node, 'fill') not in ('', 'none')
Expand All @@ -1024,6 +1026,7 @@ def convertPolygon(self, node):
if len(points) % 2 != 0 or len(points) == 0:
# Odd number of coordinates or no coordinates, invalid polygon
return None
nudge_points(points)
shape = Polygon(points)

return shape
Expand Down Expand Up @@ -1462,6 +1465,26 @@ def svg2rlg(path, resolve_entities=False, **kwargs):
return drawing


def nudge_points(points):
""" Nudge first coordinate if all coordinate pairs are identical.
This works around reportlab's decision to hide shapes of size zero, even
when the stroke should be visible.
"""
if not points:
return
if len(points) < 4:
return
x = points[0]
y = points[1]
for i in range(2, len(points)-1, 2):
if x != points[i] or y != points[i+1]:
break
else:
# All points were identical, so we nudge.
points[0] *= 1.0000001


def load_svg_file(path, resolve_entities=False):
parser = etree.XMLParser(
remove_comments=True, recover=True, resolve_entities=resolve_entities
Expand Down
70 changes: 69 additions & 1 deletion tests/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from tempfile import NamedTemporaryFile

from reportlab.graphics.shapes import (
_CLOSEPATH, _CURVETO, _LINETO, _MOVETO, Group, Path, Polygon, PolyLine, Rect,
_CLOSEPATH, _CURVETO, _LINETO, _MOVETO, Group, Path, Polygon, PolyLine, Rect, Line,
)
from reportlab.lib import colors
from reportlab.lib.units import cm, inch
Expand Down Expand Up @@ -702,6 +702,30 @@ def test_strokewidth_0(self):
assert rect.strokeColor is None


class TestLineNode:
def test_length_zero(self):
converter = svglib.Svg2RlgShapeConverter(None)
node = svglib.NodeTracker(etree.XML(
'<line stroke="#000000" x1="10" y1="20" x2="10" y2="20" />'
))
line = converter.convertLine(node)
assert isinstance(line, Line)

# Force a slight change in the first coordinate, so it's not invisible.
assert line.x1 != 10

def test_length_not_zero(self):
""" No change needed when length is not zero. """
converter = svglib.Svg2RlgShapeConverter(None)
node = svglib.NodeTracker(etree.XML(
'<line stroke="#000000" x1="10" y1="20" x2="10" y2="30" />'
))
line = converter.convertLine(node)
assert isinstance(line, Line)

assert line.x1 == 10


class TestPolylineNode:
def test_filling(self):
converter = svglib.Svg2RlgShapeConverter(None)
Expand All @@ -722,6 +746,50 @@ def test_filling(self):
assert group.contents[0].fillColor == colors.white
assert isinstance(group.contents[1], PolyLine)

def test_length_zero(self):
converter = svglib.Svg2RlgShapeConverter(None)
node = svglib.NodeTracker(etree.XML(
'<polyline fill="none" stroke="#000000" '
'points="10,50,10,50" />'
))
polyline = converter.convertPolyline(node)
assert isinstance(polyline, PolyLine)

# Force a slight change in the first coordinate, so it's not invisible.
assert polyline.points[0] != 10

def test_odd_length(self):
converter = svglib.Svg2RlgShapeConverter(None)
node = svglib.NodeTracker(etree.XML(
'<polyline fill="none" stroke="#000000" '
'points="10,50,10,50,10" />'
))
polyline = converter.convertPolyline(node)
assert polyline is None


class TestPolygonNode:
def test_length_zero(self):
converter = svglib.Svg2RlgShapeConverter(None)
node = svglib.NodeTracker(etree.XML(
'<polygon fill="none" stroke="#000000" '
'points="10,50,10,50" />'
))
polygon = converter.convertPolygon(node)
assert isinstance(polygon, Polygon)

# Force a slight change in the first coordinate, so it's not invisible.
assert polygon.points[0] != 10

def test_odd_length(self):
converter = svglib.Svg2RlgShapeConverter(None)
node = svglib.NodeTracker(etree.XML(
'<polygon fill="none" stroke="#000000" '
'points="10,50,10,50,10" />'
))
polygon = converter.convertPolygon(node)
assert polygon is None


class TestUseNode:
def test_use(self, drawing_source=None):
Expand Down

0 comments on commit 43476a5

Please sign in to comment.