Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added Tests/images/multiline_text_justify_anchor.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions Tests/test_imagefont.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,23 @@ def test_render_multiline_text_align(
assert_image_similar_tofile(im, f"Tests/images/multiline_text{ext}.png", 0.01)


def test_render_multiline_text_justify_anchor(
font: ImageFont.FreeTypeFont,
) -> None:
im = Image.new("RGB", (280, 240))
draw = ImageDraw.Draw(im)
for xy, anchor in (((0, 0), "la"), ((140, 80), "ma"), ((280, 160), "ra")):
draw.multiline_text(
xy,
"hey you you are awesome\nthis looks awkward\nthis\nlooks awkward",
font=font,
anchor=anchor,
align="justify",
)

assert_image_equal_tofile(im, "Tests/images/multiline_text_justify_anchor.png")


def test_unknown_align(font: ImageFont.FreeTypeFont) -> None:
im = Image.new(mode="RGB", size=(300, 100))
draw = ImageDraw.Draw(im)
Expand Down
71 changes: 39 additions & 32 deletions src/PIL/ImageDraw.py
Original file line number Diff line number Diff line change
Expand Up @@ -702,8 +702,7 @@ def _prepare_multiline_text(
font_size: float | None,
) -> tuple[
ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont,
str,
list[tuple[tuple[float, float], AnyStr]],
list[tuple[tuple[float, float], str, AnyStr]],
]:
if direction == "ttb":
msg = "ttb direction is unsupported for multiline text"
Expand Down Expand Up @@ -753,13 +752,7 @@ def _prepare_multiline_text(
left = xy[0]
width_difference = max_width - widths[idx]

# first align left by anchor
if anchor[0] == "m":
left -= width_difference / 2.0
elif anchor[0] == "r":
left -= width_difference

# then align by align parameter
# align by align parameter
if align in ("left", "justify"):
pass
elif align == "center":
Expand All @@ -770,29 +763,43 @@ def _prepare_multiline_text(
msg = 'align must be "left", "center", "right" or "justify"'
raise ValueError(msg)

if align == "justify" and width_difference != 0:
if align == "justify" and width_difference != 0 and idx != len(lines) - 1:
words = line.split(" " if isinstance(text, str) else b" ")
word_widths = [
self.textlength(
word,
font,
direction=direction,
features=features,
language=language,
embedded_color=embedded_color,
)
for word in words
]
width_difference = max_width - sum(word_widths)
for i, word in enumerate(words):
parts.append(((left, top), word))
left += word_widths[i] + width_difference / (len(words) - 1)
else:
parts.append(((left, top), line))
if len(words) > 1:
# align left by anchor
if anchor[0] == "m":
left -= max_width / 2.0
elif anchor[0] == "r":
left -= max_width

word_widths = [
self.textlength(
word,
font,
direction=direction,
features=features,
language=language,
embedded_color=embedded_color,
)
for word in words
]
word_anchor = "l" + anchor[1]
width_difference = max_width - sum(word_widths)
for i, word in enumerate(words):
parts.append(((left, top), word_anchor, word))
left += word_widths[i] + width_difference / (len(words) - 1)
top += line_spacing
continue

# align left by anchor
if anchor[0] == "m":
left -= width_difference / 2.0
elif anchor[0] == "r":
left -= width_difference
parts.append(((left, top), anchor, line))
top += line_spacing

return font, anchor, parts
return font, parts

def multiline_text(
self,
Expand All @@ -817,7 +824,7 @@ def multiline_text(
*,
font_size: float | None = None,
) -> None:
font, anchor, lines = self._prepare_multiline_text(
font, lines = self._prepare_multiline_text(
xy,
text,
font,
Expand All @@ -832,7 +839,7 @@ def multiline_text(
font_size,
)

for xy, line in lines:
for xy, anchor, line in lines:
self.text(
xy,
line,
Expand Down Expand Up @@ -947,7 +954,7 @@ def multiline_textbbox(
*,
font_size: float | None = None,
) -> tuple[float, float, float, float]:
font, anchor, lines = self._prepare_multiline_text(
font, lines = self._prepare_multiline_text(
xy,
text,
font,
Expand All @@ -964,7 +971,7 @@ def multiline_textbbox(

bbox: tuple[float, float, float, float] | None = None

for xy, line in lines:
for xy, anchor, line in lines:
bbox_line = self.textbbox(
xy,
line,
Expand Down
Loading