def _convert_vmobject_to_skia_path(self, vmobject: VMobject) -> SkiaPath: """Converts a :class:`~.VMobject` to SkiaPath. This method only works for cairo renderer because it treats the points as Cubic beizer curves. Parameters ---------- vmobject: The :class:`~.VMobject` to convert from. Returns ------- SkiaPath: The converted path. """ path = SkiaPath() if not np.all(np.isfinite(vmobject.points)): points = np.zeros((1, 3)) # point invalid? else: points = vmobject.points if len(points) == 0: # what? No points so return empty path return path # In OpenGL it's quadratic beizer curves while on Cairo it's cubic... if config.renderer == "opengl": subpaths = vmobject.get_subpaths_from_points(points) for subpath in subpaths: quads = vmobject.get_bezier_tuples_from_points(subpath) start = subpath[0] path.moveTo(*start[:2]) for p0, p1, p2 in quads: path.quadTo(*p1[:2], *p2[:2]) if vmobject.consider_points_equals(subpath[0], subpath[-1]): path.close() else: subpaths = vmobject.gen_subpaths_from_points_2d(points) for subpath in subpaths: quads = vmobject.gen_cubic_bezier_tuples_from_points(subpath) start = subpath[0] path.moveTo(*start[:2]) for p0, p1, p2, p3 in quads: path.cubicTo(*p1[:2], *p2[:2], *p3[:2]) if vmobject.consider_points_equals_2d(subpath[0], subpath[-1]): path.close() return path
def test_transform(self): path = Path() path.moveTo(125, 376) path.cubicTo(181, 376, 218, 339, 218, 290) path.cubicTo(218, 225, 179, 206, 125, 206) path.close() # t = Transform().rotate(radians(-45)).translate(-100, 0) matrix = (0.707107, -0.707107, 0.707107, 0.707107, -70.7107, 70.7107) result = path.transform(*matrix) expected = Path() expected.moveTo( bits2float(0x438dc663), # 283.55 bits2float(0x437831ce), # 248.195 ) expected.cubicTo( bits2float(0x43a192ee), # 323.148 bits2float(0x435098b8), # 208.597 bits2float(0x43a192ee), # 323.148 bits2float(0x431c454a), # 156.271 bits2float(0x43903ff5), # 288.5 bits2float(0x42f33ead), # 121.622 ) expected.cubicTo( bits2float(0x437289a8), # 242.538 bits2float(0x42975227), # 75.6605 bits2float(0x43498688), # 201.526 bits2float(0x42b39aee), # 89.8026 bits2float(0x4323577c), # 163.342 bits2float(0x42fff906), # 127.986 ) expected.close() result.dump(as_hex=True) assert result == expected
def test_duplicate_start_point(): # https://github.com/fonttools/skia-pathops/issues/13 path = Path() path.moveTo( bits2float(0x43480000), # 200 bits2float(0x43db8ce9), # 439.101 ) path.lineTo( bits2float(0x43480000), # 200 bits2float(0x4401c000), # 519 ) path.cubicTo( bits2float(0x43480000), # 200 bits2float(0x441f0000), # 636 bits2float(0x43660000), # 230 bits2float(0x44340000), # 720 bits2float(0x43c80000), # 400 bits2float(0x44340000), # 720 ) path.cubicTo( bits2float(0x4404c000), # 531 bits2float(0x44340000), # 720 bits2float(0x440d0000), # 564 bits2float(0x442b8000), # 686 bits2float(0x44118000), # 582 bits2float(0x4416c000), # 603 ) path.lineTo( bits2float(0x442cc000), # 691 bits2float(0x441c8000), # 626 ) path.cubicTo( bits2float(0x44260000), # 664 bits2float(0x443d4000), # 757 bits2float(0x44114000), # 581 bits2float(0x444a8000), # 810 bits2float(0x43c88000), # 401 bits2float(0x444a8000), # 810 ) path.cubicTo( bits2float(0x43350000), # 181 bits2float(0x444a8000), # 810 bits2float(0x42c80000), # 100 bits2float(0x442e0000), # 696 bits2float(0x42c80000), # 100 bits2float(0x4401c000), # 519 ) path.lineTo( bits2float(0x42c80000), # 100 bits2float(0x438a8000), # 277 ) path.cubicTo( bits2float(0x42c80000), # 100 bits2float(0x42cc0000), # 102 bits2float(0x433e0000), # 190 bits2float(0xc1200000), # -10 bits2float(0x43cd0000), # 410 bits2float(0xc1200000), # -10 ) path.cubicTo( bits2float(0x441d8000), # 630 bits2float(0xc1200000), # -10 bits2float(0x442f0000), # 700 bits2float(0x42e60000), # 115 bits2float(0x442f0000), # 700 bits2float(0x437a0000), # 250 ) path.lineTo( bits2float(0x442f0000), # 700 bits2float(0x43880000), # 272 ) path.cubicTo( bits2float(0x442f0000), # 700 bits2float(0x43d18000), # 419 bits2float(0x44164000), # 601 bits2float(0x43fa0000), # 500 bits2float(0x43c88000), # 401 bits2float(0x43fa0000), # 500 ) path.cubicTo( bits2float(0x43964752), # 300.557 bits2float(0x43fa0000), # 500 bits2float(0x436db1ed), # 237.695 bits2float(0x43ef6824), # 478.814 bits2float(0x43480000), # 200 bits2float(0x43db8ce9), # 439.101 ) path.close() path.moveTo( bits2float(0x434805cb), # 200.023 bits2float(0x43881798), # 272.184 ) path.cubicTo( bits2float(0x43493da4), # 201.241 bits2float(0x43b2a869), # 357.316 bits2float(0x437bd6b1), # 251.839 bits2float(0x43cd0000), # 410 bits2float(0x43c80000), # 400 bits2float(0x43cd0000), # 410 ) path.cubicTo( bits2float(0x44098000), # 550 bits2float(0x43cd0000), # 410 bits2float(0x44160000), # 600 bits2float(0x43b20000), # 356 bits2float(0x44160000), # 600 bits2float(0x43868000), # 269 ) path.lineTo( bits2float(0x44160000), # 600 bits2float(0x43808000), # 257 ) path.cubicTo( bits2float(0x44160000), # 600 bits2float(0x43330000), # 179 bits2float(0x44110000), # 580 bits2float(0x429c0000), # 78 bits2float(0x43cd0000), # 410 bits2float(0x429c0000), # 78 ) path.cubicTo( bits2float(0x43725298), # 242.323 bits2float(0x429c0000), # 78 bits2float(0x43491e05), # 201.117 bits2float(0x431ccd43), # 156.802 bits2float(0x434805cb), # 200.023 bits2float(0x43881797), # 272.184 ) path.close() contours = list(path.contours) # on the second contour, the last and first points' Y coordinate only # differ by one bit: 0x43881798 != 0x43881797 points = contours[1].points assert points[0] != points[-1] assert points[0] == pytest.approx(points[-1]) # when "drawn" as segments, almost equal last/first points are treated # as exactly equal, without the need of an extra closing lineTo for contour in path.contours: segments = list(contour.segments) assert segments[-1][0] == "closePath" first_type, first_pts = segments[0] last_type, last_pts = segments[-2] assert first_type == "moveTo" assert last_type == "curveTo" assert last_pts[-1] == first_pts[-1]