Skip to content

Commit

Permalink
Mirror function (#783)
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastiantia committed May 8, 2023
1 parent 063a10b commit 47e910f
Show file tree
Hide file tree
Showing 13 changed files with 257 additions and 3 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ in order to get warned about deprecated features used in your code.
This can also be enabled programmatically with `warnings.simplefilter('default', DeprecationWarning)`.

## [2.7.5] - Not released yet
### Added
- [`FPDF.mirror()`](https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.mirror) - New method: [documentation page](https://pyfpdf.github.io/fpdf2/Transformations.html) - Contributed by @sebastiantia

## [2.7.4] - 2023-04-28
### Added
Expand Down
35 changes: 34 additions & 1 deletion docs/Transformations.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,37 @@ pdf.set_fill_color(r=230, g=30, b=180)
with pdf.skew(ax=-45, ay=0, x=100, y=170):
pdf.circle(x=100, y=170, r=10, style="FD")
```
![](slanted_circle.png)
![](slanted_circle.png)

## Mirror ##

The `mirror` context-manager applies a mirror transformation to all objects inserted in its indented block over a given mirror line by specifying starting co-ordinate and angle.

```python
x = 100
y = 100
pdf.text(x, y, txt="mirror this text")
with pdf.mirror((x, y), "EAST"):
pdf.set_text_color(r=255, g=128, b=0)
pdf.text(x, y, txt="mirror this text")
```
![](horizontal_mirror.png)

```python
pdf.text(x, y, txt="mirror this text")
with pdf.mirror((x, y), "NORTH"):
pdf.set_text_color(r=255, g=128, b=0)
pdf.text(x, y, txt="mirror this text")
```
![](vertical_mirror.png)

```python
prev_x, prev_y = pdf.x, pdf.y
pdf.multi_cell(w=50, txt=LOREM_IPSUM)
with pdf.mirror((pdf.x, pdf.y), "NORTHEAST"):
# Reset cursor to mirror original multi-cell
pdf.x = prev_x
pdf.y = prev_y
pdf.multi_cell(w=50, txt=LOREM_IPSUM, fill=True)
```
![](diagonal_mirror.png)
Binary file added docs/diagonal_mirror.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/horizontal_mirror.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/vertical_mirror.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions fpdf/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,18 @@ class YPos(CoerciveEnum):
"bottom page margin (end of printable area)"


class Angle(CoerciveIntEnum):
"Direction values used for mirror transformations specifying the angle of mirror line"
NORTH = 90
EAST = 0
SOUTH = 270
WEST = 180
NORTHEAST = 45
SOUTHEAST = 315
SOUTHWEST = 225
NORTHWEST = 135


class PageLayout(CoerciveEnum):
"Specify the page layout shall be used when the document is opened"

Expand Down
41 changes: 39 additions & 2 deletions fpdf/fpdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class Image:
from .enums import (
AccessPermission,
Align,
Angle,
AnnotationFlag,
AnnotationName,
CharVPos,
Expand Down Expand Up @@ -2499,7 +2500,7 @@ def rotate(self, angle, x=None, y=None):
@contextmanager
def rotation(self, angle, x=None, y=None):
"""
This method allows to perform a rotation around a given center.
Method to perform a rotation around a given center.
It must be used as a context-manager using `with`:
with rotation(angle=90, x=x, y=y):
Expand Down Expand Up @@ -2540,7 +2541,7 @@ def rotation(self, angle, x=None, y=None):
@contextmanager
def skew(self, ax=0, ay=0, x=None, y=None):
"""
This method allows to perform a skew transformation originating from a given center.
Method to perform a skew transformation originating from a given center.
It must be used as a context-manager using `with`:
with skew(ax=15, ay=15, x=x, y=y):
Expand Down Expand Up @@ -2570,6 +2571,42 @@ def skew(self, ax=0, ay=0, x=None, y=None):
)
yield

@check_page
@contextmanager
def mirror(self, origin, angle):
"""
Method to perform a reflection transformation over a given mirror line.
It must be used as a context-manager using `with`:
with mirror(origin=(15,15), angle="SOUTH"):
pdf.something()
The mirror transformation affects all elements which are rendered inside the indented
context (with the exception of clickable areas).
Args:
origin (Sequence(float, float)): a point on the mirror line
angle: (fpdf.enums.Angle): the direction of the mirror line
"""
angle = Angle.coerce(angle)
x, y = origin

try:
theta = Angle.coerce(angle).value
except ValueError:
theta = angle

a = math.cos(math.radians(theta * 2))
b = math.sin(math.radians(theta * 2))
cx, cy = x * self.k, (self.h - y) * self.k

with self.local_context():
self._out(
f"{a:.5f} {b:.5f} {b:.5f} {a*-1:.5f} {cx:.2f} {cy:.2f} cm "
f"1 0 0 1 -{cx:.2f} -{cy:.2f} cm"
)
yield

@check_page
@contextmanager
def local_context(
Expand Down
Binary file added help.pdf
Binary file not shown.
Binary file added test/mirror.pdf
Binary file not shown.
Binary file added test/mirror_cell.pdf
Binary file not shown.
Binary file added test/mirror_multi_cell.pdf
Binary file not shown.
Binary file added test/mirror_text.pdf
Binary file not shown.
170 changes: 170 additions & 0 deletions test/test_mirror.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import math
from pathlib import Path

from fpdf import FPDF
from fpdf.enums import Angle
from test.conftest import assert_pdf_equal, LOREM_IPSUM

HERE = Path(__file__).resolve().parent


def draw_mirror_line(pdf, origin, angle):
"""
A helper method which converts a given angle and origin to two co-ordinates to
then draw a line.
Used to help visualize & test mirror transformations.
Args:
pdf (fpdf.FPDF): pdf to modify
origin (Sequence[float, float]): a point on the mirror line
angle: (fpdf.enums.Angle): the direction of the mirror line
"""

angle = Angle.coerce(angle)

x, y = origin

theta = angle.value

cos_theta, sin_theta = (
math.cos(math.radians(theta)),
math.sin(math.radians(theta)) * -1,
)

x1 = x - (150 * cos_theta)
y1 = y - (150 * sin_theta)
x2 = x + (150 * cos_theta)
y2 = y + (150 * sin_theta)
pdf.line(x1=x1, y1=y1, x2=x2, y2=y2)


def test_mirror(tmp_path):
pdf = FPDF()
pdf.add_page()
pdf.set_line_width(0.5)
pdf.set_draw_color(r=255, g=128, b=0)

img_filepath = HERE / "image/png_images/66ac49ef3f48ac9482049e1ab57a53e9.png"

pdf.image(img_filepath, x=100, y=100)

pdf.image(img_filepath, x=100, y=100)
with pdf.mirror((pdf.epw / 2, pdf.eph / 2.5), "WEST"):
draw_mirror_line(pdf, (pdf.epw / 2, pdf.eph / 2.5), "WEST")
pdf.image(img_filepath, x=100, y=100)

with pdf.mirror((pdf.epw / 2.5, pdf.eph / 2), "SOUTH"):
pdf.set_draw_color(r=128, g=0, b=0)
draw_mirror_line(pdf, (pdf.epw / 2.5, pdf.eph / 2), "SOUTH")
pdf.image(img_filepath, x=100, y=100)

with pdf.mirror((pdf.epw / 1.5, pdf.eph / 1.5), "SOUTHWEST"):
pdf.set_draw_color(r=0, g=0, b=128)
draw_mirror_line(pdf, (pdf.epw / 1.5, pdf.eph / 1.5), "SOUTHWEST")
pdf.image(img_filepath, x=100, y=100)

with pdf.mirror((pdf.epw / 3, pdf.eph / 2.5), "SOUTHEAST"):
pdf.set_draw_color(r=0, g=128, b=0)
draw_mirror_line(pdf, (pdf.epw / 3, pdf.eph / 2.5), "SOUTHEAST")
pdf.image(img_filepath, x=100, y=100)

assert_pdf_equal(pdf, HERE / "mirror.pdf", tmp_path)


def test_mirror_text(tmp_path):
pdf = FPDF()
pdf.add_page()
pdf.set_font("helvetica", size=12)

pdf.text(pdf.epw / 2, pdf.epw / 2, txt="mirror this text")

with pdf.mirror((pdf.epw / 2, pdf.eph / 2.5), "EAST"):
pdf.text(pdf.epw / 2, pdf.eph / 2, txt="mirrored text E/W")

with pdf.mirror((pdf.epw / 2.5, pdf.eph / 2), "NORTH"):
pdf.text(pdf.epw / 2, pdf.eph / 2, txt="mirrored text N/S")

with pdf.mirror((pdf.epw / 1.5, pdf.eph / 1.5), "NORTHWEST"):
pdf.text(pdf.epw / 2, pdf.eph / 2, txt="mirrored text NW/SE")

with pdf.mirror((pdf.epw / 2.5, pdf.eph / 2.5), "NORTHEAST"):
pdf.text(pdf.epw / 2, pdf.eph / 2, txt="mirrored text NE/SW")

assert_pdf_equal(pdf, HERE / "mirror_text.pdf", tmp_path)


def test_mirror_cell(tmp_path):
pdf = FPDF()
pdf.add_page()
pdf.set_font("helvetica", size=12)
pdf.set_fill_color(255, 255, 0)

pdf.cell(txt="cell to be mirrored repeatedly")
pdf.x = pdf.l_margin
with pdf.mirror((pdf.epw / 2, 0), "NORTH"):
draw_mirror_line(pdf, (pdf.epw / 2, 0), "NORTH")
pdf.cell(txt="cell mirrored", fill=True)
pdf.cell(txt="cell mirrored", fill=True)
pdf.cell(txt="cell mirrored", fill=True)
pdf.ln(40)

pdf.cell(txt="cell text 1")
pdf.x = pdf.l_margin
with pdf.mirror((pdf.epw / 2, pdf.eph / 4), "EAST"):
draw_mirror_line(pdf, (pdf.epw / 2, pdf.eph / 4), "EAST")
pdf.cell(txt="cell text 1", fill=True)
pdf.ln(40)

pdf.cell(txt="cell text 2")
pdf.x = pdf.l_margin
with pdf.mirror((pdf.epw / 2, 0), "SOUTHWEST"):
draw_mirror_line(pdf, (pdf.epw / 2, 0), "SOUTHWEST")
pdf.cell(txt="cell text 2", fill=True)
pdf.ln(40)

pdf.cell(txt="cell text 3")
pdf.x = pdf.l_margin
with pdf.mirror((pdf.epw / 2, pdf.eph / 4), "NORTHEAST"):
draw_mirror_line(pdf, (pdf.epw / 2, pdf.eph / 4), "NORTHEAST")
pdf.cell(txt="cell text 3", fill=True, border=1)
pdf.ln(40)

assert_pdf_equal(pdf, HERE / "mirror_cell.pdf", tmp_path)


def test_mirror_multi_cell(tmp_path):
pdf = FPDF()
pdf.add_page()
pdf.set_font("helvetica", size=12)
pdf.set_fill_color(255, 255, 0)

pdf.multi_cell(w=50, txt=LOREM_IPSUM[:200])

pdf.x = pdf.l_margin
pdf.y = pdf.t_margin
with pdf.mirror((pdf.epw / 2, pdf.eph / 4), "NORTHEAST"):
draw_mirror_line(pdf, (pdf.epw / 2, pdf.eph / 4), "NORTHEAST")
pdf.multi_cell(w=50, txt=LOREM_IPSUM[:200], fill=True)
pdf.ln(20)

prev_y = pdf.y
pdf.multi_cell(w=100, txt=LOREM_IPSUM[:200])
pdf.x = pdf.l_margin
pdf.y = prev_y

with pdf.mirror((0, pdf.eph / 2), "EAST"):
draw_mirror_line(pdf, (0, pdf.eph / 2), "EAST")
pdf.multi_cell(w=100, txt=LOREM_IPSUM[:200], fill=True)
pdf.ln(150)

prev_y = pdf.y
pdf.multi_cell(w=120, txt=LOREM_IPSUM[:120])
pdf.x = pdf.l_margin
pdf.y = prev_y

with pdf.mirror((pdf.epw / 2, pdf.eph), "SOUTH"):
draw_mirror_line(pdf, (pdf.epw / 2, pdf.eph), "SOUTH")
pdf.multi_cell(w=120, txt=LOREM_IPSUM[:120], fill=True, border=1)

pdf.output(HERE / "mirror_multi_cell.pdf")
assert_pdf_equal(pdf, HERE / "mirror_multi_cell.pdf", tmp_path)

0 comments on commit 47e910f

Please sign in to comment.