~ubuntu-branches/ubuntu/feisty/fonttools/feisty

« back to all changes in this revision

Viewing changes to Lib/fontTools/pens/pointInsidePen.py

  • Committer: Bazaar Package Importer
  • Author(s): Anthony Fok
  • Date: 2003-11-18 00:53:59 UTC
  • Revision ID: james.westby@ubuntu.com-20031118005359-pqirsxbgdz0f0xmx
Tags: upstream-1.99+2.0b1+cvs20031014
ImportĀ upstreamĀ versionĀ 1.99+2.0b1+cvs20031014

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""fontTools.pens.pointInsidePen -- Pen implementing "point inside" testing
 
2
for shapes.
 
3
"""
 
4
 
 
5
from fontTools.pens.basePen import BasePen
 
6
from fontTools.misc.bezierTools import solveQuadratic, solveCubic
 
7
 
 
8
 
 
9
__all__ = ["PointInsidePen"]
 
10
 
 
11
 
 
12
# working around floating point errors
 
13
EPSILON = 1e-10
 
14
ONE_PLUS_EPSILON = 1 + EPSILON
 
15
ZERO_MINUS_EPSILON = 0 - EPSILON
 
16
 
 
17
 
 
18
class PointInsidePen(BasePen):
 
19
 
 
20
        """This pen implements "point inside" testing: to test whether
 
21
        a given point lies inside the shape (black) or outside (white).
 
22
        Instances of this class can be recycled, as long as the
 
23
        setTestPoint() method is used to set the new point to test.
 
24
 
 
25
        Typical usage:
 
26
 
 
27
                pen = PointInsidePen(glyphSet, (100, 200))
 
28
                outline.draw(pen)
 
29
                isInside = pen.getResult()
 
30
 
 
31
        Both the even-odd algorithm and the non-zero-winding-rule
 
32
        algorithm are implemented. The latter is the default, specify
 
33
        True for the evenOdd argument of __init__ or setTestPoint
 
34
        to use the even-odd algorithm.
 
35
        """
 
36
 
 
37
        # This class implements the classical "shoot a ray from the test point
 
38
        # to infinity and count how many times it intersects the outline" (as well
 
39
        # as the non-zero variant, where the counter is incremented if the outline
 
40
        # intersects the ray in one direction and decremented if it intersects in
 
41
        # the other direction).
 
42
        # I found an amazingly clear explanation of the subtleties involved in
 
43
        # implementing this correctly for polygons here:
 
44
        #   http://graphics.cs.ucdavis.edu/~okreylos/TAship/Spring2000/PointInPolygon.html
 
45
        # I extended the principles outlined on that page to curves.
 
46
 
 
47
        def __init__(self, glyphSet, testPoint, evenOdd=0):
 
48
                BasePen.__init__(self, glyphSet)
 
49
                self.setTestPoint(testPoint, evenOdd)
 
50
 
 
51
        def setTestPoint(self, testPoint, evenOdd=0):
 
52
                """Set the point to test. Call this _before_ the outline gets drawn."""
 
53
                self.testPoint = testPoint
 
54
                self.evenOdd = evenOdd
 
55
                self.firstPoint = None
 
56
                self.intersectionCount = 0
 
57
 
 
58
        def getResult(self):
 
59
                """After the shape has been drawn, getResult() returns True if the test
 
60
                point lies within the (black) shape, and False if it doesn't.
 
