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 *
9import math
10
11def calcBounds(array):
12    """Return the bounding rectangle of a 2D points array as a tuple:
13    (xMin, yMin, xMax, yMax)
14    """
15    if len(array) == 0:
16        return 0, 0, 0, 0
17    xs = [x for x, y in array]
18    ys = [y for x, y in array]
19    return min(xs), min(ys), max(xs), max(ys)
20
21def calcIntBounds(array):
22    """Return the integer bounding rectangle of a 2D points array as a
23    tuple: (xMin, yMin, xMax, yMax)
24    """
25    xMin, yMin, xMax, yMax = calcBounds(array)
26    xMin = int(math.floor(xMin))
27    xMax = int(math.ceil(xMax))
28    yMin = int(math.floor(yMin))
29    yMax = int(math.ceil(yMax))
30    return xMin, yMin, xMax, yMax
31
32
33def updateBounds(bounds, p, min=min, max=max):
34    """Return the bounding recangle of rectangle bounds and point (x, y)."""
35    (x, y) = p
36    xMin, yMin, xMax, yMax = bounds
37    return min(xMin, x), min(yMin, y), max(xMax, x), max(yMax, y)
38
39def pointInRect(p, rect):
40    """Return True when point (x, y) is inside rect."""
41    (x, y) = p
42    xMin, yMin, xMax, yMax = rect
43    return (xMin <= x <= xMax) and (yMin <= y <= yMax)
44
45def pointsInRect(array, rect):
46    """Find out which points or array are inside rect.
47    Returns an array with a boolean for each point.
48    """
49    if len(array) < 1:
50        return []
51    xMin, yMin, xMax, yMax = rect
52    return [(xMin <= x <= xMax) and (yMin <= y <= yMax) for x, y in array]
53
54def vectorLength(vector):
55    """Return the length of the given vector."""
56    x, y = vector
57    return math.sqrt(x**2 + y**2)
58
59def asInt16(array):
60    """Round and cast to 16 bit integer."""
61    return [int(math.floor(i+0.5)) for i in array]
62
63
64def normRect(rect):
65    """Normalize the rectangle so that the following holds:
66        xMin <= xMax and yMin <= yMax
67    """
68    (xMin, yMin, xMax, yMax) = rect
69    return min(xMin, xMax), min(yMin, yMax), max(xMin, xMax), max(yMin, yMax)
70
71def scaleRect(rect, x, y):
72    """Scale the rectangle by x, y."""
73    (xMin, yMin, xMax, yMax) = rect
74    return xMin * x, yMin * y, xMax * x, yMax * y
75
76def offsetRect(rect, dx, dy):
77    """Offset the rectangle by dx, dy."""
78    (xMin, yMin, xMax, yMax) = rect
79    return xMin+dx, yMin+dy, xMax+dx, yMax+dy
80
81def insetRect(rect, dx, dy):
82    """Inset the rectangle by dx, dy on all sides."""
83    (xMin, yMin, xMax, yMax) = rect
84    return xMin+dx, yMin+dy, xMax-dx, yMax-dy
85
86def sectRect(rect1, rect2):
87    """Return a boolean and a rectangle. If the input rectangles intersect, return
88    True and the intersecting rectangle. Return False and (0, 0, 0, 0) if the input
89    rectangles don't intersect.
90    """
91    (xMin1, yMin1, xMax1, yMax1) = rect1
92    (xMin2, yMin2, xMax2, yMax2) = rect2
93    xMin, yMin, xMax, yMax = (max(xMin1, xMin2), max(yMin1, yMin2),
94                              min(xMax1, xMax2), min(yMax1, yMax2))
95    if xMin >= xMax or yMin >= yMax:
96        return False, (0, 0, 0, 0)
97    return True, (xMin, yMin, xMax, yMax)
98
99def unionRect(rect1, rect2):
100    """Return the smallest rectangle in which both input rectangles are fully
101    enclosed. In other words, return the total bounding rectangle of both input
102    rectangles.
103    """
104    (xMin1, yMin1, xMax1, yMax1) = rect1
105    (xMin2, yMin2, xMax2, yMax2) = rect2
106    xMin, yMin, xMax, yMax = (min(xMin1, xMin2), min(yMin1, yMin2),
107                              max(xMax1, xMax2), max(yMax1, yMax2))
108    return (xMin, yMin, xMax, yMax)
109
110def rectCenter(rect0):
111    """Return the center of the rectangle as an (x, y) coordinate."""
112    (xMin, yMin, xMax, yMax) = rect0
113    return (xMin+xMax)/2, (yMin+yMax)/2
114
115def intRect(rect1):
116    """Return the rectangle, rounded off to integer values, but guaranteeing that
117    the resulting rectangle is NOT smaller than the original.
118    """
119    (xMin, yMin, xMax, yMax) = rect1
120    xMin = int(math.floor(xMin))
121    yMin = int(math.floor(yMin))
122    xMax = int(math.ceil(xMax))
123    yMax = int(math.ceil(yMax))
124    return (xMin, yMin, xMax, yMax)
125
126
127def _test():
128    """
129    >>> import math
130    >>> calcBounds([])
131    (0, 0, 0, 0)
132    >>> calcBounds([(0, 40), (0, 100), (50, 50), (80, 10)])
133    (0, 10, 80, 100)
134    >>> updateBounds((0, 0, 0, 0), (100, 100))
135    (0, 0, 100, 100)
136    >>> pointInRect((50, 50), (0, 0, 100, 100))
137    True
138    >>> pointInRect((0, 0), (0, 0, 100, 100))
139    True
140    >>> pointInRect((100, 100), (0, 0, 100, 100))
141    True
142    >>> not pointInRect((101, 100), (0, 0, 100, 100))
143    True
144    >>> list(pointsInRect([(50, 50), (0, 0), (100, 100), (101, 100)], (0, 0, 100, 100)))
145    [True, True, True, False]
146    >>> vectorLength((3, 4))
147    5.0
148    >>> vectorLength((1, 1)) == math.sqrt(2)
149    True
150    >>> list(asInt16([0, 0.1, 0.5, 0.9]))
151    [0, 0, 1, 1]
152    >>> normRect((0, 10, 100, 200))
153    (0, 10, 100, 200)
154    >>> normRect((100, 200, 0, 10))
155    (0, 10, 100, 200)
156    >>> scaleRect((10, 20, 50, 150), 1.5, 2)
157    (15.0, 40, 75.0, 300)
158    >>> offsetRect((10, 20, 30, 40), 5, 6)
159    (15, 26, 35, 46)
160    >>> insetRect((10, 20, 50, 60), 5, 10)
161    (15, 30, 45, 50)
162    >>> insetRect((10, 20, 50, 60), -5, -10)
163    (5, 10, 55, 70)
164    >>> intersects, rect = sectRect((0, 10, 20, 30), (0, 40, 20, 50))
165    >>> not intersects
166    True
167    >>> intersects, rect = sectRect((0, 10, 20, 30), (5, 20, 35, 50))
168    >>> intersects
169    1
170    >>> rect
171    (5, 20, 20, 30)
172    >>> unionRect((0, 10, 20, 30), (0, 40, 20, 50))
173    (0, 10, 20, 50)
174    >>> rectCenter((0, 0, 100, 200))
175    (50.0, 100.0)
176    >>> rectCenter((0, 0, 100, 199.0))
177    (50.0, 99.5)
178    >>> intRect((0.9, 2.9, 3.1, 4.1))
179    (0, 2, 4, 5)
180    """
181
182if __name__ == "__main__":
183    import doctest
184    doctest.testmod()
185