From dbc9d132c04ea9a26ce43f305037712c5ab2eea8 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Wed, 29 Apr 2020 11:11:39 +0100 Subject: [PATCH] transform: make Transform class a NamedTuple This removes some boilerplate code, and also helps when using static type checkers like mypy. The typing.NamedTuple class was added with python 3.6 which is our min required python, so we are good. --- Lib/fontTools/misc/transform.py | 166 ++++++++++++++------------------ 1 file changed, 72 insertions(+), 94 deletions(-) diff --git a/Lib/fontTools/misc/transform.py b/Lib/fontTools/misc/transform.py index e0e5111b7..3e6f028f6 100644 --- a/Lib/fontTools/misc/transform.py +++ b/Lib/fontTools/misc/transform.py @@ -45,7 +45,8 @@ Examples: >>> """ -from fontTools.misc.py23 import * +from typing import NamedTuple + __all__ = ["Transform", "Identity", "Offset", "Scale"] @@ -65,7 +66,7 @@ def _normSinCos(v): return v -class Transform(object): +class Transform(NamedTuple): """2x2 transformation matrix plus offset, a.k.a. Affine transform. Transform instances are immutable: all transforming methods, eg. @@ -82,20 +83,68 @@ class Transform(object): >>> >>> t.scale(2, 3).transformPoint((100, 100)) (200, 300) + + Transform's constructor takes six arguments, all of which are + optional, and can be used as keyword arguments: + >>> Transform(12) + + >>> Transform(dx=12) + + >>> Transform(yx=12) + + + Transform instances also behave like sequences of length 6: + >>> len(Identity) + 6 + >>> list(Identity) + [1, 0, 0, 1, 0, 0] + >>> tuple(Identity) + (1, 0, 0, 1, 0, 0) + + Transform instances are comparable: + >>> t1 = Identity.scale(2, 3).translate(4, 6) + >>> t2 = Identity.translate(8, 18).scale(2, 3) + >>> t1 == t2 + 1 + + But beware of floating point rounding errors: + >>> t1 = Identity.scale(0.2, 0.3).translate(0.4, 0.6) + >>> t2 = Identity.translate(0.08, 0.18).scale(0.2, 0.3) + >>> t1 + + >>> t2 + + >>> t1 == t2 + 0 + + Transform instances are hashable, meaning you can use them as + keys in dictionaries: + >>> d = {Scale(12, 13): None} + >>> d + {: None} + + But again, beware of floating point rounding errors: + >>> t1 = Identity.scale(0.2, 0.3).translate(0.4, 0.6) + >>> t2 = Identity.translate(0.08, 0.18).scale(0.2, 0.3) + >>> t1 + + >>> t2 + + >>> d = {t1: None} + >>> d + {: None} + >>> d[t2] + Traceback (most recent call last): + File "", line 1, in ? + KeyError: """ - def __init__(self, xx=1, xy=0, yx=0, yy=1, dx=0, dy=0): - """Transform's constructor takes six arguments, all of which are - optional, and can be used as keyword arguments: - >>> Transform(12) - - >>> Transform(dx=12) - - >>> Transform(yx=12) - - >>> - """ - self.__affine = xx, xy, yx, yy, dx, dy + xx: float = 1 + xy: float = 0 + yx: float = 0 + yy: float = 1 + dx: float = 0 + dy: float = 0 def transformPoint(self, p): """Transform a point. @@ -107,7 +156,7 @@ class Transform(object): (250.0, 550.0) """ (x, y) = p - xx, xy, yx, yy, dx, dy = self.__affine + xx, xy, yx, yy, dx, dy = self return (xx*x + yx*y + dx, xy*x + yy*y + dy) def transformPoints(self, points): @@ -119,7 +168,7 @@ class Transform(object): [(0, 0), (0, 300), (200, 300), (200, 0)] >>> """ - xx, xy, yx, yy, dx, dy = self.__affine + xx, xy, yx, yy, dx, dy = self return [(xx*x + yx*y + dx, xy*x + yy*y + dy) for x, y in points] def translate(self, x=0, y=0): @@ -188,7 +237,7 @@ class Transform(object): >>> """ xx1, xy1, yx1, yy1, dx1, dy1 = other - xx2, xy2, yx2, yy2, dx2, dy2 = self.__affine + xx2, xy2, yx2, yy2, dx2, dy2 = self return self.__class__( xx1*xx2 + xy1*yx2, xx1*xy2 + xy1*yy2, @@ -210,7 +259,7 @@ class Transform(object): >>> """ - xx1, xy1, yx1, yy1, dx1, dy1 = self.__affine + xx1, xy1, yx1, yy1, dx1, dy1 = self xx2, xy2, yx2, yy2, dx2, dy2 = other return self.__class__( xx1*xx2 + xy1*yx2, @@ -232,9 +281,9 @@ class Transform(object): (10.0, 20.0) >>> """ - if self.__affine == (1, 0, 0, 1, 0, 0): + if self == Identity: return self - xx, xy, yx, yy, dx, dy = self.__affine + xx, xy, yx, yy, dx, dy = self det = xx*yy - yx*xy xx, xy, yx, yy = yy/det, -xy/det, -yx/det, xx/det dx, dy = -xx*dx - yx*dy, -xy*dx - yy*dy @@ -247,77 +296,7 @@ class Transform(object): '[2 0 0 3 8 15]' >>> """ - return "[%s %s %s %s %s %s]" % self.__affine - - def __len__(self): - """Transform instances also behave like sequences of length 6: - >>> len(Identity) - 6 - >>> - """ - return 6 - - def __getitem__(self, index): - """Transform instances also behave like sequences of length 6: - >>> list(Identity) - [1, 0, 0, 1, 0, 0] - >>> tuple(Identity) - (1, 0, 0, 1, 0, 0) - >>> - """ - return self.__affine[index] - - def __ne__(self, other): - return not self.__eq__(other) - def __eq__(self, other): - """Transform instances are comparable: - >>> t1 = Identity.scale(2, 3).translate(4, 6) - >>> t2 = Identity.translate(8, 18).scale(2, 3) - >>> t1 == t2 - 1 - >>> - - But beware of floating point rounding errors: - >>> t1 = Identity.scale(0.2, 0.3).translate(0.4, 0.6) - >>> t2 = Identity.translate(0.08, 0.18).scale(0.2, 0.3) - >>> t1 - - >>> t2 - - >>> t1 == t2 - 0 - >>> - """ - xx1, xy1, yx1, yy1, dx1, dy1 = self.__affine - xx2, xy2, yx2, yy2, dx2, dy2 = other - return (xx1, xy1, yx1, yy1, dx1, dy1) == \ - (xx2, xy2, yx2, yy2, dx2, dy2) - - def __hash__(self): - """Transform instances are hashable, meaning you can use them as - keys in dictionaries: - >>> d = {Scale(12, 13): None} - >>> d - {: None} - >>> - - But again, beware of floating point rounding errors: - >>> t1 = Identity.scale(0.2, 0.3).translate(0.4, 0.6) - >>> t2 = Identity.translate(0.08, 0.18).scale(0.2, 0.3) - >>> t1 - - >>> t2 - - >>> d = {t1: None} - >>> d - {: None} - >>> d[t2] - Traceback (most recent call last): - File "", line 1, in ? - KeyError: - >>> - """ - return hash(self.__affine) + return "[%s %s %s %s %s %s]" % self def __bool__(self): """Returns True if transform is not identity, False otherwise. @@ -336,13 +315,12 @@ class Transform(object): >>> bool(Offset(2)) True """ - return self.__affine != Identity.__affine + return self != Identity __nonzero__ = __bool__ def __repr__(self): - return "<%s [%g %g %g %g %g %g]>" % ((self.__class__.__name__,) \ - + self.__affine) + return "<%s [%g %g %g %g %g %g]>" % ((self.__class__.__name__,) + self) Identity = Transform()