Blame Lib/fontTools/misc/transform.py

rpm-build 6a2e4c
"""Affine 2D transformation matrix class.
rpm-build 6a2e4c
rpm-build 6a2e4c
The Transform class implements various transformation matrix operations,
rpm-build 6a2e4c
both on the matrix itself, as well as on 2D coordinates.
rpm-build 6a2e4c
rpm-build 6a2e4c
Transform instances are effectively immutable: all methods that operate on the
rpm-build 6a2e4c
transformation itself always return a new instance. This has as the
rpm-build 6a2e4c
interesting side effect that Transform instances are hashable, ie. they can be
rpm-build 6a2e4c
used as dictionary keys.
rpm-build 6a2e4c
rpm-build 6a2e4c
This module exports the following symbols:
rpm-build 6a2e4c
rpm-build 6a2e4c
	Transform -- this is the main class
rpm-build 6a2e4c
	Identity  -- Transform instance set to the identity transformation
rpm-build 6a2e4c
	Offset    -- Convenience function that returns a translating transformation
rpm-build 6a2e4c
	Scale     -- Convenience function that returns a scaling transformation
rpm-build 6a2e4c
rpm-build 6a2e4c
Examples:
rpm-build 6a2e4c
rpm-build 6a2e4c
	>>> t = Transform(2, 0, 0, 3, 0, 0)
rpm-build 6a2e4c
	>>> t.transformPoint((100, 100))
rpm-build 6a2e4c
	(200, 300)
rpm-build 6a2e4c
	>>> t = Scale(2, 3)
rpm-build 6a2e4c
	>>> t.transformPoint((100, 100))
rpm-build 6a2e4c
	(200, 300)
rpm-build 6a2e4c
	>>> t.transformPoint((0, 0))
rpm-build 6a2e4c
	(0, 0)
rpm-build 6a2e4c
	>>> t = Offset(2, 3)
rpm-build 6a2e4c
	>>> t.transformPoint((100, 100))
rpm-build 6a2e4c
	(102, 103)
rpm-build 6a2e4c
	>>> t.transformPoint((0, 0))
rpm-build 6a2e4c
	(2, 3)
rpm-build 6a2e4c
	>>> t2 = t.scale(0.5)
rpm-build 6a2e4c
	>>> t2.transformPoint((100, 100))
rpm-build 6a2e4c
	(52.0, 53.0)
rpm-build 6a2e4c
	>>> import math
rpm-build 6a2e4c
	>>> t3 = t2.rotate(math.pi / 2)
rpm-build 6a2e4c
	>>> t3.transformPoint((0, 0))
rpm-build 6a2e4c
	(2.0, 3.0)
rpm-build 6a2e4c
	>>> t3.transformPoint((100, 100))
rpm-build 6a2e4c
	(-48.0, 53.0)
rpm-build 6a2e4c
	>>> t = Identity.scale(0.5).translate(100, 200).skew(0.1, 0.2)
rpm-build 6a2e4c
	>>> t.transformPoints([(0, 0), (1, 1), (100, 100)])
rpm-build 6a2e4c
	[(50.0, 100.0), (50.550167336042726, 100.60135501775433), (105.01673360427253, 160.13550177543362)]
rpm-build 6a2e4c
	>>>
rpm-build 6a2e4c
"""
rpm-build 6a2e4c
rpm-build 6a2e4c
from __future__ import print_function, division, absolute_import
rpm-build 6a2e4c
from fontTools.misc.py23 import *
rpm-build 6a2e4c
rpm-build 6a2e4c
__all__ = ["Transform", "Identity", "Offset", "Scale"]
rpm-build 6a2e4c
rpm-build 6a2e4c
rpm-build 6a2e4c
_EPSILON = 1e-15
rpm-build 6a2e4c
_ONE_EPSILON = 1 - _EPSILON
rpm-build 6a2e4c
_MINUS_ONE_EPSILON = -1 + _EPSILON
rpm-build 6a2e4c
rpm-build 6a2e4c
rpm-build 6a2e4c
def _normSinCos(v):
rpm-build 6a2e4c
	if abs(v) < _EPSILON:
rpm-build 6a2e4c
		v = 0
