Skip to content

Commit

Permalink
Allow passing a transformation object directly
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinThoma committed May 19, 2022
1 parent be1c3b6 commit db7fe7e
Showing 1 changed file with 80 additions and 72 deletions.
152 changes: 80 additions & 72 deletions PyPDF2/_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,76 @@ def createRectangleAccessor(name: str, fallback: Iterable[str]) -> property:
)


class Transformation:
"""
The transformation between two coordinate systems is represented by a 3-by-3
transformation matrix written as follows:
a b 0
c d 0
e f 1
Because a transformation matrix has only six elements that can be changed,
it is usually specified in PDF as the six-element array [ a b c d e f ].
Coordinate transformations are expressed as matrix multiplications:
a b 0
[ x′ y′ 1 ] = [ x y 1 ] × c d 0
e f 1
Usage
-----
>>> from PyPDF2 import Transformation
>>> op = Transformation().scale(sx=2, sy=3).translate(tx=10, ty=20)
>>> page.mergeTransformedPage(page2, op)
"""

# 9.5.4 Coordinate Systems for 3D
# 4.2.2 Common Transformations
def __init__(self, ctm: CompressedTransformationMatrix = (1, 0, 0, 1, 0, 0)):
self.ctm = ctm

@property
def matrix(self) -> TransformationMatrixType:
return (
(self.ctm[0], self.ctm[1], 0),
(self.ctm[2], self.ctm[3], 0),
(self.ctm[4], self.ctm[5], 1),
)

@staticmethod
def compress(matrix: TransformationMatrixType) -> CompressedTransformationMatrix:
return (
matrix[0][0],
matrix[0][1],
matrix[1][0],
matrix[1][1],
matrix[0][2],
matrix[1][2],
)

def translate(self, tx: float, ty: float) -> "Transformation":
op = ((1, 0, 0), (0, 1, 0), (tx, ty, 1))
ctm = Transformation.compress(matrixMultiply(self.matrix, op))
return Transformation(ctm)

def scale(self, sx: float, sy: float) -> "Transformation":
op: TransformationMatrixType = ((sx, 0, 0), (0, sy, 0), (0, 0, 1))
ctm = Transformation.compress(matrixMultiply(self.matrix, op))
return Transformation(ctm)

def rotate(self, rotation: float) -> "Transformation":
rotation = math.radians(rotation)
op: TransformationMatrixType = (
(math.cos(rotation), math.sin(rotation), 0),
(-math.sin(rotation), math.cos(rotation), 0),
(0, 0, 1),
)
ctm = Transformation.compress(matrixMultiply(self.matrix, op))
return Transformation(ctm)


