Skip to content

Commit

Permalink
Allow keep_aspect_ratio flag in templates.
Browse files Browse the repository at this point in the history
Add appropriate tests and update documentation.
  • Loading branch information
Clock-Speed committed Feb 21, 2024
1 parent 5553aee commit 5792f50
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 4 deletions.
7 changes: 5 additions & 2 deletions docs/Templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,9 @@ Dimensions (except font size, which always uses points) are given in user define
* __rotation__: rotate the element in degrees around the top left corner x1/y1 (float)
* _optional_
* default: 0.0 - no rotation
* __keep\_aspect\_ratio__: keeps the aspect ratio when scaling image to fit inside the bounds. See [Fitting an image inside a rectange](https://py-pdf.github.io/fpdf2/Images.html#fitting-an-image-inside-a-rectangle) for more details on how this works.
* _optional_
* default: false

Fields that are not relevant to a specific element type will be ignored there, but if not left empty, they must still adhere to the specified data type (in dicts, string fields may be None).

Expand All @@ -231,7 +234,7 @@ from fpdf import Template

#this will define the ELEMENTS that will compose the template.
elements = [
{ 'name': 'company_logo', 'type': 'I', 'x1': 20.0, 'y1': 17.0, 'x2': 78.0, 'y2': 30.0, 'font': None, 'size': 0.0, 'bold': 0, 'italic': 0, 'underline': 0, 'align': 'C', 'text': 'logo', 'priority': 2, 'multiline': False},
{ 'name': 'company_logo', 'type': 'I', 'x1': 20.0, 'y1': 17.0, 'x2': 78.0, 'y2': 30.0, 'font': None, 'size': 0.0, 'bold': 0, 'italic': 0, 'underline': 0, 'align': 'C', 'text': 'logo', 'priority': 2, 'multiline': False, 'keep_aspect_ratio': False},
{ 'name': 'company_name', 'type': 'T', 'x1': 17.0, 'y1': 32.5, 'x2': 115.0, 'y2': 37.5, 'font': 'helvetica', 'size': 12.0, 'bold': 1, 'italic': 0, 'underline': 0,'align': 'C', 'text': '', 'priority': 2, 'multiline': False},
{ 'name': 'multline_text', 'type': 'T', 'x1': 20, 'y1': 100, 'x2': 40, 'y2': 105, 'font': 'helvetica', 'size': 12, 'bold': 0, 'italic': 0, 'underline': 0, 'background': 0x88ff00, 'align': 'C', 'text': 'Lorem ipsum dolor sit amet, consectetur adipisici elit', 'priority': 2, 'multiline': True},
{ 'name': 'box', 'type': 'B', 'x1': 15.0, 'y1': 15.0, 'x2': 185.0, 'y2': 260.0, 'font': 'helvetica', 'size': 0.0, 'bold': 0, 'italic': 0, 'underline': 0, 'align': 'C', 'text': None, 'priority': 0, 'multiline': False},
Expand Down Expand Up @@ -272,7 +275,7 @@ rotated;T;21.0;80.0;100.0;84.0;times;10.5;0;0;0;0;;R;ROTATED;0;0;30.0
```

Remember that each line represents an element and each field represents one of the properties of the element in the following order:
('name','type','x1','y1','x2','y2','font','size','bold','italic','underline','foreground','background','align','text','priority', 'multiline', 'rotate')
('name','type','x1','y1','x2','y2','font','size','bold','italic','underline','foreground','background','align','text','priority', 'multiline', 'rotate', 'keep_aspect_ratio')
As noted above, most fields may be left empty, so a line is valid with only 6 items. The "empty_fields" line of the example demonstrates all that can be left away. In addition, for the barcode types "x2" may be empty.

Then you can use the file like this:
Expand Down
16 changes: 14 additions & 2 deletions fpdf/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ def load_elements(self, elements):
"priority": int,
"multiline": (bool, type(None)),
"rotate": (int, float),
"keep_aspect_ratio": object, # "bool or equivalent",
}

self.elements = elements
Expand Down Expand Up @@ -190,6 +191,7 @@ def _varsep_float(s, default="0"):
("priority", int, False),
("multiline", self._parse_multiline, False),
("rotate", _varsep_float, False),
("keep_aspect_ratio", int, False),
)
self.elements = []
if encoding is None:
Expand Down Expand Up @@ -423,9 +425,19 @@ def _ellipse(
pdf.set_line_width(size * scale)
pdf.ellipse(x1, y1, x2 - x1, y2 - y1, style=style)

def _image(self, *_, x1=0, y1=0, x2=0, y2=0, text="", **__):
def _image(
self, *_, x1=0, y1=0, x2=0, y2=0, text="", keep_aspect_ratio=False, **__
):
if text:
self.pdf.image(text, x1, y1, w=x2 - x1, h=y2 - y1, link="")
self.pdf.image(
text,
x1,
y1,
w=x2 - x1,
h=y2 - y1,
link="",
keep_aspect_ratio=keep_aspect_ratio,
)

def _barcode(
self,
Expand Down
Binary file added test/template/flextemplate_keep_aspect_ratio.pdf
Binary file not shown.
2 changes: 2 additions & 0 deletions test/template/keep_aspect_ratio.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
img;I;0;0;50;25;;;;;;;;image;;;;;1
box;B;0;0;50;25;;;;;;;;box;;;;;
42 changes: 42 additions & 0 deletions test/template/test_flextemplate.py
Original file line number Diff line number Diff line change
Expand Up @@ -508,3 +508,45 @@ def test_flextemplate_leak(tmp_path): # issue #570
pdf.ln()
pdf.cell(text="after", new_x="LEFT", new_y="NEXT")
assert_pdf_equal(pdf, HERE / "flextemplate_leak.pdf", tmp_path)


def test_flextemplate_keep_aspect_ratio(tmp_path):
"""
Tries to render a square image inside a rectangle with keep_aspect_ratio=True.
The image should be fit the rectangle but should remain a square.
"""

tmpl = [
{
"name": "box",
"type": "B",
"x1": 0,
"y1": 0,
"x2": 50,
"y2": 25,
},
{
"name": "img",
"type": "I",
"x1": 0,
"y1": 0,
"x2": 50,
"y2": 25,
"keep_aspect_ratio": True,
},
]

img = qrcode.make("Test keep_aspect_ratio").get_image()

pdf = FPDF()
pdf.add_page()
templ1 = FlexTemplate(pdf, tmpl)
templ1["img"] = img
templ1.render(offsetx=20, offsety=20)

templ2 = FlexTemplate(pdf)
templ2.parse_csv(HERE / "keep_aspect_ratio.csv", delimiter=";")
templ2["img"] = img
templ2.render(offsetx=20, offsety=50)

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

0 comments on commit 5792f50

Please sign in to comment.