rpm-build 6a2e4c
	elif v > _ONE_EPSILON:
rpm-build 6a2e4c
		v = 1
rpm-build 6a2e4c
	elif v < _MINUS_ONE_EPSILON:
rpm-build 6a2e4c
		v = -1
rpm-build 6a2e4c
	return v
rpm-build 6a2e4c
rpm-build 6a2e4c
rpm-build 6a2e4c
class Transform(object):
rpm-build 6a2e4c
rpm-build 6a2e4c
	"""2x2 transformation matrix plus offset, a.k.a. Affine transform.
rpm-build 6a2e4c
	Transform instances are immutable: all transforming methods, eg.
rpm-build 6a2e4c
	rotate(), return a new Transform instance.
rpm-build 6a2e4c
rpm-build 6a2e4c
	Examples:
rpm-build 6a2e4c
		>>> t = Transform()
rpm-build 6a2e4c
		>>> t
rpm-build 6a2e4c
		<Transform [1 0 0 1 0 0]>
rpm-build 6a2e4c
		>>> t.scale(2)
rpm-build 6a2e4c
		<Transform [2 0 0 2 0 0]>
rpm-build 6a2e4c
		>>> t.scale(2.5, 5.5)
rpm-build 6a2e4c
		<Transform [2.5 0 0 5.5 0 0]>
rpm-build 6a2e4c
		>>>
rpm-build 6a2e4c
		>>> t.scale(2, 3).transformPoint((100, 100))
rpm-build 6a2e4c
		(200, 300)
rpm-build 6a2e4c
	"""
rpm-build 6a2e4c
rpm-build 6a2e4c
	def __init__(self, xx=1, xy=0, yx=0, yy=1, dx=0, dy=0):
rpm-build 6a2e4c
		"""Transform's constructor takes six arguments, all of which are
rpm-build 6a2e4c
		optional, and can be used as keyword arguments:
rpm-build 6a2e4c
			>>> Transform(12)
rpm-build 6a2e4c
			<Transform [12 0 0 1 0 0]>
rpm-build 6a2e4c
			>>> Transform(dx=12)
rpm-build 6a2e4c
			<Transform [1 0 0 1 12 0]>
rpm-build 6a2e4c
			>>> Transform(yx=12)
rpm-build 6a2e4c
			<Transform [1 0 12 1 0 0]>
rpm-build 6a2e4c
			>>>
rpm-build 6a2e4c
		"""
rpm-build 6a2e4c
		self.__affine = xx, xy, yx, yy, dx, dy
rpm-build 6a2e4c
rpm-build 6a2e4c
	def transformPoint(self, p):
rpm-build 6a2e4c
		"""Transform a point.
rpm-build 6a2e4c
rpm-build 6a2e4c
		Example:
rpm-build 6a2e4c
			>>> t = Transform()
rpm-build 6a2e4c
			>>> t = t.scale(2.5, 5.5)
rpm-build 6a2e4c
			>>> t.transformPoint((100, 100))
rpm-build 6a2e4c
			(250.0, 550.0)
rpm-build 6a2e4c
		"""
rpm-build 6a2e4c
		(x, y) = p
rpm-build 6a2e4c
		xx, xy, yx, yy, dx, dy = self.__affine
rpm-build 6a2e4c
		return (xx*x + yx*y + dx, xy*x + yy*y + dy)
rpm-build 6a2e4c
rpm-build 6a2e4c
	def transformPoints(self, points):
rpm-build 6a2e4c
		"""Transform a list of points.
rpm-build 6a2e4c
rpm-build 6a2e4c
		Example:
rpm-build 6a2e4c
			>>> t = Scale(2, 3)
rpm-build 6a2e4c
			>>> t.transformPoints([(0, 0), (0, 100), (100, 100), (100, 0)])
rpm-build 6a2e4c
			[(0, 0), (0, 300), (200, 300), (200, 0)]
rpm-build 6a2e4c
			>>>
rpm-build 6a2e4c
		"""
rpm-build 6a2e4c
		xx, xy, yx, yy, dx, dy = self.__affine
rpm-build 6a2e4c
		return [(xx*x + yx*y + dx, xy*x + yy*y + dy) for x, y in points]
