1"""Routines for calculating bounding boxes, point in rectangle calculations and 2so on. 3""" 4 5from fontTools.misc.roundTools import otRound 6from fontTools.misc.vector import Vector as _Vector 7import math 8import warnings 9 10 11def calcBounds(array): 12 """Calculate the bounding rectangle of a 2D points array. 13 14 Args: 15 array: A sequence of 2D tuples. 16 17 Returns: 18 A four-item tuple representing the bounding rectangle ``(xMin, yMin, xMax, yMax)``. 19 """ 20 if len(array) == 0: 21 return 0, 0, 0, 0 22 xs = [x for x, y in array] 23 ys = [y for x, y in array] 24 return min(xs), min(ys), max(xs), max(ys) 25 26def calcIntBounds(array, round=otRound): 27 """Calculate the integer bounding rectangle of a 2D points array. 28 29 Values are rounded to closest integer towards ``+Infinity`` using the 30 :func:`fontTools.misc.fixedTools.otRound` function by default, unless 31 an optional ``round`` function is passed. 32 33 Args: 34 array: A sequence of 2D tuples. 35 round: A rounding function of type ``f(x: float) -> int``. 36 37 Returns: 38 A four-item tuple of integers representing the bounding rectangle: 39 ``(xMin, yMin, xMax, yMax)``. 40 """ 41 return tuple(round(v) for v in calcBounds(array)) 42 43 44def updateBounds(bounds, p, min=min, max=max): 45 """Add a point to a bounding rectangle. 46 47 Args: 48 bounds: A bounding rectangle expressed as a tuple 49 ``(xMin, yMin, xMax, yMax)``. 50 p: A 2D tuple representing a point. 51 min,max: functions to compute the minimum and maximum. 52 53 Returns: 54 The updated bounding rectangle ``(xMin, yMin, xMax, yMax)``. 55 """ 56 (x, y) = p 57 xMin, yMin, xMax, yMax = bounds 58 return min(xMin, x), min(yMin, y), max(xMax, x), max(yMax, y) 59 60def pointInRect(p, rect): 61 """Test if a point is inside a bounding rectangle. 62 63 Args: 64 p: A 2D tuple representing a point. 65 rect: A bounding rectangle expressed as a tuple 66 ``(xMin, yMin, xMax, yMax)``. 67 68 Returns: 69 ``True`` if the point is inside the rectangle, ``False`` otherwise. 70 """ 71 (x, y) = p 72 xMin, yMin, xMax, yMax = rect 73 return (xMin <= x <= xMax) and (yMin <= y <= yMax) 74 75def pointsInRect(array, rect): 76 """Determine which points are inside a bounding rectangle. 77 78 Args: 79 array: A sequence of 2D tuples. 80 rect: A bounding rectangle expressed as a tuple 81 ``(xMin, yMin, xMax, yMax)``. 82 83 Returns: 84 A list containing the points inside the rectangle. 85 """ 86 if len(array) < 1: 87 return [] 88 xMin, yMin, xMax, yMax = rect 89 return [(xMin <= x <= xMax) and (yMin <= y <= yMax) for x, y in array] 90 91def vectorLength(vector): 92 """Calculate the length of the given vector. 93 94 Args: 95 vector: A 2D tuple. 96 97 Returns: 98 The Euclidean length of the vector. 99 """ 100 x, y = vector 101 return math.sqrt(x**2 + y**2) 102 103def asInt16(array): 104 """Round a list of floats to 16-bit signed integers. 105 106 Args: 107 array: List of float values. 108 109 Returns: 110 A list of rounded integers. 111 """ 112 return [int(math.floor(i+0.5)) for i in array] 113 114 115def normRect(rect): 116 """Normalize a bounding box rectangle. 117 118 This function "turns the rectangle the right way up", so that the following 119 holds:: 120 121 xMin <= xMax and yMin <= yMax 122 123 Args: 124 rect: A bounding rectangle expressed as a tuple 125 ``(xMin, yMin, xMax, yMax)``. 126 127 Returns: 128 A normalized bounding rectangle. 129 """ 130 (xMin, yMin, xMax, yMax) = rect 131 return min(xMin, xMax), min(yMin, yMax), max(xMin, xMax), max(yMin, yMax) 132 133def scaleRect(rect, x, y): 134 """Scale a bounding box rectangle. 135 136 Args: 137 rect: A bounding rectangle expressed as a tuple 138 ``(xMin, yMin, xMax, yMax)``. 139 x: Factor to scale the rectangle along the X axis. 140 Y: Factor to scale the rectangle along the Y axis. 141 142 Returns: 143 A scaled bounding rectangle. 144 """ 145 (xMin, yMin, xMax, yMax) = rect 146 return xMin * x, yMin * y, xMax * x, yMax * y 147 148def offsetRect(rect, dx, dy): 149 """Offset a bounding box rectangle. 150 151 Args: 152 rect: A bounding rectangle expressed as a tuple 153 ``(xMin, yMin, xMax, yMax)``. 154 dx: Amount to offset the rectangle along the X axis. 155 dY: Amount to offset the rectangle along the Y axis. 156 157 Returns: 158 An offset bounding rectangle. 159 """ 160 (xMin, yMin, xMax, yMax) = rect 161 return xMin+dx, yMin+dy, xMax+dx, yMax+dy 162 163def insetRect(rect, dx, dy): 164 """Inset a bounding box rectangle on all sides. 165 166 Args: 167 rect: A bounding rectangle expressed as a tuple 168 ``(xMin, yMin, xMax, yMax)``. 169 dx: Amount to inset the rectangle along the X axis. 170 dY: Amount to inset the rectangle along the Y axis. 171 172 Returns: 173 An inset bounding rectangle. 174 """ 175 (xMin, yMin, xMax, yMax) = rect 176 return xMin+dx, yMin+dy, xMax-dx, yMax-dy 177 178def sectRect(rect1, rect2): 179 """Test for rectangle-rectangle intersection. 180 181 Args: 182 rect1: First bounding rectangle, expressed as tuples 183 ``(xMin, yMin, xMax, yMax)``. 184 rect2: Second bounding rectangle. 185 186 Returns: 187 A boolean and a rectangle. 188 If the input rectangles intersect, returns ``True`` and the intersecting 189 rectangle. Returns ``False`` and ``(0, 0, 0, 0)`` if the input 190 rectangles don't intersect. 191 """ 192 (xMin1, yMin1, xMax1, yMax1) = rect1 193 (xMin2, yMin2, xMax2, yMax2) = rect2 194 xMin, yMin, xMax, yMax = (max(xMin1, xMin2), max(yMin1, yMin2), 195 min(xMax1, xMax2), min(yMax1, yMax2)) 196 if xMin >= xMax or yMin >= yMax: 197 return False, (0, 0, 0, 0) 198 return True, (xMin, yMin, xMax, yMax) 199 200def unionRect(rect1, rect2): 201 """Determine union of bounding rectangles. 202 203 Args: 204 rect1: First bounding rectangle, expressed as tuples 205 ``(xMin, yMin, xMax, yMax)``. 206 rect2: Second bounding rectangle. 207 208 Returns: 209 The smallest rectangle in which both input rectangles are fully 210 enclosed. 211 """ 212 (xMin1, yMin1, xMax1, yMax1) = rect1 213 (xMin2, yMin2, xMax2, yMax2) = rect2 214 xMin, yMin, xMax, yMax = (min(xMin1, xMin2), min(yMin1, yMin2), 215 max(xMax1, xMax2), max(yMax1, yMax2)) 216 return (xMin, yMin, xMax, yMax) 217 218def rectCenter(rect): 219 """Determine rectangle center. 220 221 Args: 222 rect: Bounding rectangle, expressed as tuples 223 ``(xMin, yMin, xMax, yMax)``. 224 225 Returns: 226 A 2D tuple representing the point at the center of the rectangle. 227 """ 228 (xMin, yMin, xMax, yMax) = rect 229 return (xMin+xMax)/2, (yMin+yMax)/2 230 231def rectArea(rect): 232 """Determine rectangle area. 233 234 Args: 235 rect: Bounding rectangle, expressed as tuples 236 ``(xMin, yMin, xMax, yMax)``. 237 238 Returns: 239 The area of the rectangle. 240 """ 241 (xMin, yMin, xMax, yMax) = rect 242 return (yMax - yMin) * (xMax - xMin) 243 244def intRect(rect): 245 """Round a rectangle to integer values. 246 247 Guarantees that the resulting rectangle is NOT smaller than the original. 248 249 Args: 250 rect: Bounding rectangle, expressed as tuples 251 ``(xMin, yMin, xMax, yMax)``. 252 253 Returns: 254 A rounded bounding rectangle. 255 """ 256 (xMin, yMin, xMax, yMax) = rect 257 xMin = int(math.floor(xMin)) 258 yMin = int(math.floor(yMin)) 259 xMax = int(math.ceil(xMax)) 260 yMax = int(math.ceil(yMax)) 261 return (xMin, yMin, xMax, yMax) 262 263 264class Vector(_Vector): 265 266 def __init__(self, *args, **kwargs): 267 warnings.warn( 268 "fontTools.misc.arrayTools.Vector has been deprecated, please use " 269 "fontTools.misc.vector.Vector instead.", 270 DeprecationWarning, 271 ) 272 273 274def pairwise(iterable, reverse=False): 275 """Iterate over current and next items in iterable. 276 277 Args: 278 iterable: An iterable 279 reverse: If true, iterate in reverse order. 280 281 Returns: 282 A iterable yielding two elements per iteration. 283 284 Example: 285 286 >>> tuple(pairwise([])) 287 () 288 >>> tuple(pairwise([], reverse=True)) 289 () 290 >>> tuple(pairwise([0])) 291 ((0, 0),) 292 >>> tuple(pairwise([0], reverse=True)) 293 ((0, 0),) 294 >>> tuple(pairwise([0, 1])) 295 ((0, 1), (1, 0)) 296 >>> tuple(pairwise([0, 1], reverse=True)) 297 ((1, 0), (0, 1)) 298 >>> tuple(pairwise([0, 1, 2])) 299 ((0, 1), (1, 2), (2, 0)) 300 >>> tuple(pairwise([0, 1, 2], reverse=True)) 301 ((2, 1), (1, 0), (0, 2)) 302 >>> tuple(pairwise(['a', 'b', 'c', 'd'])) 303 (('a', 'b'), ('b', 'c'), ('c', 'd'), ('d', 'a')) 304 >>> tuple(pairwise(['a', 'b', 'c', 'd'], reverse=True)) 305 (('d', 'c'), ('c', 'b'), ('b', 'a'), ('a', 'd')) 306 """ 307 if not iterable: 308 return 309 if reverse: 310 it = reversed(iterable) 311 else: 312 it = iter(iterable) 313 first = next(it, None) 314 a = first 315 for b in it: 316 yield (a, b) 317 a = b 318 yield (a, first) 319 320 321def _test(): 322 """ 323 >>> import math 324 >>> calcBounds([]) 325 (0, 0, 0, 0) 326 >>> calcBounds([(0, 40), (0, 100), (50, 50), (80, 10)]) 327 (0, 10, 80, 100) 328 >>> updateBounds((0, 0, 0, 0), (100, 100)) 329 (0, 0, 100, 100) 330 >>> pointInRect((50, 50), (0, 0, 100, 100)) 331 True 332 >>> pointInRect((0, 0), (0, 0, 100, 100)) 333 True 334 >>> pointInRect((100, 100), (0, 0, 100, 100)) 335 True 336 >>> not pointInRect((101, 100), (0, 0, 100, 100)) 337 True 338 >>> list(pointsInRect([(50, 50), (0, 0), (100, 100), (101, 100)], (0, 0, 100, 100))) 339 [True, True, True, False] 340 >>> vectorLength((3, 4)) 341 5.0 342 >>> vectorLength((1, 1)) == math.sqrt(2) 343 True 344 >>> list(asInt16([0, 0.1, 0.5, 0.9])) 345 [0, 0, 1, 1] 346 >>> normRect((0, 10, 100, 200)) 347 (0, 10, 100, 200) 348 >>> normRect((100, 200, 0, 10)) 349 (0, 10, 100, 200) 350 >>> scaleRect((10, 20, 50, 150), 1.5, 2) 351 (15.0, 40, 75.0, 300) 352 >>> offsetRect((10, 20, 30, 40), 5, 6) 353 (15, 26, 35, 46) 354 >>> insetRect((10, 20, 50, 60), 5, 10) 355 (15, 30, 45, 50) 356 >>> insetRect((10, 20, 50, 60), -5, -10) 357 (5, 10, 55, 70) 358 >>> intersects, rect = sectRect((0, 10, 20, 30), (0, 40, 20, 50)) 359 >>> not intersects 360 True 361 >>> intersects, rect = sectRect((0, 10, 20, 30), (5, 20, 35, 50)) 362 >>> intersects 363 1 364 >>> rect 365 (5, 20, 20, 30) 366 >>> unionRect((0, 10, 20, 30), (0, 40, 20, 50)) 367 (0, 10, 20, 50) 368 >>> rectCenter((0, 0, 100, 200)) 369 (50.0, 100.0) 370 >>> rectCenter((0, 0, 100, 199.0)) 371 (50.0, 99.5) 372 >>> intRect((0.9, 2.9, 3.1, 4.1)) 373 (0, 2, 4, 5) 374 """ 375 376if __name__ == "__main__": 377 import sys 378 import doctest 379 sys.exit(doctest.testmod().failed) 380