61
                """
 
62
                if self.firstPoint is not None:
 
63
                        # always make sure the sub paths are closed; the algorithm only works
 
64
                        # for closed paths.
 
65
                        self.closePath()
 
66
                if self.evenOdd:
 
67
                        result = self.intersectionCount % 2
 
68
                else:
 
69
                        result = self.intersectionCount
 
70
                return not not result
 
71
 
 
72
        def _addIntersection(self, goingUp):
 
73
                if self.evenOdd or goingUp:
 
74
                        self.intersectionCount += 1
 
75
                else:
 
76
                        self.intersectionCount -= 1
 
77
 
 
78
        def _moveTo(self, point):
 
79
                if self.firstPoint is not None:
 
80
                        # always make sure the sub paths are closed; the algorithm only works
 
81
                        # for closed paths.
 
82
                        self.closePath()
 
83
                self.firstPoint = point
 
84
 
 
85
        def _lineTo(self, point):
 
86
                x, y = self.testPoint
 
87
                x1, y1 = self._getCurrentPoint()
 
88
                x2, y2 = point
 
89
 
 
90
                if x1 < x and x2 < x:
 
91
                        return
 
92
                if y1 < y and y2 < y:
 
93
                        return
 
94
                if y1 >= y and y2 >= y:
 
95
                        return
 
96
 
 
97
                dx = x2 - x1
 
98
                dy = y2 - y1
 
99
                t = float(y - y1) / dy
 
100
                ix = dx * t + x1
 
101
                if ix < x:
 
102
                        return
 
103
                self._addIntersection(y2 > y1)
 
104
 
 
105
        def _curveToOne(self, bcp1, bcp2, point):
 
106
                x, y = self.testPoint
 
107
                x1, y1 = self._getCurrentPoint()
 
108
                x2, y2 = bcp1
 
109
                x3, y3 = bcp2
 
110
                x4, y4 = point
 
111
 
 
112
                if x1 < x and x2 < x and x3 < x and x4 < x:
 
113
                        return
 
114
                if y1 < y and y2 < y and y3 < y and y4 < y:
 
115
                        return
 
116
                if y1 >= y and y2 >= y and y3 >= y and y4 >= y:
 
117
                        return
 
118
 
 
119
                dy = y1
 
120
                cy = (y2 - dy) * 3.0
 
121
                by = (y3 - y2) * 3.0 - cy
 
122
                ay = y4 - dy - cy - by
 
123
                solutions = solveCubic(ay, by, cy, dy - y)
 
124
                solutions.sort()
 
125
                solutions = [t for t in solutions if ZERO_MINUS_EPSILON <= t <= ONE_PLUS_EPSILON]
 
126
                if not solutions:
 
127
                        return
 
128
 
 
129
                dx = x1
 
130
                cx = (x2 - dx) * 3.0
 
131
                bx = (x3 - x2) * 3.0 - cx
 
132
                ax = x4 - dx - cx - bx
 
133
 
 
134
                above = y1 >= y
 
135
                lastT = None
 
136
                for t in solutions:
 
137
                        if t == lastT:
 
138
                                continue
 
139
                        lastT = t
 
140
                        t2 = t * t
 
141
                        t3 = t2 * t
 
142
 
 
143
                        direction = 3*ay*t2 + 2*by*t + cy
 
144
                        if direction == 0.0:
 
145
                                direction = 6*ay*t + 2*by
 
146
                                if direction == 0.0:
 
147
                                        direction = ay
 
148
                        goingUp = direction > 0.0
 
149
 
 
150
                        xt = ax*t3 + bx*t2 + cx*t + dx
 
151
                        if xt < x:
 
152
                                above = goingUp
 
153
                                continue
 
154
 
 
155
                        if t == 0.0:
 
156
                                if not goingUp:
 
157
                                        self._addIntersection(goingUp)
 
158
                        elif t == 1.0:
 
159
                                if not above:
 
160
                                        self._addIntersection(goingUp)
 
161
                        else:
 
162
                                if above != goingUp:
 
163
                                        self._addIntersection(goingUp)
 
164
                                #else:
 
165
                                #   we're not really intersecting, merely touching the 'top'
 
166
                        above = goingUp
 
167
 
 
168
        def _qCurveToOne_unfinished(self, bcp, point):
 
169
                # XXX need to finish this, for now doing it through a cubic
 
170
                # (BasePen implements _qCurveTo in terms of a cubic) will
 
171
                # have to do.
 
172
                x, y = self.testPoint
 
173
                x1, y1 = self._getCurrentPoint()
 
174
                x2, y2 = bcp
 
175
                x3, y3 = point
 
176
                c = y1
 
177
                b = (y2 - c) * 2.0
 
178
                a = y3 - c - b
 
179
                solutions = solveQuadratic(a, b, c - y)
 
180
                solutions.sort()
 
181
                solutions = [t for t in solutions if ZERO_MINUS_EPSILON <= t <= ONE_PLUS_EPSILON]
 
182
                if not solutions:
 
183
                        return
 
184
                XXX
 
185
 
 
186
        def _closePath(self):
 
187
                if self._getCurrentPoint() != self.firstPoint:
 
188
                        self.lineTo(self.firstPoint)
 
189
                self.firstPoint = None