1 # 2 # Various array and rectangle tools, but mostly rectangles, hence the 3 # name of this module (not). 4 # 5 6 7 from __future__ import print_function, division, absolute_import 8 from fontTools.misc.py23 import * 9 from numbers import Number 10 import math 11 import operator 12 13 def 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 23 def 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 31 def 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 37 def 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 43 def 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 52 def vectorLength(vector): 53 """Return the length of the given vector.""" 54 x, y = vector 55 return math.sqrt(x**2 + y**2) 56 57 def asInt16(array): 58 """Round and cast to 16 bit integer.""" 59 return [int(math.floor(i+0.5)) for i in array] 60 61 62 def 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 69 def 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 74 def 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 79 def 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 84 def 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 97 def 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 108 def 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 113 def 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 125 class 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 216 def 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 255 def _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 310 if __name__ == "__main__": 311 import sys 312 import doctest 313 sys.exit(doctest.testmod().failed) 314