Mercurial > hgrepos > Python2 > PyMuPDF
diff src_classic/helper-geo-py.i @ 1:1d09e1dec1d9 upstream
ADD: PyMuPDF v1.26.4: the original sdist.
It does not yet contain MuPDF. This normally will be downloaded when
building PyMuPDF.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:37:51 +0200 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src_classic/helper-geo-py.i Mon Sep 15 11:37:51 2025 +0200 @@ -0,0 +1,1155 @@ +%pythoncode %{ + +# ------------------------------------------------------------------------ +# Copyright 2020-2022, Harald Lieder, mailto:harald.lieder@outlook.com +# License: GNU AFFERO GPL 3.0, https://www.gnu.org/licenses/agpl-3.0.html +# +# Part of "PyMuPDF", a Python binding for "MuPDF" (http://mupdf.com), a +# lightweight PDF, XPS, and E-book viewer, renderer and toolkit which is +# maintained and developed by Artifex Software, Inc. https://artifex.com. +# ------------------------------------------------------------------------ + +# largest 32bit integers surviving C float conversion roundtrips +# used by MuPDF to define infinite rectangles +FZ_MIN_INF_RECT = -0x80000000 +FZ_MAX_INF_RECT = 0x7fffff80 + + +class Matrix(object): + """Matrix() - all zeros + Matrix(a, b, c, d, e, f) + Matrix(zoom-x, zoom-y) - zoom + Matrix(shear-x, shear-y, 1) - shear + Matrix(degree) - rotate + Matrix(Matrix) - new copy + Matrix(sequence) - from 'sequence'""" + def __init__(self, *args): + if not args: + self.a = self.b = self.c = self.d = self.e = self.f = 0.0 + return None + if len(args) > 6: + raise ValueError("Matrix: bad seq len") + if len(args) == 6: # 6 numbers + self.a, self.b, self.c, self.d, self.e, self.f = map(float, args) + return None + if len(args) == 1: # either an angle or a sequ + if hasattr(args[0], "__float__"): + theta = math.radians(args[0]) + c = round(math.cos(theta), 12) + s = round(math.sin(theta), 12) + self.a = self.d = c + self.b = s + self.c = -s + self.e = self.f = 0.0 + return None + else: + self.a, self.b, self.c, self.d, self.e, self.f = map(float, args[0]) + return None + if len(args) == 2 or len(args) == 3 and args[2] == 0: + self.a, self.b, self.c, self.d, self.e, self.f = float(args[0]), \ + 0.0, 0.0, float(args[1]), 0.0, 0.0 + return None + if len(args) == 3 and args[2] == 1: + self.a, self.b, self.c, self.d, self.e, self.f = 1.0, \ + float(args[1]), float(args[0]), 1.0, 0.0, 0.0 + return None + raise ValueError("Matrix: bad args") + + def invert(self, src=None): + """Calculate the inverted matrix. Return 0 if successful and replace + current one. Else return 1 and do nothing. + """ + if src is None: + dst = util_invert_matrix(self) + else: + dst = util_invert_matrix(src) + if dst[0] == 1: + return 1 + self.a, self.b, self.c, self.d, self.e, self.f = dst[1] + return 0 + + def pretranslate(self, tx, ty): + """Calculate pre translation and replace current matrix.""" + tx = float(tx) + ty = float(ty) + self.e += tx * self.a + ty * self.c + self.f += tx * self.b + ty * self.d + return self + + def prescale(self, sx, sy): + """Calculate pre scaling and replace current matrix.""" + sx = float(sx) + sy = float(sy) + self.a *= sx + self.b *= sx + self.c *= sy + self.d *= sy + return self + + def preshear(self, h, v): + """Calculate pre shearing and replace current matrix.""" + h = float(h) + v = float(v) + a, b = self.a, self.b + self.a += v * self.c + self.b += v * self.d + self.c += h * a + self.d += h * b + return self + + def prerotate(self, theta): + """Calculate pre rotation and replace current matrix.""" + theta = float(theta) + while theta < 0: theta += 360 + while theta >= 360: theta -= 360 + if abs(0 - theta) < EPSILON: + pass + + elif abs(90.0 - theta) < EPSILON: + a = self.a + b = self.b + self.a = self.c + self.b = self.d + self.c = -a + self.d = -b + + elif abs(180.0 - theta) < EPSILON: + self.a = -self.a + self.b = -self.b + self.c = -self.c + self.d = -self.d + + elif abs(270.0 - theta) < EPSILON: + a = self.a + b = self.b + self.a = -self.c + self.b = -self.d + self.c = a + self.d = b + + else: + rad = math.radians(theta) + s = math.sin(rad) + c = math.cos(rad) + a = self.a + b = self.b + self.a = c * a + s * self.c + self.b = c * b + s * self.d + self.c =-s * a + c * self.c + self.d =-s * b + c * self.d + + return self + + def concat(self, one, two): + """Multiply two matrices and replace current one.""" + if not len(one) == len(two) == 6: + raise ValueError("Matrix: bad seq len") + self.a, self.b, self.c, self.d, self.e, self.f = util_concat_matrix(one, two) + return self + + def __getitem__(self, i): + return (self.a, self.b, self.c, self.d, self.e, self.f)[i] + + def __setitem__(self, i, v): + v = float(v) + if i == 0: self.a = v + elif i == 1: self.b = v + elif i == 2: self.c = v + elif i == 3: self.d = v + elif i == 4: self.e = v + elif i == 5: self.f = v + else: + raise IndexError("index out of range") + return + + def __len__(self): + return 6 + + def __repr__(self): + return "Matrix" + str(tuple(self)) + + def __invert__(self): + """Calculate inverted matrix.""" + m1 = Matrix() + m1.invert(self) + return m1 + __inv__ = __invert__ + + def __mul__(self, m): + if hasattr(m, "__float__"): + return Matrix(self.a * m, self.b * m, self.c * m, + self.d * m, self.e * m, self.f * m) + m1 = Matrix(1,1) + return m1.concat(self, m) + + def __truediv__(self, m): + if hasattr(m, "__float__"): + return Matrix(self.a * 1./m, self.b * 1./m, self.c * 1./m, + self.d * 1./m, self.e * 1./m, self.f * 1./m) + m1 = util_invert_matrix(m)[1] + if not m1: + raise ZeroDivisionError("matrix not invertible") + m2 = Matrix(1,1) + return m2.concat(self, m1) + __div__ = __truediv__ + + def __add__(self, m): + if hasattr(m, "__float__"): + return Matrix(self.a + m, self.b + m, self.c + m, + self.d + m, self.e + m, self.f + m) + if len(m) != 6: + raise ValueError("Matrix: bad seq len") + return Matrix(self.a + m[0], self.b + m[1], self.c + m[2], + self.d + m[3], self.e + m[4], self.f + m[5]) + + def __sub__(self, m): + if hasattr(m, "__float__"): + return Matrix(self.a - m, self.b - m, self.c - m, + self.d - m, self.e - m, self.f - m) + if len(m) != 6: + raise ValueError("Matrix: bad seq len") + return Matrix(self.a - m[0], self.b - m[1], self.c - m[2], + self.d - m[3], self.e - m[4], self.f - m[5]) + + def __pos__(self): + return Matrix(self) + + def __neg__(self): + return Matrix(-self.a, -self.b, -self.c, -self.d, -self.e, -self.f) + + def __bool__(self): + return not (max(self) == min(self) == 0) + + def __nonzero__(self): + return not (max(self) == min(self) == 0) + + def __eq__(self, mat): + if not hasattr(mat, "__len__"): + return False + return len(mat) == 6 and bool(self - mat) is False + + def __abs__(self): + return math.sqrt(sum([c*c for c in self])) + + norm = __abs__ + + @property + def is_rectilinear(self): + """True if rectangles are mapped to rectangles.""" + return (abs(self.b) < EPSILON and abs(self.c) < EPSILON) or \ + (abs(self.a) < EPSILON and abs(self.d) < EPSILON); + + +class IdentityMatrix(Matrix): + """Identity matrix [1, 0, 0, 1, 0, 0]""" + def __init__(self): + Matrix.__init__(self, 1.0, 1.0) + def __setattr__(self, name, value): + if name in "ad": + self.__dict__[name] = 1.0 + elif name in "bcef": + self.__dict__[name] = 0.0 + else: + self.__dict__[name] = value + + def checkargs(*args): + raise NotImplementedError("Identity is readonly") + + prerotate = checkargs + preshear = checkargs + prescale = checkargs + pretranslate = checkargs + concat = checkargs + invert = checkargs + + def __repr__(self): + return "IdentityMatrix(1.0, 0.0, 0.0, 1.0, 0.0, 0.0)" + + def __hash__(self): + return hash((1,0,0,1,0,0)) + + +Identity = IdentityMatrix() + +class Point(object): + """Point() - all zeros\nPoint(x, y)\nPoint(Point) - new copy\nPoint(sequence) - from 'sequence'""" + def __init__(self, *args): + if not args: + self.x = 0.0 + self.y = 0.0 + return None + + if len(args) > 2: + raise ValueError("Point: bad seq len") + if len(args) == 2: + self.x = float(args[0]) + self.y = float(args[1]) + return None + if len(args) == 1: + l = args[0] + if hasattr(l, "__getitem__") is False: + raise ValueError("Point: bad args") + if len(l) != 2: + raise ValueError("Point: bad seq len") + self.x = float(l[0]) + self.y = float(l[1]) + return None + raise ValueError("Point: bad args") + + def transform(self, m): + """Replace point by its transformation with matrix-like m.""" + if len(m) != 6: + raise ValueError("Matrix: bad seq len") + self.x, self.y = util_transform_point(self, m) + return self + + @property + def unit(self): + """Unit vector of the point.""" + s = self.x * self.x + self.y * self.y + if s < EPSILON: + return Point(0,0) + s = math.sqrt(s) + return Point(self.x / s, self.y / s) + + @property + def abs_unit(self): + """Unit vector with positive coordinates.""" + s = self.x * self.x + self.y * self.y + if s < EPSILON: + return Point(0,0) + s = math.sqrt(s) + return Point(abs(self.x) / s, abs(self.y) / s) + + def distance_to(self, *args): + """Return distance to rectangle or another point.""" + if not len(args) > 0: + raise ValueError("at least one parameter must be given") + + x = args[0] + if len(x) == 2: + x = Point(x) + elif len(x) == 4: + x = Rect(x) + else: + raise ValueError("arg1 must be point-like or rect-like") + + if len(args) > 1: + unit = args[1] + else: + unit = "px" + u = {"px": (1.,1.), "in": (1.,72.), "cm": (2.54, 72.), + "mm": (25.4, 72.)} + f = u[unit][0] / u[unit][1] + + if type(x) is Point: + return abs(self - x) * f + + # from here on, x is a rectangle + # as a safeguard, make a finite copy of it + r = Rect(x.top_left, x.top_left) + r = r | x.bottom_right + if self in r: + return 0.0 + if self.x > r.x1: + if self.y >= r.y1: + return self.distance_to(r.bottom_right, unit) + elif self.y <= r.y0: + return self.distance_to(r.top_right, unit) + else: + return (self.x - r.x1) * f + elif r.x0 <= self.x <= r.x1: + if self.y >= r.y1: + return (self.y - r.y1) * f + else: + return (r.y0 - self.y) * f + else: + if self.y >= r.y1: + return self.distance_to(r.bottom_left, unit) + elif self.y <= r.y0: + return self.distance_to(r.top_left, unit) + else: + return (r.x0 - self.x) * f + + def __getitem__(self, i): + return (self.x, self.y)[i] + + def __len__(self): + return 2 + + def __setitem__(self, i, v): + v = float(v) + if i == 0: self.x = v + elif i == 1: self.y = v + else: + raise IndexError("index out of range") + return None + + def __repr__(self): + return "Point" + str(tuple(self)) + + def __pos__(self): + return Point(self) + + def __neg__(self): + return Point(-self.x, -self.y) + + def __bool__(self): + return not (max(self) == min(self) == 0) + + def __nonzero__(self): + return not (max(self) == min(self) == 0) + + def __eq__(self, p): + if not hasattr(p, "__len__"): + return False + return len(p) == 2 and bool(self - p) is False + + def __abs__(self): + return math.sqrt(self.x * self.x + self.y * self.y) + + norm = __abs__ + + def __add__(self, p): + if hasattr(p, "__float__"): + return Point(self.x + p, self.y + p) + if len(p) != 2: + raise ValueError("Point: bad seq len") + return Point(self.x + p[0], self.y + p[1]) + + def __sub__(self, p): + if hasattr(p, "__float__"): + return Point(self.x - p, self.y - p) + if len(p) != 2: + raise ValueError("Point: bad seq len") + return Point(self.x - p[0], self.y - p[1]) + + def __mul__(self, m): + if hasattr(m, "__float__"): + return Point(self.x * m, self.y * m) + p = Point(self) + return p.transform(m) + + def __truediv__(self, m): + if hasattr(m, "__float__"): + return Point(self.x * 1./m, self.y * 1./m) + m1 = util_invert_matrix(m)[1] + if not m1: + raise ZeroDivisionError("matrix not invertible") + p = Point(self) + return p.transform(m1) + + __div__ = __truediv__ + + def __hash__(self): + return hash(tuple(self)) + +class Rect(object): + """Rect() - all zeros + Rect(x0, y0, x1, y1) - 4 coordinates + Rect(top-left, x1, y1) - point and 2 coordinates + Rect(x0, y0, bottom-right) - 2 coordinates and point + Rect(top-left, bottom-right) - 2 points + Rect(sequ) - new from sequence or rect-like + """ + def __init__(self, *args): + self.x0, self.y0, self.x1, self.y1 = util_make_rect(args) + return None + + def normalize(self): + """Replace rectangle with its valid version.""" + if self.x1 < self.x0: + self.x0, self.x1 = self.x1, self.x0 + if self.y1 < self.y0: + self.y0, self.y1 = self.y1, self.y0 + return self + + @property + def is_empty(self): + """True if rectangle area is empty.""" + return self.x0 >= self.x1 or self.y0 >= self.y1 + + @property + def is_valid(self): + """True if rectangle is valid.""" + return self.x0 <= self.x1 and self.y0 <= self.y1 + + @property + def is_infinite(self): + """True if this is the infinite rectangle.""" + return self.x0 == self.y0 == FZ_MIN_INF_RECT and self.x1 == self.y1 == FZ_MAX_INF_RECT + + @property + def top_left(self): + """Top-left corner.""" + return Point(self.x0, self.y0) + + @property + def top_right(self): + """Top-right corner.""" + return Point(self.x1, self.y0) + + @property + def bottom_left(self): + """Bottom-left corner.""" + return Point(self.x0, self.y1) + + @property + def bottom_right(self): + """Bottom-right corner.""" + return Point(self.x1, self.y1) + + tl = top_left + tr = top_right + bl = bottom_left + br = bottom_right + + @property + def quad(self): + """Return Quad version of rectangle.""" + return Quad(self.tl, self.tr, self.bl, self.br) + + def torect(self, r): + """Return matrix that converts to target rect.""" + + r = Rect(r) + if self.is_infinite or self.is_empty or r.is_infinite or r.is_empty: + raise ValueError("rectangles must be finite and not empty") + return ( + Matrix(1, 0, 0, 1, -self.x0, -self.y0) + * Matrix(r.width / self.width, r.height / self.height) + * Matrix(1, 0, 0, 1, r.x0, r.y0) + ) + + def morph(self, p, m): + """Morph with matrix-like m and point-like p. + + Returns a new quad.""" + if self.is_infinite: + return INFINITE_QUAD() + return self.quad.morph(p, m) + + def round(self): + """Return the IRect.""" + return IRect(util_round_rect(self)) + + irect = property(round) + + width = property(lambda self: self.x1 - self.x0 if self.x1 > self.x0 else 0) + height = property(lambda self: self.y1 - self.y0 if self.y1 > self.y0 else 0) + + def include_point(self, p): + """Extend to include point-like p.""" + if len(p) != 2: + raise ValueError("Point: bad seq len") + self.x0, self.y0, self.x1, self.y1 = util_include_point_in_rect(self, p) + return self + + def include_rect(self, r): + """Extend to include rect-like r.""" + if len(r) != 4: + raise ValueError("Rect: bad seq len") + r = Rect(r) + if r.is_infinite or self.is_infinite: + self.x0, self.y0, self.x1, self.y1 = FZ_MIN_INF_RECT, FZ_MIN_INF_RECT, FZ_MAX_INF_RECT, FZ_MAX_INF_RECT + elif r.is_empty: + return self + elif self.is_empty: + self.x0, self.y0, self.x1, self.y1 = r.x0, r.y0, r.x1, r.y1 + else: + self.x0, self.y0, self.x1, self.y1 = util_union_rect(self, r) + return self + + def intersect(self, r): + """Restrict to common rect with rect-like r.""" + if not len(r) == 4: + raise ValueError("Rect: bad seq len") + r = Rect(r) + if r.is_infinite: + return self + elif self.is_infinite: + self.x0, self.y0, self.x1, self.y1 = r.x0, r.y0, r.x1, r.y1 + elif r.is_empty: + self.x0, self.y0, self.x1, self.y1 = r.x0, r.y0, r.x1, r.y1 + elif self.is_empty: + return self + else: + self.x0, self.y0, self.x1, self.y1 = util_intersect_rect(self, r) + return self + + def contains(self, x): + """Check if containing point-like or rect-like x.""" + return self.__contains__(x) + + def transform(self, m): + """Replace with the transformation by matrix-like m.""" + if not len(m) == 6: + raise ValueError("Matrix: bad seq len") + self.x0, self.y0, self.x1, self.y1 = util_transform_rect(self, m) + return self + + def __getitem__(self, i): + return (self.x0, self.y0, self.x1, self.y1)[i] + + def __len__(self): + return 4 + + def __setitem__(self, i, v): + v = float(v) + if i == 0: self.x0 = v + elif i == 1: self.y0 = v + elif i == 2: self.x1 = v + elif i == 3: self.y1 = v + else: + raise IndexError("index out of range") + return None + + def __repr__(self): + return "Rect" + str(tuple(self)) + + def __pos__(self): + return Rect(self) + + def __neg__(self): + return Rect(-self.x0, -self.y0, -self.x1, -self.y1) + + def __bool__(self): + return not self.x0 == self.y0 == self.x1 == self.y1 == 0 + + def __nonzero__(self): + return not self.x0 == self.y0 == self.x1 == self.y1 == 0 + + def __eq__(self, r): + if not hasattr(r, "__len__"): + return False + return len(r) == 4 and self.x0 == r[0] and self.y0 == r[1] and self.x1 == r[2] and self.y1 == r[3] + + def __abs__(self): + if self.is_infinite or not self.is_valid: + return 0.0 + return self.width * self.height + + def norm(self): + return math.sqrt(sum([c*c for c in self])) + + def __add__(self, p): + if hasattr(p, "__float__"): + return Rect(self.x0 + p, self.y0 + p, self.x1 + p, self.y1 + p) + if len(p) != 4: + raise ValueError("Rect: bad seq len") + return Rect(self.x0 + p[0], self.y0 + p[1], self.x1 + p[2], self.y1 + p[3]) + + + def __sub__(self, p): + if hasattr(p, "__float__"): + return Rect(self.x0 - p, self.y0 - p, self.x1 - p, self.y1 - p) + if len(p) != 4: + raise ValueError("Rect: bad seq len") + return Rect(self.x0 - p[0], self.y0 - p[1], self.x1 - p[2], self.y1 - p[3]) + + + def __mul__(self, m): + if hasattr(m, "__float__"): + return Rect(self.x0 * m, self.y0 * m, self.x1 * m, self.y1 * m) + r = Rect(self) + r = r.transform(m) + return r + + def __truediv__(self, m): + if hasattr(m, "__float__"): + return Rect(self.x0 * 1./m, self.y0 * 1./m, self.x1 * 1./m, self.y1 * 1./m) + im = util_invert_matrix(m)[1] + if not im: + raise ZeroDivisionError("Matrix not invertible") + r = Rect(self) + r = r.transform(im) + return r + + __div__ = __truediv__ + + def __contains__(self, x): + if hasattr(x, "__float__"): + return x in tuple(self) + l = len(x) + if l == 2: + return util_is_point_in_rect(x, self) + if l == 4: + r = INFINITE_RECT() + try: + r = Rect(x) + except: + r = Quad(x).rect + return (self.x0 <= r.x0 <= r.x1 <= self.x1 and + self.y0 <= r.y0 <= r.y1 <= self.y1) + return False + + + def __or__(self, x): + if not hasattr(x, "__len__"): + raise ValueError("bad type op 2") + + r = Rect(self) + if len(x) == 2: + return r.include_point(x) + if len(x) == 4: + return r.include_rect(x) + raise ValueError("bad type op 2") + + def __and__(self, x): + if not hasattr(x, "__len__") or len(x) != 4: + raise ValueError("bad type op 2") + r = Rect(self) + return r.intersect(x) + + def intersects(self, x): + """Check if intersection with rectangle x is not empty.""" + r1 = Rect(x) + if self.is_empty or self.is_infinite or r1.is_empty or r1.is_infinite: + return False + r = Rect(self) + if r.intersect(r1).is_empty: + return False + return True + + def __hash__(self): + return hash(tuple(self)) + +class IRect(object): + """IRect() - all zeros + IRect(x0, y0, x1, y1) - 4 coordinates + IRect(top-left, x1, y1) - point and 2 coordinates + IRect(x0, y0, bottom-right) - 2 coordinates and point + IRect(top-left, bottom-right) - 2 points + IRect(sequ) - new from sequence or rect-like + """ + def __init__(self, *args): + self.x0, self.y0, self.x1, self.y1 = util_make_irect(args) + return None + + def normalize(self): + """Replace rectangle with its valid version.""" + if self.x1 < self.x0: + self.x0, self.x1 = self.x1, self.x0 + if self.y1 < self.y0: + self.y0, self.y1 = self.y1, self.y0 + return self + + @property + def is_empty(self): + """True if rectangle area is empty.""" + return self.x0 >= self.x1 or self.y0 >= self.y1 + + @property + def is_valid(self): + """True if rectangle is valid.""" + return self.x0 <= self.x1 and self.y0 <= self.y1 + + @property + def is_infinite(self): + """True if rectangle is infinite.""" + return self.x0 == self.y0 == FZ_MIN_INF_RECT and self.x1 == self.y1 == FZ_MAX_INF_RECT + + @property + def top_left(self): + """Top-left corner.""" + return Point(self.x0, self.y0) + + @property + def top_right(self): + """Top-right corner.""" + return Point(self.x1, self.y0) + + @property + def bottom_left(self): + """Bottom-left corner.""" + return Point(self.x0, self.y1) + + @property + def bottom_right(self): + """Bottom-right corner.""" + return Point(self.x1, self.y1) + + tl = top_left + tr = top_right + bl = bottom_left + br = bottom_right + + @property + def quad(self): + """Return Quad version of rectangle.""" + return Quad(self.tl, self.tr, self.bl, self.br) + + + def torect(self, r): + """Return matrix that converts to target rect.""" + + r = Rect(r) + if self.is_infinite or self.is_empty or r.is_infinite or r.is_empty: + raise ValueError("rectangles must be finite and not empty") + return ( + Matrix(1, 0, 0, 1, -self.x0, -self.y0) + * Matrix(r.width / self.width, r.height / self.height) + * Matrix(1, 0, 0, 1, r.x0, r.y0) + ) + + def morph(self, p, m): + """Morph with matrix-like m and point-like p. + + Returns a new quad.""" + if self.is_infinite: + return INFINITE_QUAD() + return self.quad.morph(p, m) + + @property + def rect(self): + return Rect(self) + + width = property(lambda self: self.x1 - self.x0 if self.x1 > self.x0 else 0) + height = property(lambda self: self.y1 - self.y0 if self.y1 > self.y0 else 0) + + def include_point(self, p): + """Extend rectangle to include point p.""" + rect = self.rect.include_point(p) + return rect.irect + + def include_rect(self, r): + """Extend rectangle to include rectangle r.""" + rect = self.rect.include_rect(r) + return rect.irect + + def intersect(self, r): + """Restrict rectangle to intersection with rectangle r.""" + rect = self.rect.intersect(r) + return rect.irect + + def __getitem__(self, i): + return (self.x0, self.y0, self.x1, self.y1)[i] + + def __len__(self): + return 4 + + def __setitem__(self, i, v): + v = int(v) + if i == 0: self.x0 = v + elif i == 1: self.y0 = v + elif i == 2: self.x1 = v + elif i == 3: self.y1 = v + else: + raise IndexError("index out of range") + return None + + def __repr__(self): + return "IRect" + str(tuple(self)) + + def __pos__(self): + return IRect(self) + + def __neg__(self): + return IRect(-self.x0, -self.y0, -self.x1, -self.y1) + + def __bool__(self): + return not self.x0 == self.y0 == self.x1 == self.y1 == 0 + + def __nonzero__(self): + return not self.x0 == self.y0 == self.x1 == self.y1 == 0 + + def __eq__(self, r): + if not hasattr(r, "__len__"): + return False + return len(r) == 4 and self.x0 == r[0] and self.y0 == r[1] and self.x1 == r[2] and self.y1 == r[3] + + def __abs__(self): + if self.is_infinite or not self.is_valid: + return 0 + return self.width * self.height + + def norm(self): + return math.sqrt(sum([c*c for c in self])) + + def __add__(self, p): + return Rect.__add__(self, p).round() + + def __sub__(self, p): + return Rect.__sub__(self, p).round() + + def transform(self, m): + return Rect.transform(self, m).round() + + def __mul__(self, m): + return Rect.__mul__(self, m).round() + + def __truediv__(self, m): + return Rect.__truediv__(self, m).round() + + __div__ = __truediv__ + + + def __contains__(self, x): + return Rect.__contains__(self, x) + + + def __or__(self, x): + return Rect.__or__(self, x).round() + + def __and__(self, x): + return Rect.__and__(self, x).round() + + def intersects(self, x): + return Rect.intersects(self, x) + + def __hash__(self): + return hash(tuple(self)) + + +class Quad(object): + """Quad() - all zero points\nQuad(ul, ur, ll, lr)\nQuad(quad) - new copy\nQuad(sequence) - from 'sequence'""" + def __init__(self, *args): + if not args: + self.ul = self.ur = self.ll = self.lr = Point() + return None + + if len(args) > 4: + raise ValueError("Quad: bad seq len") + if len(args) == 4: + self.ul, self.ur, self.ll, self.lr = map(Point, args) + return None + if len(args) == 1: + l = args[0] + if hasattr(l, "__getitem__") is False: + raise ValueError("Quad: bad args") + if len(l) != 4: + raise ValueError("Quad: bad seq len") + self.ul, self.ur, self.ll, self.lr = map(Point, l) + return None + raise ValueError("Quad: bad args") + + @property + def is_rectangular(self)->bool: + """Check if quad is rectangular. + + Notes: + Some rotation matrix can thus transform it into a rectangle. + This is equivalent to three corners enclose 90 degrees. + Returns: + True or False. + """ + + sine = util_sine_between(self.ul, self.ur, self.lr) + if abs(sine - 1) > EPSILON: # the sine of the angle + return False + + sine = util_sine_between(self.ur, self.lr, self.ll) + if abs(sine - 1) > EPSILON: + return False + + sine = util_sine_between(self.lr, self.ll, self.ul) + if abs(sine - 1) > EPSILON: + return False + + return True + + + @property + def is_convex(self)->bool: + """Check if quad is convex and not degenerate. + + Notes: + Check that for the two diagonals, the other two corners are not + on the same side of the diagonal. + Returns: + True or False. + """ + m = planish_line(self.ul, self.lr) # puts this diagonal on x-axis + p1 = self.ll * m # transform the + p2 = self.ur * m # other two points + if p1.y * p2.y > 0: + return False + m = planish_line(self.ll, self.ur) # puts other diagonal on x-axis + p1 = self.lr * m # tranform the + p2 = self.ul * m # remaining points + if p1.y * p2.y > 0: + return False + return True + + + width = property(lambda self: max(abs(self.ul - self.ur), abs(self.ll - self.lr))) + height = property(lambda self: max(abs(self.ul - self.ll), abs(self.ur - self.lr))) + + @property + def is_empty(self): + """Check whether all quad corners are on the same line. + + This is the case if width or height is zero. + """ + return self.width < EPSILON or self.height < EPSILON + + @property + def is_infinite(self): + """Check whether this is the infinite quad.""" + return self.rect.is_infinite + + @property + def rect(self): + r = Rect() + r.x0 = min(self.ul.x, self.ur.x, self.lr.x, self.ll.x) + r.y0 = min(self.ul.y, self.ur.y, self.lr.y, self.ll.y) + r.x1 = max(self.ul.x, self.ur.x, self.lr.x, self.ll.x) + r.y1 = max(self.ul.y, self.ur.y, self.lr.y, self.ll.y) + return r + + + def __contains__(self, x): + try: + l = x.__len__() + except: + return False + if l == 2: + return util_point_in_quad(x, self) + if l != 4: + return False + if CheckRect(x): + if Rect(x).is_empty: + return True + return util_point_in_quad(x[:2], self) and util_point_in_quad(x[2:], self) + if CheckQuad(x): + for i in range(4): + if not util_point_in_quad(x[i], self): + return False + return True + return False + + + def __getitem__(self, i): + return (self.ul, self.ur, self.ll, self.lr)[i] + + def __len__(self): + return 4 + + def __setitem__(self, i, v): + if i == 0: self.ul = Point(v) + elif i == 1: self.ur = Point(v) + elif i == 2: self.ll = Point(v) + elif i == 3: self.lr = Point(v) + else: + raise IndexError("index out of range") + return None + + def __repr__(self): + return "Quad" + str(tuple(self)) + + def __pos__(self): + return Quad(self) + + def __neg__(self): + return Quad(-self.ul, -self.ur, -self.ll, -self.lr) + + def __bool__(self): + return not self.is_empty + + def __nonzero__(self): + return not self.is_empty + + def __eq__(self, quad): + if not hasattr(quad, "__len__"): + return False + return len(quad) == 4 and ( + self.ul == quad[0] and + self.ur == quad[1] and + self.ll == quad[2] and + self.lr == quad[3] + ) + + def __abs__(self): + if self.is_empty: + return 0.0 + return abs(self.ul - self.ur) * abs(self.ul - self.ll) + + + def morph(self, p, m): + """Morph the quad with matrix-like 'm' and point-like 'p'. + + Return a new quad.""" + if self.is_infinite: + return INFINITE_QUAD() + delta = Matrix(1, 1).pretranslate(p.x, p.y) + q = self * ~delta * m * delta + return q + + + def transform(self, m): + """Replace quad by its transformation with matrix m.""" + if hasattr(m, "__float__"): + pass + elif len(m) != 6: + raise ValueError("Matrix: bad seq len") + self.ul *= m + self.ur *= m + self.ll *= m + self.lr *= m + return self + + def __mul__(self, m): + q = Quad(self) + q = q.transform(m) + return q + + def __add__(self, q): + if hasattr(q, "__float__"): + return Quad(self.ul + q, self.ur + q, self.ll + q, self.lr + q) + if len(p) != 4: + raise ValueError("Quad: bad seq len") + return Quad(self.ul + q[0], self.ur + q[1], self.ll + q[2], self.lr + q[3]) + + + def __sub__(self, q): + if hasattr(q, "__float__"): + return Quad(self.ul - q, self.ur - q, self.ll - q, self.lr - q) + if len(p) != 4: + raise ValueError("Quad: bad seq len") + return Quad(self.ul - q[0], self.ur - q[1], self.ll - q[2], self.lr - q[3]) + + + def __truediv__(self, m): + if hasattr(m, "__float__"): + im = 1. / m + else: + im = util_invert_matrix(m)[1] + if not im: + raise ZeroDivisionError("Matrix not invertible") + q = Quad(self) + q = q.transform(im) + return q + + __div__ = __truediv__ + + + def __hash__(self): + return hash(tuple(self)) + + +# some special geometry objects +def EMPTY_RECT(): + return Rect(FZ_MAX_INF_RECT, FZ_MAX_INF_RECT, FZ_MIN_INF_RECT, FZ_MIN_INF_RECT) + + +def INFINITE_RECT(): + return Rect(FZ_MIN_INF_RECT, FZ_MIN_INF_RECT, FZ_MAX_INF_RECT, FZ_MAX_INF_RECT) + + +def EMPTY_IRECT(): + return IRect(FZ_MAX_INF_RECT, FZ_MAX_INF_RECT, FZ_MIN_INF_RECT, FZ_MIN_INF_RECT) + + +def INFINITE_IRECT(): + return IRect(FZ_MIN_INF_RECT, FZ_MIN_INF_RECT, FZ_MAX_INF_RECT, FZ_MAX_INF_RECT) + + +def INFINITE_QUAD(): + return INFINITE_RECT().quad + + +def EMPTY_QUAD(): + return EMPTY_RECT().quad + + +%}
