up follow livre
This commit is contained in:
parent
b4b4398bb0
commit
3a7a3849ae
12242 changed files with 2564461 additions and 6914 deletions
|
@ -0,0 +1,192 @@
|
|||
"""fontTools.pens.pointInsidePen -- Pen implementing "point inside" testing
|
||||
for shapes.
|
||||
"""
|
||||
|
||||
from fontTools.pens.basePen import BasePen
|
||||
from fontTools.misc.bezierTools import solveQuadratic, solveCubic
|
||||
|
||||
|
||||
__all__ = ["PointInsidePen"]
|
||||
|
||||
|
||||
class PointInsidePen(BasePen):
|
||||
"""This pen implements "point inside" testing: to test whether
|
||||
a given point lies inside the shape (black) or outside (white).
|
||||
Instances of this class can be recycled, as long as the
|
||||
setTestPoint() method is used to set the new point to test.
|
||||
|
||||
:Example:
|
||||
.. code-block::
|
||||
|
||||
pen = PointInsidePen(glyphSet, (100, 200))
|
||||
outline.draw(pen)
|
||||
isInside = pen.getResult()
|
||||
|
||||
Both the even-odd algorithm and the non-zero-winding-rule
|
||||
algorithm are implemented. The latter is the default, specify
|
||||
True for the evenOdd argument of __init__ or setTestPoint
|
||||
to use the even-odd algorithm.
|
||||
"""
|
||||
|
||||
# This class implements the classical "shoot a ray from the test point
|
||||
# to infinity and count how many times it intersects the outline" (as well
|
||||
# as the non-zero variant, where the counter is incremented if the outline
|
||||
# intersects the ray in one direction and decremented if it intersects in
|
||||
# the other direction).
|
||||
# I found an amazingly clear explanation of the subtleties involved in
|
||||
# implementing this correctly for polygons here:
|
||||
# http://graphics.cs.ucdavis.edu/~okreylos/TAship/Spring2000/PointInPolygon.html
|
||||
# I extended the principles outlined on that page to curves.
|
||||
|
||||
def __init__(self, glyphSet, testPoint, evenOdd=False):
|
||||
BasePen.__init__(self, glyphSet)
|
||||
self.setTestPoint(testPoint, evenOdd)
|
||||
|
||||
def setTestPoint(self, testPoint, evenOdd=False):
|
||||
"""Set the point to test. Call this _before_ the outline gets drawn."""
|
||||
self.testPoint = testPoint
|
||||
self.evenOdd = evenOdd
|
||||
self.firstPoint = None
|
||||
self.intersectionCount = 0
|
||||
|
||||
def getWinding(self):
|
||||
if self.firstPoint is not None:
|
||||
# always make sure the sub paths are closed; the algorithm only works
|
||||
# for closed paths.
|
||||
self.closePath()
|
||||
return self.intersectionCount
|
||||
|
||||
def getResult(self):
|
||||
"""After the shape has been drawn, getResult() returns True if the test
|
||||
point lies within the (black) shape, and False if it doesn't.
|
||||
"""
|
||||
winding = self.getWinding()
|
||||
if self.evenOdd:
|
||||
result = winding % 2
|
||||
else: # non-zero
|
||||
result = self.intersectionCount != 0
|
||||
return not not result
|
||||
|
||||
def _addIntersection(self, goingUp):
|
||||
if self.evenOdd or goingUp:
|
||||
self.intersectionCount += 1
|
||||
else:
|
||||
self.intersectionCount -= 1
|
||||
|
||||
def _moveTo(self, point):
|
||||
if self.firstPoint is not None:
|
||||
# always make sure the sub paths are closed; the algorithm only works
|
||||
# for closed paths.
|
||||
self.closePath()
|
||||
self.firstPoint = point
|
||||
|
||||
def _lineTo(self, point):
|
||||
x, y = self.testPoint
|
||||
x1, y1 = self._getCurrentPoint()
|
||||
x2, y2 = point
|
||||
|
||||
if x1 < x and x2 < x:
|
||||
return
|
||||
if y1 < y and y2 < y:
|
||||
return
|
||||
if y1 >= y and y2 >= y:
|
||||
return
|
||||
|
||||
dx = x2 - x1
|
||||
dy = y2 - y1
|
||||
t = (y - y1) / dy
|
||||
ix = dx * t + x1
|
||||
if ix < x:
|
||||
return
|
||||
self._addIntersection(y2 > y1)
|
||||
|
||||
def _curveToOne(self, bcp1, bcp2, point):
|
||||
x, y = self.testPoint
|
||||
x1, y1 = self._getCurrentPoint()
|
||||
x2, y2 = bcp1
|
||||
x3, y3 = bcp2
|
||||
x4, y4 = point
|
||||
|
||||
if x1 < x and x2 < x and x3 < x and x4 < x:
|
||||
return
|
||||
if y1 < y and y2 < y and y3 < y and y4 < y:
|
||||
return
|
||||
if y1 >= y and y2 >= y and y3 >= y and y4 >= y:
|
||||
return
|
||||
|
||||
dy = y1
|
||||
cy = (y2 - dy) * 3.0
|
||||
by = (y3 - y2) * 3.0 - cy
|
||||
ay = y4 - dy - cy - by
|
||||
solutions = sorted(solveCubic(ay, by, cy, dy - y))
|
||||
solutions = [t for t in solutions if -0.0 <= t <= 1.0]
|
||||
if not solutions:
|
||||
return
|
||||
|
||||
dx = x1
|
||||
cx = (x2 - dx) * 3.0
|
||||
bx = (x3 - x2) * 3.0 - cx
|
||||
ax = x4 - dx - cx - bx
|
||||
|
||||
above = y1 >= y
|
||||
lastT = None
|
||||
for t in solutions:
|
||||
if t == lastT:
|
||||
continue
|
||||
lastT = t
|
||||
t2 = t * t
|
||||
t3 = t2 * t
|
||||
|
||||
direction = 3 * ay * t2 + 2 * by * t + cy
|
||||
incomingGoingUp = outgoingGoingUp = direction > 0.0
|
||||
if direction == 0.0:
|
||||
direction = 6 * ay * t + 2 * by
|
||||
outgoingGoingUp = direction > 0.0
|
||||
incomingGoingUp = not outgoingGoingUp
|
||||
if direction == 0.0:
|
||||
direction = ay
|
||||
incomingGoingUp = outgoingGoingUp = direction > 0.0
|
||||
|
||||
xt = ax * t3 + bx * t2 + cx * t + dx
|
||||
if xt < x:
|
||||
continue
|
||||
|
||||
if t in (0.0, -0.0):
|
||||
if not outgoingGoingUp:
|
||||
self._addIntersection(outgoingGoingUp)
|
||||
elif t == 1.0:
|
||||
if incomingGoingUp:
|
||||
self._addIntersection(incomingGoingUp)
|
||||
else:
|
||||
if incomingGoingUp == outgoingGoingUp:
|
||||
self._addIntersection(outgoingGoingUp)
|
||||
# else:
|
||||
# we're not really intersecting, merely touching
|
||||
|
||||
def _qCurveToOne_unfinished(self, bcp, point):
|
||||
# XXX need to finish this, for now doing it through a cubic
|
||||
# (BasePen implements _qCurveTo in terms of a cubic) will
|
||||
# have to do.
|
||||
x, y = self.testPoint
|
||||
x1, y1 = self._getCurrentPoint()
|
||||
x2, y2 = bcp
|
||||
x3, y3 = point
|
||||
c = y1
|
||||
b = (y2 - c) * 2.0
|
||||
a = y3 - c - b
|
||||
solutions = sorted(solveQuadratic(a, b, c - y))
|
||||
solutions = [
|
||||
t for t in solutions if ZERO_MINUS_EPSILON <= t <= ONE_PLUS_EPSILON
|
||||
]
|
||||
if not solutions:
|
||||
return
|
||||
# XXX
|
||||
|
||||
def _closePath(self):
|
||||
if self._getCurrentPoint() != self.firstPoint:
|
||||
self.lineTo(self.firstPoint)
|
||||
self.firstPoint = None
|
||||
|
||||
def _endPath(self):
|
||||
"""Insideness is not defined for open contours."""
|
||||
raise NotImplementedError
|
Loading…
Add table
Add a link
Reference in a new issue