Merge pull request #1904 from anthrotype/transform-named-tuple
transform: make Transform class a NamedTuple
This commit is contained in:
commit
70f0bb3570
@ -45,7 +45,8 @@ Examples:
|
|||||||
>>>
|
>>>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from fontTools.misc.py23 import *
|
from typing import NamedTuple
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["Transform", "Identity", "Offset", "Scale"]
|
__all__ = ["Transform", "Identity", "Offset", "Scale"]
|
||||||
|
|
||||||
@ -65,7 +66,7 @@ def _normSinCos(v):
|
|||||||
return v
|
return v
|
||||||
|
|
||||||
|
|
||||||
class Transform(object):
|
class Transform(NamedTuple):
|
||||||
|
|
||||||
"""2x2 transformation matrix plus offset, a.k.a. Affine transform.
|
"""2x2 transformation matrix plus offset, a.k.a. Affine transform.
|
||||||
Transform instances are immutable: all transforming methods, eg.
|
Transform instances are immutable: all transforming methods, eg.
|
||||||
@ -82,20 +83,68 @@ class Transform(object):
|
|||||||
>>>
|
>>>
|
||||||
>>> t.scale(2, 3).transformPoint((100, 100))
|
>>> t.scale(2, 3).transformPoint((100, 100))
|
||||||
(200, 300)
|
(200, 300)
|
||||||
|
|
||||||
|
Transform's constructor takes six arguments, all of which are
|
||||||
|
optional, and can be used as keyword arguments:
|
||||||
|
>>> Transform(12)
|
||||||
|
<Transform [12 0 0 1 0 0]>
|
||||||
|
>>> Transform(dx=12)
|
||||||
|
<Transform [1 0 0 1 12 0]>
|
||||||
|
>>> Transform(yx=12)
|
||||||
|
<Transform [1 0 12 1 0 0]>
|
||||||
|
|
||||||
|
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
|
||||||
|
<Transform [0.2 0 0 0.3 0.08 0.18]>
|
||||||
|
>>> t2
|
||||||
|
<Transform [0.2 0 0 0.3 0.08 0.18]>
|
||||||
|
>>> t1 == t2
|
||||||
|
0
|
||||||
|
|
||||||
|
Transform instances are hashable, meaning you can use them as
|
||||||
|
keys in dictionaries:
|
||||||
|
>>> d = {Scale(12, 13): None}
|
||||||
|
>>> d
|
||||||
|
{<Transform [12 0 0 13 0 0]>: 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
|
||||||
|
<Transform [0.2 0 0 0.3 0.08 0.18]>
|
||||||
|
>>> t2
|
||||||
|
<Transform [0.2 0 0 0.3 0.08 0.18]>
|
||||||
|
>>> d = {t1: None}
|
||||||
|
>>> d
|
||||||
|
{<Transform [0.2 0 0 0.3 0.08 0.18]>: None}
|
||||||
|
>>> d[t2]
|
||||||
|
Traceback (most recent call last):
|
||||||
|
File "<stdin>", line 1, in ?
|
||||||
|
KeyError: <Transform [0.2 0 0 0.3 0.08 0.18]>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, xx=1, xy=0, yx=0, yy=1, dx=0, dy=0):
|
xx: float = 1
|
||||||
"""Transform's constructor takes six arguments, all of which are
|
xy: float = 0
|
||||||
optional, and can be used as keyword arguments:
|
yx: float = 0
|
||||||
>>> Transform(12)
|
yy: float = 1
|
||||||
<Transform [12 0 0 1 0 0]>
|
dx: float = 0
|
||||||
>>> Transform(dx=12)
|
dy: float = 0
|
||||||
<Transform [1 0 0 1 12 0]>
|
|
||||||
>>> Transform(yx=12)
|
|
||||||
<Transform [1 0 12 1 0 0]>
|
|
||||||
>>>
|
|
||||||
"""
|
|
||||||
self.__affine = xx, xy, yx, yy, dx, dy
|
|
||||||
|
|
||||||
def transformPoint(self, p):
|
def transformPoint(self, p):
|
||||||
"""Transform a point.
|
"""Transform a point.
|
||||||
@ -107,7 +156,7 @@ class Transform(object):
|
|||||||
(250.0, 550.0)
|
(250.0, 550.0)
|
||||||
"""
|
"""
|
||||||
(x, y) = p
|
(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)
|
return (xx*x + yx*y + dx, xy*x + yy*y + dy)
|
||||||
|
|
||||||
def transformPoints(self, points):
|
def transformPoints(self, points):
|
||||||
@ -119,7 +168,7 @@ class Transform(object):
|
|||||||
[(0, 0), (0, 300), (200, 300), (200, 0)]
|
[(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]
|
return [(xx*x + yx*y + dx, xy*x + yy*y + dy) for x, y in points]
|
||||||
|
|
||||||
def translate(self, x=0, y=0):
|
def translate(self, x=0, y=0):
|
||||||
@ -188,7 +237,7 @@ class Transform(object):
|
|||||||
>>>
|
>>>
|
||||||
"""
|
"""
|
||||||
xx1, xy1, yx1, yy1, dx1, dy1 = other
|
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__(
|
return self.__class__(
|
||||||
xx1*xx2 + xy1*yx2,
|
xx1*xx2 + xy1*yx2,
|
||||||
xx1*xy2 + xy1*yy2,
|
xx1*xy2 + xy1*yy2,
|
||||||
@ -210,7 +259,7 @@ class Transform(object):
|
|||||||
<Transform [8 6 6 3 21 15]>
|
<Transform [8 6 6 3 21 15]>
|
||||||
>>>
|
>>>
|
||||||
"""
|
"""
|
||||||
xx1, xy1, yx1, yy1, dx1, dy1 = self.__affine
|
xx1, xy1, yx1, yy1, dx1, dy1 = self
|
||||||
xx2, xy2, yx2, yy2, dx2, dy2 = other
|
xx2, xy2, yx2, yy2, dx2, dy2 = other
|
||||||
return self.__class__(
|
return self.__class__(
|
||||||
xx1*xx2 + xy1*yx2,
|
xx1*xx2 + xy1*yx2,
|
||||||
@ -232,9 +281,9 @@ class Transform(object):
|
|||||||
(10.0, 20.0)
|
(10.0, 20.0)
|
||||||
>>>
|
>>>
|
||||||
"""
|
"""
|
||||||
if self.__affine == (1, 0, 0, 1, 0, 0):
|
if self == Identity:
|
||||||
return self
|
return self
|
||||||
xx, xy, yx, yy, dx, dy = self.__affine
|
xx, xy, yx, yy, dx, dy = self
|
||||||
det = xx*yy - yx*xy
|
det = xx*yy - yx*xy
|
||||||
xx, xy, yx, yy = yy/det, -xy/det, -yx/det, xx/det
|
xx, xy, yx, yy = yy/det, -xy/det, -yx/det, xx/det
|
||||||
dx, dy = -xx*dx - yx*dy, -xy*dx - yy*dy
|
dx, dy = -xx*dx - yx*dy, -xy*dx - yy*dy
|
||||||
@ -247,77 +296,7 @@ class Transform(object):
|
|||||||
'[2 0 0 3 8 15]'
|
'[2 0 0 3 8 15]'
|
||||||
>>>
|
>>>
|
||||||
"""
|
"""
|
||||||
return "[%s %s %s %s %s %s]" % self.__affine
|
return "[%s %s %s %s %s %s]" % self
|
||||||
|
|
||||||
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
|
|
||||||
<Transform [0.2 0 0 0.3 0.08 0.18]>
|
|
||||||
>>> t2
|
|
||||||
<Transform [0.2 0 0 0.3 0.08 0.18]>
|
|
||||||
>>> 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
|
|
||||||
{<Transform [12 0 0 13 0 0]>: 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
|
|
||||||
<Transform [0.2 0 0 0.3 0.08 0.18]>
|
|
||||||
>>> t2
|
|
||||||
<Transform [0.2 0 0 0.3 0.08 0.18]>
|
|
||||||
>>> d = {t1: None}
|
|
||||||
>>> d
|
|
||||||
{<Transform [0.2 0 0 0.3 0.08 0.18]>: None}
|
|
||||||
>>> d[t2]
|
|
||||||
Traceback (most recent call last):
|
|
||||||
File "<stdin>", line 1, in ?
|
|
||||||
KeyError: <Transform [0.2 0 0 0.3 0.08 0.18]>
|
|
||||||
>>>
|
|
||||||
"""
|
|
||||||
return hash(self.__affine)
|
|
||||||
|
|
||||||
def __bool__(self):
|
def __bool__(self):
|
||||||
"""Returns True if transform is not identity, False otherwise.
|
"""Returns True if transform is not identity, False otherwise.
|
||||||
@ -336,13 +315,10 @@ class Transform(object):
|
|||||||
>>> bool(Offset(2))
|
>>> bool(Offset(2))
|
||||||
True
|
True
|
||||||
"""
|
"""
|
||||||
return self.__affine != Identity.__affine
|
return self != Identity
|
||||||
|
|
||||||
__nonzero__ = __bool__
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<%s [%g %g %g %g %g %g]>" % ((self.__class__.__name__,) \
|
return "<%s [%g %g %g %g %g %g]>" % ((self.__class__.__name__,) + self)
|
||||||
+ self.__affine)
|
|
||||||
|
|
||||||
|
|
||||||
Identity = Transform()
|
Identity = Transform()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user