Skip to content

Commit

Permalink
feat(image): allow images to keep their original aspect ratio
Browse files Browse the repository at this point in the history
  • Loading branch information
anehx committed Jun 18, 2024
1 parent 8311f89 commit 05ade2b
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 4 deletions.
2 changes: 1 addition & 1 deletion CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ In python-docx-template following additional custom filters are implemented:
- timeformat(value, format, locale)
- getwithdefault(value, default) - converts None to empty string (or provided default value) or leaves strings as is
- emptystring(value) - converts None to empty string or leaves strings as is (deprecated in favor of getwithdefault)
- image(width, height) - Creates an [inline image](https://docxtpl.readthedocs.io/en/latest/) from provided file with the same name. `width` and `height` are optional and represent millimetres.
- image(width, height, keep_aspect_ratio) - Creates an [inline image](https://docxtpl.readthedocs.io/en/latest/) from provided file with the same name. `width` and `height` are optional and represent millimetres. If `keep_aspect_ratio` is `True` the image will be scaled keeping it's original aspect ratio and the width/height parameters become a size limit instead. `keep_aspect_ration` has no effect if `width` and `height` are not given.

For formatting use babel and its uniode compatible [format](http://babel.pocoo.org/en/latest/dates.html#date-fields).

Expand Down
19 changes: 18 additions & 1 deletion document_merge_service/api/jinja.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from docxtpl import InlineImage, Listing
from jinja2 import pass_context
from jinja2.sandbox import SandboxedEnvironment
from PIL import Image
from rest_framework.exceptions import ValidationError


Expand Down Expand Up @@ -65,7 +66,7 @@ def multiline(value):


@pass_context
def image(ctx, img_name, width=None, height=None):
def image(ctx, img_name, width=None, height=None, keep_aspect_ratio=False):
tpl = ctx["_tpl"]

if img_name not in ctx:
Expand All @@ -83,6 +84,11 @@ def image(ctx, img_name, width=None, height=None):

width = Mm(width) if width else None
height = Mm(height) if height else None

if width and height and keep_aspect_ratio:
w, h = Image.open(img).size
width, height = get_size_with_aspect_ratio(width, height, w / h)

return InlineImage(tpl, img, width=width, height=height)


Expand All @@ -102,3 +108,14 @@ def get_jinja_env():
jinja_env = SandboxedEnvironment(extensions=settings.DOCXTEMPLATE_JINJA_EXTENSIONS)
jinja_env.filters.update(get_jinja_filters())
return jinja_env


def get_size_with_aspect_ratio(width, height, aspect_ratio):
tpl_aspect_ratio = width / height

if tpl_aspect_ratio >= aspect_ratio:
width = height * aspect_ratio
else:
height = width / aspect_ratio

return width, height
40 changes: 39 additions & 1 deletion document_merge_service/api/tests/test_jinja.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import pytest
from docx.shared import Mm

from ..jinja import dateformat, datetimeformat, emptystring, getwithdefault, timeformat
from document_merge_service.api.data import django_file

from ..jinja import (
dateformat,
datetimeformat,
emptystring,
getwithdefault,
image,
timeformat,
)


@pytest.mark.parametrize(
Expand Down Expand Up @@ -41,3 +51,31 @@ def test_emptystring(inp, expected):
def test_getwithdefault(inp, default, expected):
formatted = getwithdefault(inp, default=default)
assert formatted == expected


@pytest.mark.parametrize(
"width,height,keep_aspect_ratio,expected_size",
[
(20, 10, False, (20, 10)),
(20, 10, True, (10, 10)),
(10, 20, True, (10, 10)),
(10, None, False, (10, None)),
(None, 10, False, (None, 10)),
(10, None, True, (10, None)),
(None, 10, True, (None, 10)),
],
)
def test_image(width, height, keep_aspect_ratio, expected_size):
# The size of "black.png" is 32x32 pixels which is an aspect ratio of 1
inline_image = image(
{"_tpl": None, "black.png": django_file("black.png").file},
"black.png",
width,
height,
keep_aspect_ratio,
)

w, h = expected_size

assert inline_image.width == (Mm(w) if w else None)
assert inline_image.height == (Mm(h) if h else None)
88 changes: 87 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ docx-mailmerge = "^0.5.0"
docxtpl = "^0.17.0"
Jinja2 = "^3.1.4"
mysqlclient = { version = "^2.2.4", optional = true }
pillow = "^10.3.0"
psycopg = { version = "^3.1.19", optional = true, extras = ["binary"] }
pymemcache = { version = "^4.0.0", optional = true }
python-dateutil = "^2.9.0"
Expand Down

0 comments on commit 05ade2b

Please sign in to comment.