Skip to content

Commit c96b277

Browse files
authored
Improve justifying text (#8905)
2 parents ba37249 + bc05a88 commit c96b277

File tree

3 files changed

+56
-32
lines changed

3 files changed

+56
-32
lines changed
11 KB
Loading

Tests/test_imagefont.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,23 @@ def test_render_multiline_text_align(
267267
assert_image_similar_tofile(im, f"Tests/images/multiline_text{ext}.png", 0.01)
268268

269269

270+
def test_render_multiline_text_justify_anchor(
271+
font: ImageFont.FreeTypeFont,
272+
) -> None:
273+
im = Image.new("RGB", (280, 240))
274+
draw = ImageDraw.Draw(im)
275+
for xy, anchor in (((0, 0), "la"), ((140, 80), "ma"), ((280, 160), "ra")):
276+
draw.multiline_text(
277+
xy,
278+
"hey you you are awesome\nthis looks awkward\nthis\nlooks awkward",
279+
font=font,
280+
anchor=anchor,
281+
align="justify",
282+
)
283+
284+
assert_image_equal_tofile(im, "Tests/images/multiline_text_justify_anchor.png")
285+
286+
270287
def test_unknown_align(font: ImageFont.FreeTypeFont) -> None:
271288
im = Image.new(mode="RGB", size=(300, 100))
272289
draw = ImageDraw.Draw(im)

src/PIL/ImageDraw.py

Lines changed: 39 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -690,8 +690,7 @@ def _prepare_multiline_text(
690690
font_size: float | None,
691691
) -> tuple[
692692
ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont,
693-
str,
694-
list[tuple[tuple[float, float], AnyStr]],
693+
list[tuple[tuple[float, float], str, AnyStr]],
695694
]:
696695
if direction == "ttb":
697696
msg = "ttb direction is unsupported for multiline text"
@@ -741,13 +740,7 @@ def _prepare_multiline_text(
741740
left = xy[0]
742741
width_difference = max_width - widths[idx]
743742

744-
# first align left by anchor
745-
if anchor[0] == "m":
746-
left -= width_difference / 2.0
747-
elif anchor[0] == "r":
748-
left -= width_difference
749-
750-
# then align by align parameter
743+
# align by align parameter
751744
if align in ("left", "justify"):
752745
pass
753746
elif align == "center":
@@ -758,29 +751,43 @@ def _prepare_multiline_text(
758751
msg = 'align must be "left", "center", "right" or "justify"'
759752
raise ValueError(msg)
760753

761-
if align == "justify" and width_difference != 0:
754+
if align == "justify" and width_difference != 0 and idx != len(lines) - 1:
762755
words = line.split(" " if isinstance(text, str) else b" ")
763-
word_widths = [
764-
self.textlength(
765-
word,
766-
font,
767-
direction=direction,
768-
features=features,
769-
language=language,
770-
embedded_color=embedded_color,
771-
)
772-
for word in words
773-
]
774-
width_difference = max_width - sum(word_widths)
775-
for i, word in enumerate(words):
776-
parts.append(((left, top), word))
777-
left += word_widths[i] + width_difference / (len(words) - 1)
778-
else:
779-
parts.append(((left, top), line))
756+
if len(words) > 1:
757+
# align left by anchor
758+
if anchor[0] == "m":
759+
left -= max_width / 2.0
760+
elif anchor[0] == "r":
761+
left -= max_width
762+
763+
word_widths = [
764+
self.textlength(
765+
word,
766+
font,
767+
direction=direction,
768+
features=features,
769+
language=language,
770+
embedded_color=embedded_color,
771+
)
772+
for word in words
773+
]
774+
word_anchor = "l" + anchor[1]
775+
width_difference = max_width - sum(word_widths)
776+
for i, word in enumerate(words):
777+
parts.append(((left, top), word_anchor, word))
778+
left += word_widths[i] + width_difference / (len(words) - 1)
779+
top += line_spacing
780+
continue
780781

782+
# align left by anchor
783+
if anchor[0] == "m":
784+
left -= width_difference / 2.0
785+
elif anchor[0] == "r":
786+
left -= width_difference
787+
parts.append(((left, top), anchor, line))
781788
top += line_spacing
782789

783-
return font, anchor, parts
790+
return font, parts
784791

785792
def multiline_text(
786793
self,
@@ -805,7 +812,7 @@ def multiline_text(
805812
*,
806813
font_size: float | None = None,
807814
) -> None:
808-
font, anchor, lines = self._prepare_multiline_text(
815+
font, lines = self._prepare_multiline_text(
809816
xy,
810817
text,
811818
font,
@@ -820,7 +827,7 @@ def multiline_text(
820827
font_size,
821828
)
822829

823-
for xy, line in lines:
830+
for xy, anchor, line in lines:
824831
self.text(
825832
xy,
826833
line,
@@ -935,7 +942,7 @@ def multiline_textbbox(
935942
*,
936943
font_size: float | None = None,
937944
) -> tuple[float, float, float, float]:
938-
font, anchor, lines = self._prepare_multiline_text(
945+
font, lines = self._prepare_multiline_text(
939946
xy,
940947
text,
941948
font,
@@ -952,7 +959,7 @@ def multiline_textbbox(
952959

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

955-
for xy, line in lines:
962+
for xy, anchor, line in lines:
956963
bbox_line = self.textbbox(
957964
xy,
958965
line,

0 commit comments

Comments
 (0)