File: //lib/python3/dist-packages/pikepdf/models/matrix.py
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# Copyright (C) 2017, James R. Barlow (https://github.com/jbarlow83/)
from math import cos, pi, sin
class PdfMatrix:
"""
Support class for PDF content stream matrices
PDF content stream matrices are 3x3 matrices summarized by a shorthand
``(a, b, c, d, e, f)`` which correspond to the first two column vectors.
The final column vector is always ``(0, 0, 1)`` since this is using
`homogenous coordinates <https://en.wikipedia.org/wiki/Homogeneous_coordinates>`_.
PDF uses row vectors. That is, ``vr @ A'`` gives the effect of transforming
a row vector ``vr=(x, y, 1)`` by the matrix ``A'``. Most textbook
treatments use ``A @ vc`` where the column vector ``vc=(x, y, 1)'``.
(``@`` is the Python matrix multiplication operator added in Python 3.5.)
Addition and other operations are not implemented because they're not that
meaningful in a PDF context (they can be defined and are mathematically
meaningful in general).
PdfMatrix objects are immutable. All transformations on them produce a new
matrix.
"""
def __init__(self, *args):
# fmt: off
if not args:
self.values = ((1, 0, 0), (0, 1, 0), (0, 0, 1))
elif len(args) == 6:
a, b, c, d, e, f = map(float, args)
self.values = ((a, b, 0),
(c, d, 0),
(e, f, 1))
elif isinstance(args[0], PdfMatrix):
self.values = args[0].values
elif len(args[0]) == 6:
a, b, c, d, e, f = map(float, args[0])
self.values = ((a, b, 0),
(c, d, 0),
(e, f, 1))
elif len(args[0]) == 3 and len(args[0]) == 3:
self.values = (tuple(args[0][0]),
tuple(args[0][1]),
tuple(args[0][2]))
else:
raise ValueError('arguments')
# fmt: on
@staticmethod
def identity():
"""Constructs and returns an identity matrix"""
return PdfMatrix()
def __matmul__(self, other):
"""Multiply this matrix by another matrix
Can be used to concatenate transformations.
"""
a = self.values
b = other.values
return PdfMatrix(
[
[
sum([float(i) * float(j) for i, j in zip(row, col)])
for col in zip(*b)
]
for row in a
]
)
def scaled(self, x, y):
"""Concatenates a scaling matrix on this matrix"""
return self @ PdfMatrix((x, 0, 0, y, 0, 0))
def rotated(self, angle_degrees_ccw):
"""Concatenates a rotation matrix on this matrix"""
angle = angle_degrees_ccw / 180.0 * pi
c, s = cos(angle), sin(angle)
return self @ PdfMatrix((c, s, -s, c, 0, 0))
def translated(self, x, y):
"""Translates this matrix"""
return self @ PdfMatrix((1, 0, 0, 1, x, y))
@property
def shorthand(self):
"""Return the 6-tuple (a,b,c,d,e,f) that describes this matrix"""
return (self.a, self.b, self.c, self.d, self.e, self.f)
@property
def a(self):
return self.values[0][0]
@property
def b(self):
return self.values[0][1]
@property
def c(self):
return self.values[1][0]
@property
def d(self):
return self.values[1][1]
@property
def e(self):
return self.values[2][0]
@property
def f(self):
return self.values[2][1]
def encode(self):
"""Encode this matrix in binary suitable for including in a PDF"""
return '{:.6f} {:.6f} {:.6f} {:.6f} {:.6f} {:.6f}'.format(
self.a, self.b, self.c, self.d, self.e, self.f
).encode()
def __repr__(self):
return 'pikepdf.Matrix(' + repr(self.values) + ')'