Home | History | Annotate | Download | only in misc
      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