rpm-build 6a2e4c
rpm-build 6a2e4c
	def translate(self, x=0, y=0):
rpm-build 6a2e4c
		"""Return a new transformation, translated (offset) by x, y.
rpm-build 6a2e4c
rpm-build 6a2e4c
		Example:
rpm-build 6a2e4c
			>>> t = Transform()
rpm-build 6a2e4c
			>>> t.translate(20, 30)
rpm-build 6a2e4c
			<Transform [1 0 0 1 20 30]>
rpm-build 6a2e4c
			>>>
rpm-build 6a2e4c
		"""
rpm-build 6a2e4c
		return self.transform((1, 0, 0, 1, x, y))
rpm-build 6a2e4c
rpm-build 6a2e4c
	def scale(self, x=1, y=None):
rpm-build 6a2e4c
		"""Return a new transformation, scaled by x, y. The 'y' argument
rpm-build 6a2e4c
		may be None, which implies to use the x value for y as well.
rpm-build 6a2e4c
rpm-build 6a2e4c
		Example:
rpm-build 6a2e4c
			>>> t = Transform()
rpm-build 6a2e4c
			>>> t.scale(5)
rpm-build 6a2e4c
			<Transform [5 0 0 5 0 0]>
rpm-build 6a2e4c
			>>> t.scale(5, 6)
rpm-build 6a2e4c
			<Transform [5 0 0 6 0 0]>
rpm-build 6a2e4c
			>>>
rpm-build 6a2e4c
		"""
rpm-build 6a2e4c
		if y is None:
rpm-build 6a2e4c
			y = x
rpm-build 6a2e4c
		return self.transform((x, 0, 0, y, 0, 0))
rpm-build 6a2e4c
rpm-build 6a2e4c
	def rotate(self, angle):
rpm-build 6a2e4c
		"""Return a new transformation, rotated by 'angle' (radians).
rpm-build 6a2e4c
rpm-build 6a2e4c
		Example:
rpm-build 6a2e4c
			>>> import math
rpm-build 6a2e4c
			>>> t = Transform()
rpm-build 6a2e4c
			>>> t.rotate(math.pi / 2)
rpm-build 6a2e4c
			<Transform [0 1 -1 0 0 0]>
rpm-build 6a2e4c
			>>>
rpm-build 6a2e4c
		"""
rpm-build 6a2e4c
		import math
rpm-build 6a2e4c
		c = _normSinCos(math.cos(angle))
rpm-build 6a2e4c
		s = _normSinCos(math.sin(angle))
rpm-build 6a2e4c
		return self.transform((c, s, -s, c, 0, 0))
rpm-build 6a2e4c
rpm-build 6a2e4c
	def skew(self, x=0, y=0):
rpm-build 6a2e4c
		"""Return a new transformation, skewed by x and y.
rpm-build 6a2e4c
rpm-build 6a2e4c
		Example:
rpm-build 6a2e4c
			>>> import math
rpm-build 6a2e4c
			>>> t = Transform()
rpm-build 6a2e4c
			>>> t.skew(math.pi / 4)
rpm-build 6a2e4c
			<Transform [1 0 1 1 0 0]>
rpm-build 6a2e4c
			>>>
rpm-build 6a2e4c
		"""
rpm-build 6a2e4c
		import math
rpm-build 6a2e4c
		return self.transform((1, math.tan(y), math.tan(x), 1, 0, 0))
rpm-build 6a2e4c
rpm-build 6a2e4c
	def transform(self, other):
rpm-build 6a2e4c
		"""Return a new transformation, transformed by another
rpm-build 6a2e4c
		transformation.
rpm-build 6a2e4c
rpm-build 6a2e4c
		Example:
rpm-build 6a2e4c
			>>> t = Transform(2, 0, 0, 3, 1, 6)
rpm-build 6a2e4c
			>>> t.transform((4, 3, 2, 1, 5, 6))
rpm-build 6a2e4c
			<Transform [8 9 4 3 11 24]>
rpm-build 6a2e4c
			>>>
rpm-build 6a2e4c
		"""
rpm-build 6a2e4c
		xx1, xy1, yx1, yy1, dx1, dy1 = other
