Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 33 additions & 8 deletions svgpathtools/svg_to_paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def path2pathd(path):
return path.get('d', '')


def ellipse2pathd(ellipse):
def ellipse2pathd(ellipse, use_cubics=False):
"""converts the parameters from an ellipse or a circle to a string for a
Path object d-attribute"""

Expand All @@ -46,10 +46,32 @@ def ellipse2pathd(ellipse):
cx = float(cx)
cy = float(cy)

d = ''
d += 'M' + str(cx - rx) + ',' + str(cy)
d += 'a' + str(rx) + ',' + str(ry) + ' 0 1,0 ' + str(2 * rx) + ',0'
d += 'a' + str(rx) + ',' + str(ry) + ' 0 1,0 ' + str(-2 * rx) + ',0'
if use_cubics:
# Modified by NXP 2024, 2025
PATH_KAPPA = 0.552284
rxKappa = rx * PATH_KAPPA;
ryKappa = ry * PATH_KAPPA;

#According to the SVG specification (https://lists.w3.org/Archives/Public/www-archive/2005May/att-0005/SVGT12_Main.pdf),
#Section 9.4, "The 'ellipse' element": "The arc of an 'ellipse' element begins at the "3 o'clock" point on
#the radius and progresses towards the "9 o'clock". Therefore, the ellipse begins at the rightmost point
#and progresses clockwise.
d = ''
# Move to the rightmost point
d += 'M' + str(cx + rx) + ' ' + str(cy)
# Draw bottom-right quadrant
d += 'C' + str(cx + rx) + ' ' + str(cy + ryKappa) + ' ' + str(cx + rxKappa) + ' ' + str(cy + ry) + ' ' + str(cx) + ' ' + str(cy + ry)
# Draw bottom-left quadrant
d += 'C' + str(cx - rxKappa) + ' ' + str(cy + ry) + ' ' + str(cx - rx) + ' ' + str(cy + ryKappa) + ' ' + str(cx - rx) + ' ' + str(cy)
# Draw top-left quadrant
d += 'C' + str(cx - rx) + ' ' + str(cy - ryKappa) + ' ' + str(cx - rxKappa) + ' ' + str(cy - ry) + ' ' + str(cx) + ' ' + str(cy - ry)
# Draw top-right quadrant
d += 'C' + str(cx + rxKappa) + ' ' + str(cy - ry) + ' ' + str(cx + rx) + ' ' + str(cy - ryKappa) + ' ' + str(cx + rx) + ' ' + str(cy)
else:
d = ''
d += 'M' + str(cx - rx) + ',' + str(cy)
d += 'a' + str(rx) + ',' + str(ry) + ' 0 1,0 ' + str(2 * rx) + ',0'
d += 'a' + str(rx) + ',' + str(ry) + ' 0 1,0 ' + str(-2 * rx) + ',0'

return d + 'z'

Expand All @@ -62,6 +84,9 @@ def polyline2pathd(polyline, is_polygon=False):
else:
points = COORD_PAIR_TMPLT.findall(polyline.get('points', ''))

if len(points) == 0:
return ''

closed = (float(points[0][0]) == float(points[-1][0]) and
float(points[0][1]) == float(points[-1][1]))

Expand All @@ -77,13 +102,13 @@ def polyline2pathd(polyline, is_polygon=False):
return d


def polygon2pathd(polyline):
def polygon2pathd(polyline, is_polygon=True):
"""converts the string from a polygon points-attribute to a string
for a Path object d-attribute.
Note: For a polygon made from n points, the resulting path will be
composed of n lines (even if some of these lines have length zero).
"""
return polyline2pathd(polyline, True)
return polyline2pathd(polyline, is_polygon)


def rect2pathd(rect):
Expand Down Expand Up @@ -205,7 +230,7 @@ def dom2dict(element):
# path strings, add to list
if convert_polygons_to_paths:
pgons = [dom2dict(el) for el in doc.getElementsByTagName('polygon')]
d_strings += [polygon2pathd(pg) for pg in pgons]
d_strings += [polygon2pathd(pg, True) for pg in pgons]
attribute_dictionary_list += pgons

if convert_lines_to_paths:
Expand Down
5 changes: 5 additions & 0 deletions test/polygons_no_points.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions test/polyline.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 38 additions & 0 deletions test/test_svg2paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,44 @@ def test_from_string(self):

self.assertEqual(len(paths), 2)

def test_svg2paths_polygon_no_points(self):

paths, _ = svg2paths(join(dirname(__file__), 'polygons_no_points.svg'))

path = paths[0]
path_correct = Path()
self.assertTrue(len(path)==0)
self.assertTrue(path==path_correct)

path = paths[1]
self.assertTrue(len(path)==0)
self.assertTrue(path==path_correct)

def test_svg2paths_polyline_tests(self):

paths, _ = svg2paths(join(dirname(__file__), 'polyline.svg'))

path = paths[0]
path_correct = Path(Line(59+185j, 98+203j),
Line(98+203j, 108+245j),
Line(108+245j, 82+279j),
Line(82+279j, 39+280j),
Line(39+280j, 11+247j),
Line(11+247j, 19+205j))
self.assertFalse(path.isclosed())
self.assertTrue(len(path)==6)
self.assertTrue(path==path_correct)

path = paths[1]
path_correct = Path(Line(220+50j, 267+84j),
Line(267+84j, 249+140j),
Line(249+140j, 190+140j),
Line(190+140j, 172+84j),
Line(172+84j, 220+50j))
self.assertTrue(path.isclosed())
self.assertTrue(len(path)==5)
self.assertTrue(path==path_correct)


if __name__ == '__main__':
unittest.main()
Loading