• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#
2# Various array and rectangle tools, but mostly rectangles, hence the
3# name of this module (not).
4#
5
6
7from __future__ import print_function, division, absolute_import
8from fontTools.misc.py23 import *
9from numbers import Number
10import math
11import operator
12
13def calcBounds(array):
14    """Return the bounding rectangle of a 2D points array as a tuple:
15    (xMin, yMin, xMax, yMax)
16    """
17    if len(array) == 0:
18        return 0, 0, 0, 0
19    xs = [x for x, y in array]
20    ys = [y for x, y in array]
21    return min(xs), min(ys), max(xs), max(ys)
22
23def calcIntBounds(array):
24    """Return the integer bounding rectangle of a 2D points array as a
25    tuple: (xMin, yMin, xMax, yMax)
26    Values are rounded to closest integer.
27    """
28    return tuple(round(v) for v in calcBounds(array))
29
30
31def updateBounds(bounds, p, min=min, max=max):
32    """Return the bounding recangle of rectangle bounds and point (x, y)."""
33    (x, y) = p
34    xMin, yMin, xMax, yMax = bounds
35    return min(xMin, x), min(yMin, y), max(xMax, x), max(yMax, y)
36
37def pointInRect(p, rect):
38    """Return True when point (x, y) is inside rect."""
39    (x, y) = p
40    xMin, yMin, xMax, yMax = rect
41    return (xMin <= x <= xMax) and (yMin <= y <= yMax)
42
43def pointsInRect(array, rect):
44    """Find out which points or array are inside rect.
45    Returns an array with a boolean for each point.
46    """
47    if len(array) < 1:
48        return []
49    xMin, yMin, xMax, yMax = rect
50    return [(xMin <= x <= xMax) and (yMin <= y <= yMax) for x, y in array]
51
52def vectorLength(vector):
53    """Return the length of the given vector."""
54    x, y = vector
55    return math.sqrt(x**2 + y**2)
56
57def asInt16(array):
58    """Round and cast to 16 bit integer."""
59    return [int(math.floor(i+0.5)) for i in array]
60
61
62def normRect(rect):
63    """Normalize the rectangle so that the following holds:
64        xMin <= xMax and yMin <= yMax
65    """
66    (xMin, yMin, xMax, yMax) = rect
67    return min(xMin, xMax), min(yMin, yMax), max(xMin, xMax), max(yMin, yMax)
68
69def scaleRect(rect, x, y):
70    """Scale the rectangle by x, y."""
71    (xMin, yMin, xMax, yMax) = rect
72    return xMin * x, yMin * y, xMax * x, yMax * y
73
74def offsetRect(rect, dx, dy):
75    """Offset the rectangle by dx, dy."""
76    (xMin, yMin, xMax, yMax) = rect
77    return xMin+dx, yMin+dy, xMax+dx, yMax+dy
78
79def insetRect(rect, dx, dy):
80    """Inset the rectangle by dx, dy on all sides."""
81    (xMin, yMin, xMax, yMax) = rect
82    return xMin+dx, yMin+dy, xMax-dx, yMax-dy
83
84def sectRect(rect1, rect2):
85    """Return a boolean and a rectangle. If the input rectangles intersect, return
86    True and the intersecting rectangle. Return False and (0, 0, 0, 0) if the input
87    rectangles don't intersect.
88    """
89    (xMin1, yMin1, xMax1, yMax1) = rect1
90    (xMin2, yMin2, xMax2, yMax2) = rect2
91    xMin, yMin, xMax, yMax = (max(xMin1, xMin2), max(yMin1, yMin2),
92                              min(xMax1, xMax2), min(yMax1, yMax2))
93    if xMin >= xMax or yMin >= yMax:
94        return False, (0, 0, 0, 0)
95    return True, (xMin, yMin, xMax, yMax)
96
97def unionRect(rect1, rect2):
98    """Return the smallest rectangle in which both input rectangles are fully
99    enclosed. In other words, return the total bounding rectangle of both input
100    rectangles.
101    """
102    (xMin1, yMin1, xMax1, yMax1) = rect1
103    (xMin2, yMin2, xMax2, yMax2) = rect2
104    xMin, yMin, xMax, yMax = (min(xMin1, xMin2), min(yMin1, yMin2),
105                              max(xMax1, xMax2), max(yMax1, yMax2))
106    return (xMin, yMin, xMax, yMax)
107
108def rectCenter(rect0):
109    """Return the center of the rectangle as an (x, y) coordinate."""
110    (xMin, yMin, xMax, yMax) = rect0
111    return (xMin+xMax)/2, (yMin+yMax)/2
112
113def intRect(rect1):
114    """Return the rectangle, rounded off to integer values, but guaranteeing that
115    the resulting rectangle is NOT smaller than the original.
116    """
117    (xMin, yMin, xMax, yMax) = rect1
118    xMin = int(math.floor(xMin))
119    yMin = int(math.floor(yMin))
120    xMax = int(math.ceil(xMax))
121    yMax = int(math.ceil(yMax))
122    return (xMin, yMin, xMax, yMax)
123
124
125class Vector(object):
126    """A math-like vector."""
127
128    def __init__(self, values, keep=False):
129        self.values = values if keep else list(values)
130
131    def __getitem__(self, index):
132        return self.values[index]
133
134    def __len__(self):
135        return len(self.values)
136
137    def __repr__(self):
138        return "Vector(%s)" % self.values
139
140    def _vectorOp(self, other, op):
141        if isinstance(other, Vector):
142            assert len(self.values) == len(other.values)
143            a = self.values
144            b = other.values
145            return [op(a[i], b[i]) for i in range(len(self.values))]
146        if isinstance(other, Number):
147            return [op(v, other) for v in self.values]
148        raise NotImplementedError
149
150    def _scalarOp(self, other, op):
151        if isinstance(other, Number):
152            return [op(v, other) for v in self.values]
153        raise NotImplementedError
154
155    def _unaryOp(self, op):
156        return [op(v) for v in self.values]
157
158    def __add__(self, other):
159        return Vector(self._vectorOp(other, operator.add), keep=True)
160    def __iadd__(self, other):
161        self.values = self._vectorOp(other, operator.add)
162        return self
163    __radd__ = __add__
164
165    def __sub__(self, other):
166        return Vector(self._vectorOp(other, operator.sub), keep=True)
167    def __isub__(self, other):
168        self.values = self._vectorOp(other, operator.sub)
169        return self
170    def __rsub__(self, other):
171        return other + (-self)
172
173    def __mul__(self, other):
174        return Vector(self._scalarOp(other, operator.mul), keep=True)
175    def __imul__(self, other):
176        self.values = self._scalarOp(other, operator.mul)
177        return self
178    __rmul__ = __mul__
179
180    def __truediv__(self, other):
181        return Vector(self._scalarOp(other, operator.div), keep=True)
182    def __itruediv__(self, other):
183        self.values = self._scalarOp(other, operator.div)
184        return self
185
186    def __pos__(self):
187        return Vector(self._unaryOp(operator.pos), keep=True)
188    def __neg__(self):
189        return Vector(self._unaryOp(operator.neg), keep=True)
190    def __round__(self):
191        return Vector(self._unaryOp(round), keep=True)
192    def toInt(self):
193        return self.__round__()
194
195    def __eq__(self, other):
196        if type(other) == Vector:
197            return self.values == other.values
198        else:
199            return self.values == other
200    def __ne__(self, other):
201        return not self.__eq__(other)
202
203    def __bool__(self):
204        return any(self.values)
205    __nonzero__ = __bool__
206
207    def __abs__(self):
208        return math.sqrt(sum([x*x for x in self.values]))
209    def dot(self, other):
210        a = self.values
211        b = other.values if type(other) == Vector else b
212        assert len(a) == len(b)
213        return sum([a[i] * b[i] for i in range(len(a))])
214
215
216def pairwise(iterable, reverse=False):
217    """Iterate over current and next items in iterable, optionally in
218    reverse order.
219
220    >>> tuple(pairwise([]))
221    ()
222    >>> tuple(pairwise([], reverse=True))
223    ()
224    >>> tuple(pairwise([0]))
225    ((0, 0),)
226    >>> tuple(pairwise([0], reverse=True))
227    ((0, 0),)
228    >>> tuple(pairwise([0, 1]))
229    ((0, 1), (1, 0))
230    >>> tuple(pairwise([0, 1], reverse=True))
231    ((1, 0), (0, 1))
232    >>> tuple(pairwise([0, 1, 2]))
233    ((0, 1), (1, 2), (2, 0))
234    >>> tuple(pairwise([0, 1, 2], reverse=True))
235    ((2, 1), (1, 0), (0, 2))
236    >>> tuple(pairwise(['a', 'b', 'c', 'd']))
237    (('a', 'b'), ('b', 'c'), ('c', 'd'), ('d', 'a'))
238    >>> tuple(pairwise(['a', 'b', 'c', 'd'], reverse=True))
239    (('d', 'c'), ('c', 'b'), ('b', 'a'), ('a', 'd'))
240    """
241    if not iterable:
242        return
243    if reverse:
244        it = reversed(iterable)
245    else:
246        it = iter(iterable)
247    first = next(it, None)
248    a = first
249    for b in it:
250        yield (a, b)
251        a = b
252    yield (a, first)
253
254
255def _test():
256    """
257    >>> import math
258    >>> calcBounds([])
259    (0, 0, 0, 0)
260    >>> calcBounds([(0, 40), (0, 100), (50, 50), (80, 10)])
261    (0, 10, 80, 100)
262    >>> updateBounds((0, 0, 0, 0), (100, 100))
263    (0, 0, 100, 100)
264    >>> pointInRect((50, 50), (0, 0, 100, 100))
265    True
266    >>> pointInRect((0, 0), (0, 0, 100, 100))
267    True
268    >>> pointInRect((100, 100), (0, 0, 100, 100))
269    True
270    >>> not pointInRect((101, 100), (0, 0, 100, 100))
271    True
272    >>> list(pointsInRect([(50, 50), (0, 0), (100, 100), (101, 100)], (0, 0, 100, 100)))
273    [True, True, True, False]
274    >>> vectorLength((3, 4))
275    5.0
276    >>> vectorLength((1, 1)) == math.sqrt(2)
277    True
278    >>> list(asInt16([0, 0.1, 0.5, 0.9]))
279    [0, 0, 1, 1]
280    >>> normRect((0, 10, 100, 200))
281    (0, 10, 100, 200)
282    >>> normRect((100, 200, 0, 10))
283    (0, 10, 100, 200)
284    >>> scaleRect((10, 20, 50, 150), 1.5, 2)
285    (15.0, 40, 75.0, 300)
286    >>> offsetRect((10, 20, 30, 40), 5, 6)
287    (15, 26, 35, 46)
288    >>> insetRect((10, 20, 50, 60), 5, 10)
289    (15, 30, 45, 50)
290    >>> insetRect((10, 20, 50, 60), -5, -10)
291    (5, 10, 55, 70)
292    >>> intersects, rect = sectRect((0, 10, 20, 30), (0, 40, 20, 50))
293    >>> not intersects
294    True
295    >>> intersects, rect = sectRect((0, 10, 20, 30), (5, 20, 35, 50))
296    >>> intersects
297    1
298    >>> rect
299    (5, 20, 20, 30)
300    >>> unionRect((0, 10, 20, 30), (0, 40, 20, 50))
301    (0, 10, 20, 50)
302    >>> rectCenter((0, 0, 100, 200))
303    (50.0, 100.0)
304    >>> rectCenter((0, 0, 100, 199.0))
305    (50.0, 99.5)
306    >>> intRect((0.9, 2.9, 3.1, 4.1))
307    (0, 2, 4, 5)
308    """
309
310if __name__ == "__main__":
311    import sys
312    import doctest
313    sys.exit(doctest.testmod().failed)
314