• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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