rpm-build 6a2e4c
		xx2, xy2, yx2, yy2, dx2, dy2 = self.__affine
rpm-build 6a2e4c
		return self.__class__(
rpm-build 6a2e4c
				xx1*xx2 + xy1*yx2,
rpm-build 6a2e4c
				xx1*xy2 + xy1*yy2,
rpm-build 6a2e4c
				yx1*xx2 + yy1*yx2,
rpm-build 6a2e4c
				yx1*xy2 + yy1*yy2,
rpm-build 6a2e4c
				xx2*dx1 + yx2*dy1 + dx2,
rpm-build 6a2e4c
				xy2*dx1 + yy2*dy1 + dy2)
rpm-build 6a2e4c
rpm-build 6a2e4c
	def reverseTransform(self, other):
rpm-build 6a2e4c
		"""Return a new transformation, which is the other transformation
rpm-build 6a2e4c
		transformed by self. self.reverseTransform(other) is equivalent to
rpm-build 6a2e4c
		other.transform(self).
rpm-build 6a2e4c
rpm-build 6a2e4c
		Example:
rpm-build 6a2e4c
			>>> t = Transform(2, 0, 0, 3, 1, 6)
rpm-build 6a2e4c
			>>> t.reverseTransform((4, 3, 2, 1, 5, 6))
rpm-build 6a2e4c
			<Transform [8 6 6 3 21 15]>
rpm-build 6a2e4c
			>>> Transform(4, 3, 2, 1, 5, 6).transform((2, 0, 0, 3, 1, 6))
rpm-build 6a2e4c
			<Transform [8 6 6 3 21 15]>
rpm-build 6a2e4c
			>>>
rpm-build 6a2e4c
		"""
rpm-build 6a2e4c
		xx1, xy1, yx1, yy1, dx1, dy1 = self.__affine
rpm-build 6a2e4c
		xx2, xy2, yx2, yy2, dx2, dy2 = other
rpm-build 6a2e4c
		return self.__class__(
rpm-build 6a2e4c
				xx1*xx2 + xy1*yx2,
rpm-build 6a2e4c
				xx1*xy2 + xy1*yy2,
rpm-build 6a2e4c
				yx1*xx2 + yy1*yx2,
rpm-build 6a2e4c
				yx1*xy2 + yy1*yy2,
rpm-build 6a2e4c
				xx2*dx1 + yx2*dy1 + dx2,
rpm-build 6a2e4c
				xy2*dx1 + yy2*dy1 + dy2)
rpm-build 6a2e4c
rpm-build 6a2e4c
	def inverse(self):
rpm-build 6a2e4c
		"""Return the inverse transformation.
rpm-build 6a2e4c
rpm-build 6a2e4c
		Example:
rpm-build 6a2e4c
			>>> t = Identity.translate(2, 3).scale(4, 5)
rpm-build 6a2e4c
			>>> t.transformPoint((10, 20))
rpm-build 6a2e4c
			(42, 103)
rpm-build 6a2e4c
			>>> it = t.inverse()
rpm-build 6a2e4c
			>>> it.transformPoint((42, 103))
rpm-build 6a2e4c
			(10.0, 20.0)
rpm-build 6a2e4c
			>>>
rpm-build 6a2e4c
		"""
rpm-build 6a2e4c
		if self.__affine == (1, 0, 0, 1, 0, 0):
rpm-build 6a2e4c
			return self
rpm-build 6a2e4c
		xx, xy, yx, yy, dx, dy = self.__affine
rpm-build 6a2e4c
		det = xx*yy - yx*xy
rpm-build 6a2e4c
		xx, xy, yx, yy = yy/det, -xy/det, -yx/det, xx/det
rpm-build 6a2e4c
		dx, dy = -xx*dx - yx*dy, -xy*dx - yy*dy
rpm-build 6a2e4c
		return self.__class__(xx, xy, yx, yy, dx, dy)
rpm-build 6a2e4c
rpm-build 6a2e4c
	def toPS(self):
