def test_equality(self): # This is to test the __eq__ and __ne__ methods, so we can't use # assertEqual and assertNotEqual path1 = Path( Line(start=600 + 350j, end=650 + 325j), Arc(start=650 + 325j, radius=25 + 25j, rotation=-30, arc=0, sweep=1, end=700 + 300j), CubicBezier(start=700 + 300j, control1=800 + 400j, control2=750 + 200j, end=600 + 100j), QuadraticBezier(start=600 + 100j, control=600, end=600 + 300j)) path2 = Path( Line(start=600 + 350j, end=650 + 325j), Arc(start=650 + 325j, radius=25 + 25j, rotation=-30, arc=0, sweep=1, end=700 + 300j), CubicBezier(start=700 + 300j, control1=800 + 400j, control2=750 + 200j, end=600 + 100j), QuadraticBezier(start=600 + 100j, control=600, end=600 + 300j)) self.assertTrue(path1 == path2) # Modify path2: path2[0].start = 601 + 350j self.assertTrue(path1 != path2) # Modify back: path2[0].start = 600 + 350j self.assertFalse(path1 != path2) # Get rid of the last segment: del path2[-1] self.assertFalse(path1 == path2) # It's not equal to a list of it's segments self.assertTrue(path1 != path1[:]) self.assertFalse(path1 == path1[:])
def test_equality(self): # This is to test the __eq__ and __ne__ methods, so we can't use # assertEqual and assertNotEqual segment = QuadraticBezier(200 + 300j, 400 + 50j, 600 + 300j) self.assertTrue(segment == QuadraticBezier(200 + 300j, 400 + 50j, 600 + 300j)) self.assertTrue(segment != QuadraticBezier(200 + 301j, 400 + 50j, 600 + 300j)) self.assertFalse(segment == Arc(0j, 100 + 50j, 0, 0, 0, 100 + 50j)) self.assertTrue(Arc(0j, 100 + 50j, 0, 0, 0, 100 + 50j) != segment)
def test_repr(self): from path import Point path = Path( Line(start=600 + 350j, end=650 + 325j), Arc(start=650 + 325j, radius=25 + 25j, rotation=-30, arc=0, sweep=1, end=700 + 300j), CubicBezier(start=700 + 300j, control1=800 + 400j, control2=750 + 200j, end=600 + 100j), QuadraticBezier(start=600 + 100j, control=600, end=600 + 300j)) self.assertEqual(eval(repr(path)), path)
def test_length(self): # expected results calculated with # svg.path.segment_length(q, 0, 1, q.start, q.end, 1e-14, 20, 0) q1 = QuadraticBezier(200 + 300j, 400 + 50j, 600 + 300j) q2 = QuadraticBezier(200 + 300j, 400 + 50j, 500 + 200j) closedq = QuadraticBezier(6 + 2j, 5 - 1j, 6 + 2j) linq1 = QuadraticBezier(1, 2, 3) linq2 = QuadraticBezier(1 + 3j, 2 + 5j, -9 - 17j) nodalq = QuadraticBezier(1, 1, 1) tests = [(q1, 487.77109389525975), (q2, 379.90458193489155), (closedq, 3.1622776601683795), (linq1, 2), (linq2, 22.73335777124786), (nodalq, 0)] for q, exp_res in tests: self.assertAlmostEqual(q.length(), exp_res)
def test_svg_examples(self): """These is the path in the SVG specs""" # M200,300 Q400,50 600,300 T1000,300 path1 = QuadraticBezier(200 + 300j, 400 + 50j, 600 + 300j) self.assertAlmostEqual(path1.point(0), (200 + 300j)) self.assertAlmostEqual(path1.point(0.3), (320 + 195j)) self.assertAlmostEqual(path1.point(0.5), (400 + 175j)) self.assertAlmostEqual(path1.point(0.9), (560 + 255j)) self.assertAlmostEqual(path1.point(1), (600 + 300j)) # T1000, 300 inversion = (600 + 300j) + (600 + 300j) - (400 + 50j) path2 = QuadraticBezier(600 + 300j, inversion, 1000 + 300j) self.assertAlmostEqual(path2.point(0), (600 + 300j)) self.assertAlmostEqual(path2.point(0.3), (720 + 405j)) self.assertAlmostEqual(path2.point(0.5), (800 + 425j)) self.assertAlmostEqual(path2.point(0.9), (960 + 345j)) self.assertAlmostEqual(path2.point(1), (1000 + 300j))
def parse_path(pathdef, current_pos=0j): # In the SVG specs, initial movetos are absolute, even if # specified as 'm'. This is the default behavior here as well. # But if you pass in a current_pos variable, the initial moveto # will be relative to that current_pos. This is useful. elements = list(_tokenize_path(pathdef)) # Reverse for easy use of .pop() elements.reverse() segments = Path() start_pos = None command = None while elements: if elements[-1] in COMMANDS: # New command. last_command = command # Used by S and T command = elements.pop() absolute = command in UPPERCASE command = command.upper() else: # If this element starts with numbers, it is an implicit command # and we don't change the command. Check that it's allowed: if command is None: raise ValueError("Unallowed implicit command in %s, position %s" % ( pathdef, len(pathdef.split()) - len(elements))) if command == 'M': # Moveto command. x = elements.pop() y = elements.pop() pos = float(x) + float(y) * 1j if absolute: current_pos = pos else: current_pos += pos # when M is called, reset start_pos # This behavior of Z is defined in svg spec: # http://www.w3.org/TR/SVG/paths.html#PathDataClosePathCommand start_pos = current_pos # Implicit moveto commands are treated as lineto commands. # So we set command to lineto here, in case there are # further implicit commands after this moveto. command = 'L' elif command == 'Z': # Close path if not (current_pos == start_pos): segments.append(Line(current_pos, start_pos)) segments.closed = True current_pos = start_pos start_pos = None command = None # You can't have implicit commands after closing. elif command == 'L': x = elements.pop() y = elements.pop() pos = float(x) + float(y) * 1j if not absolute: pos += current_pos segments.append(Line(current_pos, pos)) current_pos = pos elif command == 'H': x = elements.pop() pos = float(x) + current_pos.imag * 1j if not absolute: pos += current_pos.real segments.append(Line(current_pos, pos)) current_pos = pos elif command == 'V': y = elements.pop() pos = current_pos.real + float(y) * 1j if not absolute: pos += current_pos.imag * 1j segments.append(Line(current_pos, pos)) current_pos = pos elif command == 'C': control1 = float(elements.pop()) + float(elements.pop()) * 1j control2 = float(elements.pop()) + float(elements.pop()) * 1j end = float(elements.pop()) + float(elements.pop()) * 1j if not absolute: control1 += current_pos control2 += current_pos end += current_pos segments.append(CubicBezier(current_pos, control1, control2, end)) current_pos = end elif command == 'S': # Smooth curve. First control point is the "reflection" of # the second control point in the previous path. if last_command not in 'CS': # If there is no previous command or if the previous command # was not an C, c, S or s, assume the first control point is # coincident with the current point. control1 = current_pos else: # The first control point is assumed to be the reflection of # the second control point on the previous command relative # to the current point. control1 = current_pos + current_pos - segments[-1].control2 control2 = float(elements.pop()) + float(elements.pop()) * 1j end = float(elements.pop()) + float(elements.pop()) * 1j if not absolute: control2 += current_pos end += current_pos segments.append(CubicBezier(current_pos, control1, control2, end)) current_pos = end elif command == 'Q': control = float(elements.pop()) + float(elements.pop()) * 1j end = float(elements.pop()) + float(elements.pop()) * 1j if not absolute: control += current_pos end += current_pos segments.append(QuadraticBezier(current_pos, control, end)) current_pos = end elif command == 'T': # Smooth curve. Control point is the "reflection" of # the second control point in the previous path. if last_command not in 'QT': # If there is no previous command or if the previous command # was not an Q, q, T or t, assume the first control point is # coincident with the current point. control = current_pos else: # The control point is assumed to be the reflection of # the control point on the previous command relative # to the current point. control = current_pos + current_pos - segments[-1].control end = float(elements.pop()) + float(elements.pop()) * 1j if not absolute: end += current_pos segments.append(QuadraticBezier(current_pos, control, end)) current_pos = end elif command == 'A': radius = float(elements.pop()) + float(elements.pop()) * 1j rotation = float(elements.pop()) arc = float(elements.pop()) sweep = float(elements.pop()) end = float(elements.pop()) + float(elements.pop()) * 1j if not absolute: end += current_pos segments.append(Arc(current_pos, radius, rotation, arc, sweep, end)) current_pos = end return segments
def test_svg_examples(self): """Examples from the SVG spec""" path1 = parse_path('M 100 100 L 300 100 L 200 300 z') self.assertEqual( path1, Path(Line(100 + 100j, 300 + 100j), Line(300 + 100j, 200 + 300j), Line(200 + 300j, 100 + 100j))) path1 = parse_path('M 100 100 L 200 200') path2 = parse_path('M100 100L200 200') self.assertEqual(path1, path2) path1 = parse_path('M 100 200 L 200 100 L -100 -200') path2 = parse_path('M 100 200 L 200 100 -100 -200') self.assertEqual(path1, path2) path1 = parse_path("""M100,200 C100,100 250,100 250,200 S400,300 400,200""") self.assertEqual( path1, Path(CubicBezier(100 + 200j, 100 + 100j, 250 + 100j, 250 + 200j), CubicBezier(250 + 200j, 250 + 300j, 400 + 300j, 400 + 200j))) path1 = parse_path('M100,200 C100,100 400,100 400,200') self.assertEqual( path1, Path(CubicBezier(100 + 200j, 100 + 100j, 400 + 100j, 400 + 200j))) path1 = parse_path('M100,500 C25,400 475,400 400,500') self.assertEqual( path1, Path(CubicBezier(100 + 500j, 25 + 400j, 475 + 400j, 400 + 500j))) path1 = parse_path('M100,800 C175,700 325,700 400,800') self.assertEqual( path1, Path(CubicBezier(100 + 800j, 175 + 700j, 325 + 700j, 400 + 800j))) path1 = parse_path('M600,200 C675,100 975,100 900,200') self.assertEqual( path1, Path(CubicBezier(600 + 200j, 675 + 100j, 975 + 100j, 900 + 200j))) path1 = parse_path('M600,500 C600,350 900,650 900,500') self.assertEqual( path1, Path(CubicBezier(600 + 500j, 600 + 350j, 900 + 650j, 900 + 500j))) path1 = parse_path("""M600,800 C625,700 725,700 750,800 S875,900 900,800""") self.assertEqual( path1, Path(CubicBezier(600 + 800j, 625 + 700j, 725 + 700j, 750 + 800j), CubicBezier(750 + 800j, 775 + 900j, 875 + 900j, 900 + 800j))) path1 = parse_path('M200,300 Q400,50 600,300 T1000,300') self.assertEqual( path1, Path(QuadraticBezier(200 + 300j, 400 + 50j, 600 + 300j), QuadraticBezier(600 + 300j, 800 + 550j, 1000 + 300j))) path1 = parse_path('M300,200 h-150 a150,150 0 1,0 150,-150 z') self.assertEqual( path1, Path(Line(300 + 200j, 150 + 200j), Arc(150 + 200j, 150 + 150j, 0, 1, 0, 300 + 50j), Line(300 + 50j, 300 + 200j))) path1 = parse_path('M275,175 v-150 a150,150 0 0,0 -150,150 z') self.assertEqual( path1, Path(Line(275 + 175j, 275 + 25j), Arc(275 + 25j, 150 + 150j, 0, 0, 0, 125 + 175j), Line(125 + 175j, 275 + 175j))) path1 = parse_path("""M600,350 l 50,-25 a25,25 -30 0,1 50,-25 l 50,-25 a25,50 -30 0,1 50,-25 l 50,-25 a25,75 -30 0,1 50,-25 l 50,-25 a25,100 -30 0,1 50,-25 l 50,-25""") self.assertEqual( path1, Path(Line(600 + 350j, 650 + 325j), Arc(650 + 325j, 25 + 25j, -30, 0, 1, 700 + 300j), Line(700 + 300j, 750 + 275j), Arc(750 + 275j, 25 + 50j, -30, 0, 1, 800 + 250j), Line(800 + 250j, 850 + 225j), Arc(850 + 225j, 25 + 75j, -30, 0, 1, 900 + 200j), Line(900 + 200j, 950 + 175j), Arc(950 + 175j, 25 + 100j, -30, 0, 1, 1000 + 150j), Line(1000 + 150j, 1050 + 125j)))
def test_svg_examples(self): """These is the path in the SVG specs""" # M200,300 Q400,50 600,300 T1000,300 path1 = QuadraticBezier(200 + 300j, 400 + 50j, 600 + 300j) self.assertAlmostEqual(path1.point(0), (200 + 300j)) self.assertAlmostEqual(path1.point(0.3), (336.8 + 142.5j)) self.assertAlmostEqual(path1.point(0.5), (400 + 112.5j)) self.assertAlmostEqual(path1.point(0.9), (545.6 + 232.5j)) self.assertAlmostEqual(path1.point(1), (600 + 300j)) # T1000, 300 inversion = (600 + 300j) + (600 + 300j) - (400 + 50j) path2 = QuadraticBezier(600 + 300j, inversion, 1000 + 300j) self.assertAlmostEqual(path2.point(0), (600 + 300j)) self.assertAlmostEqual(path2.point(0.3), (736.8 + 457.5j)) self.assertAlmostEqual(path2.point(0.5), (800 + 487.5j)) self.assertAlmostEqual(path2.point(0.9), (945.6 + 367.5j)) self.assertAlmostEqual(path2.point(1), (1000 + 300j))