From 5792f50619d24fd8b6a3325eefbe5681e42a1eb2 Mon Sep 17 00:00:00 2001 From: Clock-Speed <55024400+Clock-Speed@users.noreply.github.com> Date: Wed, 21 Feb 2024 04:05:39 +0000 Subject: [PATCH 1/2] Allow keep_aspect_ratio flag in templates. Add appropriate tests and update documentation. --- docs/Templates.md | 7 ++- fpdf/template.py | 16 ++++++- .../flextemplate_keep_aspect_ratio.pdf | Bin 0 -> 1901 bytes test/template/keep_aspect_ratio.csv | 2 + test/template/test_flextemplate.py | 42 ++++++++++++++++++ 5 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 test/template/flextemplate_keep_aspect_ratio.pdf create mode 100644 test/template/keep_aspect_ratio.csv diff --git a/docs/Templates.md b/docs/Templates.md index 22f609953..925913d3c 100644 --- a/docs/Templates.md +++ b/docs/Templates.md @@ -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). @@ -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}, @@ -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: diff --git a/fpdf/template.py b/fpdf/template.py index f9c00fc91..9dcae775b 100644 --- a/fpdf/template.py +++ b/fpdf/template.py @@ -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 @@ -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: @@ -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, diff --git a/test/template/flextemplate_keep_aspect_ratio.pdf b/test/template/flextemplate_keep_aspect_ratio.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e9fd394b8b7ebf60feec2e94b2a2128e05650016 GIT binary patch literal 1901 zcmah~O>7%Q6i%CvI4ja@T7Ft9Eo0I&y*uH;^bwRmmU$6%_(5h^nR+4zv-1MudhL+l$wBB}OaF zyzhJS=FK-dZ+a(%;7K^(pdes@GIx&hdMSoiQnC&p#f(ZZ4a_(&6*)^Wr%_B2{Yo0l zuo!W1t^vCTc${#+!(n!MDTx3xDJ~$58XBUI93$cErmz!9B>SSeq{x++fC)-^2*{~K z0tX;d$HXNir2}R{M!b?pEy`tB10=nOu?R2fVnSIkEkjbQW-seV)-~WP&zTfeY#I?F zNK;a36luV14oMa_#UoRVctI(lBNgYG5OpM=s1idIGlt{^Jq{pb$t+D*k+?{uH&gFG z>IO@<{Qc(5x4>_oUApqg@?f}cx8?44hp&G5S5Fd!ew=@19UWy}y7$G%HwQjEs)`?M z^xuBkZSA?!^;Q4FYd(Eu_MfZIe*DMs?cdv`rC)zo zJu&N6j{iv{t}I*;+hxkyFX`GOQh8-DsmP`MlXFcd>QqvU;)4><>rxbrsNxcZ$7EAj zr$r5Ty^KF0MqdkS5a?Po@F~creJD=ab zVp!(>K_TwBYXXe;o!3-8tv#OhwpJT##@tv7NJuMj#M2d`c+;f4PG!ujkk zx5nwqSrc3u<`P^-8_qUP^S-gw%t9T%o9*grU5i-Tw?fYDY*!syGPePEZ(sY?p^Qk= zn@?SDaM|6oVZwSGTwKrOEkw>*yD4f)6cb!WsdmZQ<*k^k5@ zy_y-`sHM6Nb~MGDSfpH>>)3y)h6F!-evsa=EH$^+z}UF7c9|q<%Rd<#J~ms&ZquFJ znc&Tp8hCdtcf``_yEamTVxwq1zz>&B5Q}%Jo-NxCi%yHJgi=+mUlZMUqe+c2>d>UBM5mArW*A(v0W*!#xKNfZjK9+Jl|GECY31^e`y7UV zRE7w?Jou~Bia}Fk9#=0?mIYkkzzf2QkaKg;?tqYEaXSOKK9=q4H%(5fXr5w$ha!Br z0k_NLaDjPH#W+x+fLz5mydly|7~~rL?1PP%(~aMN+PTo_Xz+tBy9v`(QA!}S^r}rs z%Lu28ifJi~c0+{jod=57Z2!e}qI6XojAaE=U1P48C-X-|4E<^wC WakgBgL{%@z1YI0U_4WoPg4Dn6#BnD8 literal 0 HcmV?d00001 diff --git a/test/template/keep_aspect_ratio.csv b/test/template/keep_aspect_ratio.csv new file mode 100644 index 000000000..84c71672e --- /dev/null +++ b/test/template/keep_aspect_ratio.csv @@ -0,0 +1,2 @@ +img;I;0;0;50;25;;;;;;;;image;;;;;1 +box;B;0;0;50;25;;;;;;;;box;;;;; \ No newline at end of file diff --git a/test/template/test_flextemplate.py b/test/template/test_flextemplate.py index fb9c66c3c..5a93125e9 100644 --- a/test/template/test_flextemplate.py +++ b/test/template/test_flextemplate.py @@ -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) From 4b0d537cc24ca51858206e3824b46be8f63c597b Mon Sep 17 00:00:00 2001 From: Clock-Speed <55024400+Clock-Speed@users.noreply.github.com> Date: Wed, 21 Feb 2024 07:59:51 +0000 Subject: [PATCH 2/2] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a12134366..fd8c56a6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ This can also be enabled programmatically with `warnings.simplefilter('default', ## [2.7.9] - Not released yet ### Added * support for overriding paragraph direction on bidirectional text +* support for keeping aspect ratio for images in templates ### Fixed ### Changed