rpm-build 6a2e4c
		"""Return a PostScript representation:
rpm-build 6a2e4c
			>>> t = Identity.scale(2, 3).translate(4, 5)
rpm-build 6a2e4c
			>>> t.toPS()
rpm-build 6a2e4c
			'[2 0 0 3 8 15]'
rpm-build 6a2e4c
			>>>
rpm-build 6a2e4c
		"""
rpm-build 6a2e4c
		return "[%s %s %s %s %s %s]" % self.__affine
rpm-build 6a2e4c
rpm-build 6a2e4c
	def __len__(self):
rpm-build 6a2e4c
		"""Transform instances also behave like sequences of length 6:
rpm-build 6a2e4c
			>>> len(Identity)
rpm-build 6a2e4c
			6
rpm-build 6a2e4c
			>>>
rpm-build 6a2e4c
		"""
rpm-build 6a2e4c
		return 6
rpm-build 6a2e4c
rpm-build 6a2e4c
	def __getitem__(self, index):
rpm-build 6a2e4c
		"""Transform instances also behave like sequences of length 6:
rpm-build 6a2e4c
			>>> list(Identity)
rpm-build 6a2e4c
			[1, 0, 0, 1, 0, 0]
rpm-build 6a2e4c
			>>> tuple(Identity)
rpm-build 6a2e4c
			(1, 0, 0, 1, 0, 0)
rpm-build 6a2e4c
			>>>
rpm-build 6a2e4c
		"""
rpm-build 6a2e4c
		return self.__affine[index]
rpm-build 6a2e4c
rpm-build 6a2e4c
	def __ne__(self, other):
rpm-build 6a2e4c
		return not self.__eq__(other)
rpm-build 6a2e4c
	def __eq__(self, other):
rpm-build 6a2e4c
		"""Transform instances are comparable:
rpm-build 6a2e4c
			>>> t1 = Identity.scale(2, 3).translate(4, 6)
rpm-build 6a2e4c
			>>> t2 = Identity.translate(8, 18).scale(2, 3)
rpm-build 6a2e4c
			>>> t1 == t2
rpm-build 6a2e4c
			1
rpm-build 6a2e4c
			>>>
rpm-build 6a2e4c
rpm-build 6a2e4c
		But beware of floating point rounding errors:
rpm-build 6a2e4c
			>>> t1 = Identity.scale(0.2, 0.3).translate(0.4, 0.6)
rpm-build 6a2e4c
			>>> t2 = Identity.translate(0.08, 0.18).scale(0.2, 0.3)
rpm-build 6a2e4c
			>>> t1
rpm-build 6a2e4c
			<Transform [0.2 0 0 0.3 0.08 0.18]>
rpm-build 6a2e4c
			>>> t2
rpm-build 6a2e4c
			<Transform [0.2 0 0 0.3 0.08 0.18]>
rpm-build 6a2e4c
			>>> t1 == t2
rpm-build 6a2e4c
			0
rpm-build 6a2e4c
			>>>
rpm-build 6a2e4c
		"""
rpm-build 6a2e4c
		xx1, xy1, yx1, yy1, dx1, dy1 = self.__affine
rpm-build 6a2e4c
		xx2, xy2, yx2, yy2, dx2, dy2 = other
rpm-build 6a2e4c
		return (xx1, xy1, yx1, yy1, dx1, dy1) == \
rpm-build 6a2e4c
				(xx2, xy2, yx2, yy2, dx2, dy2)
rpm-build 6a2e4c
rpm-build 6a2e4c
	def __hash__(self):
