| #!/usr/bin/env python3 | |
| from collections import namedtuple | |
| class static_property(object): | |
| """ | |
| Descriptor (non-data) for building an attribute on-demand on first use. | |
| """ | |
| def __init__(self, factory): | |
| """ | |
| <factory> is called such: factory(instance) to build the attribute. | |
| """ | |
| self._attr_name = factory.__name__ | |
| self._factory = factory | |
| self.__doc__ = factory.__doc__ | |
| def __get__(self, instance, owner): | |
| if instance is None: | |
| return self | |
| # Build the attribute. | |
| attr = self._factory(instance) | |
| # Cache the value; hide ourselves. | |
| setattr(instance, self._attr_name, attr) | |
| return attr | |
| # FIXME: define operators | |
| Position = namedtuple("P", ("x", "y")) | |
| P = Position # Even shorter alias | |
| _Size = namedtuple("Size", ("w", "h")) | |
| class Size(_Size): | |
| """ | |
| >>> s = Size(2, 3) | |
| >>> s | |
| Size(w=2, h=3) | |
| >>> p = Position(4, 5) | |
| >>> s + p | |
| P(x=6, y=8) | |
| >>> s + s | |
| Size(w=4, h=6) | |
| >>> s + 1 | |
| Traceback (most recent call last): | |
| ... | |
| TypeError: unsupported operand type(s) for +: 'Size' and 'int' | |
| """ | |
| def __new__(cls, w, h): | |
| assert w >= 0 | |
| assert h >= 0 | |
| return _Size.__new__(cls, w, h) | |
| @static_property | |
| def width(self): | |
| return self.w | |
| @static_property | |
| def height(self): | |
| return self.h | |
| @static_property | |
| def x(self): | |
| return self.w | |
| @static_property | |
| def y(self): | |
| return self.h | |
| def walk(self): | |
| for x in range(0, self.x): | |
| for y in range(0, self.y): | |
| yield Position(x, y) | |
| def __add__(self, o): | |
| if isinstance(o, Position): | |
| return o.__class__(o.x + self.x, o.y + self.y) | |
| elif isinstance(o, Size): | |
| return o.__class__(o.x + self.x, o.y + self.y) | |
| return NotImplemented | |
| def __radd__(self, o): | |
| if isinstance(o, Position): | |
| return o.__class__(o.x + self.x, o.y + self.y) | |
| elif isinstance(o, Size): | |
| return o.__class__(o.x + self.x, o.y + self.y) | |
| return NotImplemented | |
| def __sub__(self, o): | |
| if isinstance(o, Position): | |
| return o.__class__(self.x - o.x, self.y - o.y) | |
| elif isinstance(o, Size): | |
| return o.__class__(self.x - o.x, self.y - o.y) | |
| return NotImplemented | |
| def __rsub__(self, o): | |
| if isinstance(o, Position): | |
| return o.__class__(o.x - self.x, o.y - self.y) | |
| elif isinstance(o, Size): | |
| return o.__class__(o.x - self.x, o.y - self.y) | |
| return NotImplemented | |
| S = Size | |
| class Offset(Size): | |
| pass | |
| O = Offset # noqa: E741 | |
| def single_element(parent, name): | |
| '''Return given single XML child entry in parent''' | |
| elements = list(parent.iterfind(name)) | |
| assert len(elements) == 1, elements | |
| return elements[0] | |
| def node_pos(node): | |
| # node as node_xml | |
| loc = single_element(node, 'loc') | |
| pos_low = Position(int(loc.get('xlow')), int(loc.get('ylow'))) | |
| pos_high = Position(int(loc.get('xhigh')), int(loc.get('yhigh'))) | |
| return pos_low, pos_high |