Skip to content

Add compare method to contour, with basic tests. #297

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 10 commits into from
Closed
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
107 changes: 106 additions & 1 deletion Lib/fontParts/base/contour.py
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ def contourInside(self, otherContour):

def _contourInside(self, otherContour):
"""
Subclasses may override this method.
Subclasses must override this method.
"""
self.raiseNotImplementedError()

Expand Down Expand Up @@ -457,6 +457,111 @@ def _get_area(self):
self.draw(pen)
return abs(pen.value)

# -------
# Compare
# -------

def compare(self, otherContour, startPoint=True, position=True,
pointTypes=False):
"""
Determine if ``otherContour`` has the same point structure as this contour.
If ``startPoint`` contours must have the same start point to compare
to ``True``. If ``position``, the contour has to have the same coordiates
to compare to ``True``. If ``pointTypes`` is ``True``, the point types must
be the same to compare to ``True``.

>>> contour.compare(anotherContour)
True

This will retun a :ref:`type-bool` indicating if the two contours have the
same struture. The path direction must be the same.

``otherContour`` must be a :class:`BaseContour`.
``startPoint`` must be a :ref:`type-bool`.
``position`` must be a :ref:`type-bool`.
``pointTypes`` must be a :ref:`type-bool`.
"""
otherContour = normalizers.normalizeContour(otherContour)
startPoint = normalizers.normalizeBoolean(startPoint)
position = normalizers.normalizeBoolean(position)
pointTypes = normalizers.normalizeBoolean(pointTypes)
return self._compare(otherContour, startPoint, position, pointTypes)

@staticmethod
def _get_pt_distances(contourPoints, pointTypes):
"""
Helper method to calcuate distance between points
"""
digest = []
for i, point in enumerate(contourPoints):
if not pointTypes:
if i is 0:
digest.append((contourPoints[len(contourPoints)-1][0]-point[0],
contourPoints[len(contourPoints)-1][1]-point[1]))
else:
digest.append((contourPoints[i-1][0]-point[0],
contourPoints[i-1][1]-point[1]))
if pointTypes:
if i is 0:
digest.append((contourPoints[len(contourPoints)-1][0][0]-point[0][0],
contourPoints[len(contourPoints)-1][0][1]-point[0][1],
contourPoints[len(contourPoints)-1][1], point[1]))
else:
digest.append((contourPoints[i-1][0][0]-point[0][0],
contourPoints[i-1][0][1]-point[0][1],
contourPoints[i-1][1], point[1]))
return digest

@staticmethod
def _shift_in_place(digest, n=1):
"""
Helper method to shift digest distances around
"""
n = n % len(digest)
head = digest[:n]
digest[:n] = []
digest.extend(head)
return digest

def _compare(self, otherContour, startPoint, position, pointTypes):
"""
Subclasses may override this method.
"""
# First a sanity check for number of points in contours
if len(self.points) != len(otherContour.points):
return False

# Handle the two cases of pointTypes
if pointTypes:
selfPoints = [(point.position, point.type) for point in self.points]
otherPoints = [(point.position, point.type) for point in otherContour.points]
else:
selfPoints = [point.position for point in self.points]
otherPoints = [point.position for point in otherContour.points]

if startPoint and position:
return selfPoints == otherPoints
elif not startPoint and position:
return sorted(selfPoints) == sorted(otherPoints)
else:
selfDigest = self._get_pt_distances(selfPoints, pointTypes)
otherDigest = self._get_pt_distances(otherPoints, pointTypes)

# If contour positions are differnt, but intial digests
# don't compare, we know start points are different.
if selfDigest != otherDigest and startPoint:
return False
elif selfDigest == otherDigest:
return True
else:
count = len(otherDigest)
while count > 0:
count = count-1
otherDigest = self._shift_in_place(otherDigest)
if selfDigest == otherDigest:
return True
return False

# --------
# Segments
# --------
Expand Down
112 changes: 112 additions & 0 deletions Lib/fontParts/test/test_contour.py
Original file line number Diff line number Diff line change
Expand Up @@ -476,3 +476,115 @@ def test_selectedBPoints_setEmptyList(self):
contour.selectedBPoints,
()
)

# -------
# Compare
# -------