rpm-build 6a2e4c
		"""Transform instances are hashable, meaning you can use them as
rpm-build 6a2e4c
		keys in dictionaries:
rpm-build 6a2e4c
			>>> d = {Scale(12, 13): None}
rpm-build 6a2e4c
			>>> d
rpm-build 6a2e4c
			{<Transform [12 0 0 13 0 0]>: None}
rpm-build 6a2e4c
			>>>
rpm-build 6a2e4c
rpm-build 6a2e4c
		But again, beware of floating point rounding errors:
rpm-build 6a2e4c
			>>> t1 = Identity.scale(0.2, 0.3).translate(0.4, 0.6)
rpm-build 6a2e4c
			>>> t2 = Identity.translate(0.08, 0.18).scale(0.2, 0.3)
rpm-build 6a2e4c
			>>> t1
rpm-build 6a2e4c
			<Transform [0.2 0 0 0.3 0.08 0.18]>
rpm-build 6a2e4c
			>>> t2
rpm-build 6a2e4c
			<Transform [0.2 0 0 0.3 0.08 0.18]>
rpm-build 6a2e4c
			>>> d = {t1: None}
rpm-build 6a2e4c
			>>> d
rpm-build 6a2e4c
			{<Transform [0.2 0 0 0.3 0.08 0.18]>: None}
rpm-build 6a2e4c
			>>> d[t2]
rpm-build 6a2e4c
			Traceback (most recent call last):
rpm-build 6a2e4c
			  File "<stdin>", line 1, in ?
rpm-build 6a2e4c
			KeyError: <Transform [0.2 0 0 0.3 0.08 0.18]>
rpm-build 6a2e4c
			>>>
rpm-build 6a2e4c
		"""
rpm-build 6a2e4c
		return hash(self.__affine)
rpm-build 6a2e4c
rpm-build 6a2e4c
	def __bool__(self):
rpm-build 6a2e4c
		"""Returns True if transform is not identity, False otherwise.
rpm-build 6a2e4c
			>>> bool(Identity)
rpm-build 6a2e4c
			False
rpm-build 6a2e4c
			>>> bool(Transform())
rpm-build 6a2e4c
			False
rpm-build 6a2e4c
			>>> bool(Scale(1.))
rpm-build 6a2e4c
			False
rpm-build 6a2e4c
			>>> bool(Scale(2))
rpm-build 6a2e4c
			True
rpm-build 6a2e4c
			>>> bool(Offset())
rpm-build 6a2e4c
			False
rpm-build 6a2e4c
			>>> bool(Offset(0))
rpm-build 6a2e4c
			False
rpm-build 6a2e4c
			>>> bool(Offset(2))
rpm-build 6a2e4c
			True
rpm-build 6a2e4c
		"""
rpm-build 6a2e4c
		return self.__affine != Identity.__affine
rpm-build 6a2e4c
rpm-build 6a2e4c
	__nonzero__ = __bool__
rpm-build 6a2e4c
rpm-build 6a2e4c
	def __repr__(self):
rpm-build 6a2e4c
		return "<%s [%g %g %g %g %g %g]>" % ((self.__class__.__name__,) \
rpm-build 6a2e4c
				+ self.__affine)
rpm-build 6a2e4c
rpm-build 6a2e4c
rpm-build 6a2e4c
Identity = Transform()
rpm-build 6a2e4c
rpm-build 6a2e4c
def Offset(x=0, y=0):
rpm-build 6a2e4c
	"""Return the identity transformation offset by x, y.
rpm-build 6a2e4c
rpm-build 6a2e4c
	Example:
rpm-build 6a2e4c
		>>> Offset(2, 3)
rpm-build 6a2e4c
		<Transform [1 0 0 1 2 3]>
rpm-build 6a2e4c
		>>>
rpm-build 6a2e4c
	"""
rpm-build 6a2e4c
	return Transform(1, 0, 0, 1, x, y)
rpm-build 6a2e4c
rpm-build 6a2e4c
def Scale(x, y=None):
rpm-build 6a2e4c
	"""Return the identity transformation scaled by x, y. The 'y' argument
rpm-build 6a2e4c
	may be None, which implies to use the x value for y as well.
rpm-build 6a2e4c
rpm-build 6a2e4c
	Example:
rpm-build 6a2e4c
		>>> Scale(2, 3)
rpm-build 6a2e4c
		<Transform [2 0 0 3 0 0]>
rpm-build 6a2e4c
		>>>
rpm-build 6a2e4c
	"""
rpm-build 6a2e4c
	if y is None:
rpm-build 6a2e4c
		y = x
rpm-build 6a2e4c
	return Transform(x, 0, 0, y, 0, 0)
rpm-build 6a2e4c
rpm-build 6a2e4c
rpm-build 6a2e4c
if __name__ == "__main__":
rpm-build 6a2e4c
	import sys
rpm-build 6a2e4c
	import doctest
rpm-build 6a2e4c
	sys.exit(doctest.testmod().failed)