class PageObject(DictionaryObject):
"""
PageObject represents a single page within a PDF file.
Expand Down Expand Up @@ -421,7 +491,7 @@ def _mergePage(
def mergeTransformedPage(
self,
page2: "PageObject",
ctm: CompressedTransformationMatrix,
ctm: Union[CompressedTransformationMatrix, Transformation],
expand: bool = False,
) -> None:
"""
Expand All @@ -435,6 +505,8 @@ def mergeTransformedPage(
:param bool expand: Whether the page should be expanded to fit the dimensions
of the page to be merged.
"""
if isinstance(ctm, Transformation):
ctm = ctm.ctm
self._mergePage(
page2,
lambda page2Content: PageObject._addTransformationMatrix(
Expand All @@ -458,7 +530,7 @@ def mergeScaledPage(
dimensions of the page to be merged.
"""
op = Transformation().scale(scale, scale)
self.mergeTransformedPage(page2, op.ctm, expand)
self.mergeTransformedPage(page2, op, expand)

def mergeRotatedPage(
self, page2: "PageObject", rotation: float, expand: bool = False
Expand All @@ -474,7 +546,7 @@ def mergeRotatedPage(
dimensions of the page to be merged.
"""
op = Transformation().rotate(rotation)
self.mergeTransformedPage(page2, op.ctm, expand)
self.mergeTransformedPage(page2, op, expand)

def mergeTranslatedPage(
self, page2: "PageObject", tx: float, ty: float, expand: bool = False
Expand All @@ -491,7 +563,7 @@ def mergeTranslatedPage(
dimensions of the page to be merged.
"""
op = Transformation().translate(tx, ty)
self.mergeTransformedPage(page2, op.ctm, expand)
self.mergeTransformedPage(page2, op, expand)

def mergeRotatedTranslatedPage(
self,
Expand All @@ -514,7 +586,7 @@ def mergeRotatedTranslatedPage(
dimensions of the page to be merged.
"""
op = Transformation().translate(-tx, -ty).rotate(rotation).translate(tx, ty)
return self.mergeTransformedPage(page2, op.ctm, expand)
return self.mergeTransformedPage(page2, op, expand)

def mergeRotatedScaledPage(
self, page2: "PageObject", rotation: float, scale: float, expand: bool = False
Expand All @@ -531,7 +603,7 @@ def mergeRotatedScaledPage(
dimensions of the page to be merged.
"""
op = Transformation().rotate(rotation).scale(scale, scale)
self.mergeTransformedPage(page2, op.ctm, expand)
self.mergeTransformedPage(page2, op, expand)

def mergeScaledTranslatedPage(
self,
Expand All @@ -554,7 +626,7 @@ def mergeScaledTranslatedPage(
dimensions of the page to be merged.
"""
op = Transformation().scale(scale, scale).translate(tx, ty)
return self.mergeTransformedPage(page2, op.ctm, expand)
return self.mergeTransformedPage(page2, op, expand)

def mergeRotatedScaledTranslatedPage(
self,
Expand All @@ -580,7 +652,7 @@ def mergeRotatedScaledTranslatedPage(
dimensions of the page to be merged.
"""
op = Transformation().rotate(rotation).scale(scale, scale).translate(tx, ty)
self.mergeTransformedPage(page2, op.ctm, expand)
self.mergeTransformedPage(page2, op, expand)

def addTransformation(self, ctm: CompressedTransformationMatrix) -> None:
"""
Expand Down Expand Up @@ -763,67 +835,3 @@ def extractText(self, Tj_sep: str = "", TJ_sep: str = "") -> str:
defining the extent of the page's meaningful content as intended by the
page's creator.
"""


class Transformation:
"""
The transformation between two coordinate systems is represented by a 3-by-3
transformation matrix written as follows:
a b 0
c d 0
e f 1
Because a transformation matrix has only six elements that can be changed,
it is usually specified in PDF as the six-element array [ a b c d e f ].
Coordinate transformations are expressed as matrix multiplications:
a b 0
[ x′ y′ 1 ] = [ x y 1 ] × c d 0
e f 1
"""

# 9.5.4 Coordinate Systems for 3D
# 4.2.2 Common Transformations
def __init__(self, ctm: CompressedTransformationMatrix = (1, 0, 0, 1, 0, 0)):
self.ctm = ctm

@property
def matrix(self) -> TransformationMatrixType:
return (
(self.ctm[0], self.ctm[1], 0),
(self.ctm[2], self.ctm[3], 0),
(self.ctm[4], self.ctm[5], 1),
)

@staticmethod
def compress(matrix: TransformationMatrixType) -> CompressedTransformationMatrix:
return (
matrix[0][0],
matrix[0][1],
matrix[1][0],
matrix[1][1],
matrix[0][2],
matrix[1][2],
)

def translate(self, tx: float, ty: float) -> "Transformation":
op = ((1, 0, 0), (0, 1, 0), (tx, ty, 1))
ctm = Transformation.compress(matrixMultiply(self.matrix, op))
return Transformation(ctm)

def scale(self, sx: float, sy: float) -> "Transformation":
op: TransformationMatrixType = ((sx, 0, 0), (0, sy, 0), (0, 0, 1))
ctm = Transformation.compress(matrixMultiply(self.matrix, op))
return Transformation(ctm)

def rotate(self, rotation: float) -> "Transformation":
rotation = math.radians(rotation)
op: TransformationMatrixType = (
(math.cos(rotation), math.sin(rotation), 0),
(-math.sin(rotation), math.cos(rotation), 0),
(0, 0, 1),
)
ctm = Transformation.compress(matrixMultiply(self.matrix, op))
return Transformation(ctm)

0 comments on commit db7fe7e

Please sign in to comment.