def test_compare__shift_in_place(self):
contour, _ = self.objectGenerator("contour")
self.assertEqual(
contour._shift_in_place([1, 2, 3, 4]),
[2, 3, 4, 1]
)
self.assertEqual(
contour._shift_in_place([1, 2, 3, 4], 2),
[3, 4, 1, 2]
)

def test_compare__get_pt_distances_pointType_False(self):
contour = self.getContour_bounds()
pointList = [point.position for point in contour.points]
self.assertEqual(
contour._get_pt_distances(pointList, False),
[(100, 0), (0, -100), (-100, 0), (0, 100)]
)

def test_compare__get_pt_distances_pointType_True(self):
contour = self.getContour_boundsExtrema()
pointList = [(point.position, point.type) for point in contour.points]
self.assertEqual(
contour._get_pt_distances(pointList, True),
[(50, 0, "curve", "line"), (0, -100, "line", "line"),
(-50, 0, "line", "line"), (-67, 0, "line", "offcurve"),
(0, 100, "offcurve", "offcurve"), (67, 0, "offcurve", "curve")]
)

def test_compare_defaults_true(self):
contour1 = self.getContour_bounds()
contour2 = self.getContour_bounds()
self.assertTrue(contour1.compare(contour2))

def test_compare_defaults_false(self):
contour1 = self.getContour_bounds()
contour2 = self.getContour_boundsExtrema()
self.assertFalse(contour1.compare(contour2))

def test_compare_sameStartPoint_true_failing(self):
contour1 = self.getContour_bounds()
contour2 = self.getContour_bounds()
contour2.setStartSegment(2)
self.assertFalse(contour1.compare(contour2))

def test_compare_sameStartPoint_false_passing(self):
contour1 = self.getContour_bounds()
contour2 = self.getContour_bounds()
contour2.setStartSegment(2)
self.assertTrue(contour1.compare(contour2, startPoint=False))

def test_compare_different_lengths(self):
contour1 = self.getContour_bounds()
contour2, _ = self.objectGenerator("contour")
contour2.appendPoint((0, 0), "line")
contour2.appendPoint((0, 100), "line")
contour2.appendPoint((100, 100), "line")
self.assertFalse(contour1.compare(contour2))

def test_compare_samePointType_true(self):
contour1 = self.getContour_bounds()
contour2 = self.getContour_bounds()
self.assertTrue(contour1.compare(contour2, pointTypes=True))

def test_compare_samePointType_false(self):
contour1 = self.getContour_bounds()
contour2, _ = self.objectGenerator("contour")
contour2.appendPoint((0, 0), "line")
contour2.appendPoint((0, 100), "line")
contour2.appendPoint((100, 100), "curve")
contour2.appendPoint((100, 0), "line")
self.assertFalse(contour1.compare(contour2, pointTypes=True))

def test_compare_samePosition_true_failing(self):
contour1 = self.getContour_bounds()
contour2 = self.getContour_bounds()
contour2.moveBy((100, 100))
self.assertFalse(contour1.compare(contour2))

def test_compare_samePosition_false_passing(self):
contour1 = self.getContour_bounds()
contour2 = self.getContour_bounds()
contour2.moveBy((100, 100))
self.assertTrue(contour1.compare(contour2, position=False))

def test_compare_sameStartPoint_true_samePosition_false_failing(self):
contour1 = self.getContour_bounds()
contour2 = self.getContour_bounds()
contour2.setStartSegment(2)
self.assertFalse(contour1.compare(contour2, position=False))

def test_compare_sameStartPoint_false_samePosition_false_passing(self):
contour1 = self.getContour_bounds()
contour2 = self.getContour_bounds()
contour2.setStartSegment(2)
self.assertTrue(contour1.compare(contour2, startPoint=False,
position=False))

def test_compare_notSameStartPoint_notSamePosition_different_Contour_failing(self):
contour1 = self.getContour_bounds()
contour2, _ = self.objectGenerator("contour")
contour2.appendPoint((10, 0), "line")
contour2.appendPoint((0, 140), "line")
contour2.appendPoint((93, 100), "line")
contour2.appendPoint((100, 10), "line")
self.assertFalse(contour1.compare(contour2, startPoint=False,
position=False))