relative path support (#81)

* relative path support

* add test for path.d(rel=True)

* add path test to test_path.py
pull/114/head
taoari 2020-06-19 21:43:29 -04:00 committed by GitHub
parent 8457dc01ee
commit c7b6c030a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 68 additions and 23 deletions

View File

@ -2265,13 +2265,13 @@ class Path(MutableSequence):
self._end = pt self._end = pt
self._segments[-1].end = pt self._segments[-1].end = pt
def d(self, useSandT=False, use_closed_attrib=False): def d(self, useSandT=False, use_closed_attrib=False, rel=False):
"""Returns a path d-string for the path object. """Returns a path d-string for the path object.
For an explanation of useSandT and use_closed_attrib, see the For an explanation of useSandT and use_closed_attrib, see the
compatibility notes in the README.""" compatibility notes in the README."""
if use_closed_attrib: if use_closed_attrib:
self_closed = self.closed(warning_on=False) self_closed = self.iscontinuous() and self.isclosed()
if self_closed: if self_closed:
segments = self[:-1] segments = self[:-1]
else: else:
@ -2279,12 +2279,12 @@ class Path(MutableSequence):
else: else:
self_closed = False self_closed = False
segments = self[:] segments = self[:]
current_pos = None current_pos = None
parts = [] parts = []
previous_segment = None previous_segment = None
end = self[-1].end end = self[-1].end
for segment in segments: for segment in segments:
seg_start = segment.start seg_start = segment.start
# If the start of this segment does not coincide with the end of # If the start of this segment does not coincide with the end of
@ -2292,44 +2292,80 @@ class Path(MutableSequence):
# of a closed path, then we should start a new subpath here. # of a closed path, then we should start a new subpath here.
if current_pos != seg_start or \ if current_pos != seg_start or \
(self_closed and seg_start == end and use_closed_attrib): (self_closed and seg_start == end and use_closed_attrib):
parts.append('M {},{}'.format(seg_start.real, seg_start.imag)) if rel:
_seg_start = seg_start - current_pos if current_pos is not None else seg_start
else:
_seg_start = seg_start
parts.append('M {},{}'.format(_seg_start.real, _seg_start.imag))
if isinstance(segment, Line): if isinstance(segment, Line):
args = segment.end.real, segment.end.imag if rel:
parts.append('L {},{}'.format(*args)) _seg_end = segment.end - seg_start
else:
_seg_end = segment.end
parts.append('L {},{}'.format(_seg_end.real, _seg_end.imag))
elif isinstance(segment, CubicBezier): elif isinstance(segment, CubicBezier):
if useSandT and segment.is_smooth_from(previous_segment, if useSandT and segment.is_smooth_from(previous_segment,
warning_on=False): warning_on=False):
args = (segment.control2.real, segment.control2.imag, if rel:
segment.end.real, segment.end.imag) _seg_control2 = segment.control2 - seg_start
_seg_end = segment.end - seg_start
else:
_seg_control2 = segment.control2
_seg_end = segment.end
args = (_seg_control2.real, _seg_control2.imag,
_seg_end.real, _seg_end.imag)
parts.append('S {},{} {},{}'.format(*args)) parts.append('S {},{} {},{}'.format(*args))
else: else:
args = (segment.control1.real, segment.control1.imag, if rel:
segment.control2.real, segment.control2.imag, _seg_control1 = segment.control1 - seg_start
segment.end.real, segment.end.imag) _seg_control2 = segment.control2 - seg_start
_seg_end = segment.end - seg_start
else:
_seg_control1 = segment.control1
_seg_control2 = segment.control2
_seg_end = segment.end
args = (_seg_control1.real, _seg_control1.imag,
_seg_control2.real, _seg_control2.imag,
_seg_end.real, _seg_end.imag)
parts.append('C {},{} {},{} {},{}'.format(*args)) parts.append('C {},{} {},{} {},{}'.format(*args))
elif isinstance(segment, QuadraticBezier): elif isinstance(segment, QuadraticBezier):
if useSandT and segment.is_smooth_from(previous_segment, if useSandT and segment.is_smooth_from(previous_segment,
warning_on=False): warning_on=False):
args = segment.end.real, segment.end.imag if rel:
_seg_end = segment.end - seg_start
else:
_seg_end = segment.end
args = _seg_end.real, _seg_end.imag
parts.append('T {},{}'.format(*args)) parts.append('T {},{}'.format(*args))
else: else:
args = (segment.control.real, segment.control.imag, if rel:
segment.end.real, segment.end.imag) _seg_control = segment.control - seg_start
_seg_end = segment.end - seg_start
else:
_seg_control = segment.control
_seg_end = segment.end
args = (_seg_control.real, _seg_control.imag,
_seg_end.real, _seg_end.imag)
parts.append('Q {},{} {},{}'.format(*args)) parts.append('Q {},{} {},{}'.format(*args))
elif isinstance(segment, Arc): elif isinstance(segment, Arc):
if rel:
_seg_end = segment.end - seg_start
else:
_seg_end = segment.end
args = (segment.radius.real, segment.radius.imag, args = (segment.radius.real, segment.radius.imag,
segment.rotation,int(segment.large_arc), segment.rotation,int(segment.large_arc),
int(segment.sweep),segment.end.real, segment.end.imag) int(segment.sweep),_seg_end.real, _seg_end.imag)
parts.append('A {},{} {} {:d},{:d} {},{}'.format(*args)) parts.append('A {},{} {} {:d},{:d} {},{}'.format(*args))
current_pos = segment.end current_pos = segment.end
previous_segment = segment previous_segment = segment
if self_closed: if self_closed:
parts.append('Z') parts.append('Z')
return ' '.join(parts) s = ' '.join(parts)
return s if not rel else s.lower()
def joins_smoothly_with(self, previous, wrt_parameterization=False): def joins_smoothly_with(self, previous, wrt_parameterization=False):
"""Checks if this Path object joins smoothly with previous """Checks if this Path object joins smoothly with previous

View File

@ -1052,7 +1052,16 @@ class TestPath(unittest.TestCase):
pt_xpct = (pt_orig - complex(0, -100)) * 0.3 + complex(0, -100) pt_xpct = (pt_orig - complex(0, -100)) * 0.3 + complex(0, -100)
self.assertAlmostEqual(pt_xpct, pt_trns, delta=0.000001) self.assertAlmostEqual(pt_xpct, pt_trns, delta=0.000001)
def test_d(self):
# the following two path represent the same path but in absolute and relative forms
abs_s = 'M 38.0,130.0 C 37.0,132.0 38.0,136.0 40.0,137.0 L 85.0,161.0 C 87.0,162.0 91.0,162.0 93.0,160.0 L 127.0,133.0 C 129.0,131.0 129.0,128.0 127.0,126.0 L 80.0,70.0 C 78.0,67.0 75.0,68.0 74.0,70.0 Z'
rel_s = 'm 38.0,130.0 c -1.0,2.0 0.0,6.0 2.0,7.0 l 45.0,24.0 c 2.0,1.0 6.0,1.0 8.0,-1.0 l 34.0,-27.0 c 2.0,-2.0 2.0,-5.0 0.0,-7.0 l -47.0,-56.0 c -2.0,-3.0 -5.0,-2.0 -6.0,0.0 z'
path1 = parse_path(abs_s)
path2 = parse_path(rel_s)
self.assertEqual(path1.d(use_closed_attrib=True), abs_s)
self.assertEqual(path2.d(use_closed_attrib=True), abs_s)
self.assertEqual(path1.d(use_closed_attrib=True, rel=True), rel_s)
self.assertEqual(path2.d(use_closed_attrib=True, rel=True), rel_s)
class Test_ilength(unittest.TestCase): class Test_ilength(unittest.TestCase):