mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-06-01 19:53:19 +03:00
Support ttb multiline text
This commit is contained in:
parent
b57b4e5f2c
commit
f056c259a7
BIN
Tests/images/test_combine_multiline_ttb.png
Normal file
BIN
Tests/images/test_combine_multiline_ttb.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
|
@ -4,7 +4,11 @@ import pytest
|
|||
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
from .helper import assert_image_similar_tofile, skip_unless_feature
|
||||
from .helper import (
|
||||
assert_image_equal_tofile,
|
||||
assert_image_similar_tofile,
|
||||
skip_unless_feature,
|
||||
)
|
||||
|
||||
FONT_SIZE = 20
|
||||
FONT_PATH = "Tests/fonts/DejaVuSans/DejaVuSans.ttf"
|
||||
|
@ -354,11 +358,27 @@ def test_combine_multiline(anchor: str, align: str) -> None:
|
|||
d.line(((200, 0), (200, 400)), "gray")
|
||||
bbox = d.multiline_textbbox((200, 200), text, anchor=anchor, font=f, align=align)
|
||||
d.rectangle(bbox, outline="red")
|
||||
d.multiline_text((200, 200), text, fill="black", anchor=anchor, font=f, align=align)
|
||||
d.multiline_text((200, 200), text, "black", anchor=anchor, font=f, align=align)
|
||||
|
||||
assert_image_similar_tofile(im, path, 0.015)
|
||||
|
||||
|
||||
def test_combine_multiline_ttb() -> None:
|
||||
path = "Tests/images/test_combine_multiline_ttb.png"
|
||||
f = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 48)
|
||||
text = "te\nxt"
|
||||
|
||||
im = Image.new("RGB", (400, 400), "white")
|
||||
d = ImageDraw.Draw(im)
|
||||
d.line(((0, 200), (400, 200)), "gray")
|
||||
d.line(((200, 0), (200, 400)), "gray")
|
||||
bbox = d.multiline_textbbox((200, 200), text, f, direction="ttb")
|
||||
d.rectangle(bbox, outline="red")
|
||||
d.multiline_text((200, 200), text, "black", f, direction="ttb")
|
||||
|
||||
assert_image_equal_tofile(im, path)
|
||||
|
||||
|
||||
def test_anchor_invalid_ttb() -> None:
|
||||
font = ImageFont.truetype(FONT_PATH, FONT_SIZE)
|
||||
im = Image.new("RGB", (100, 100), "white")
|
||||
|
@ -378,8 +398,3 @@ def test_anchor_invalid_ttb() -> None:
|
|||
d.multiline_text((0, 0), "foo\nbar", anchor=anchor, direction="ttb")
|
||||
with pytest.raises(ValueError):
|
||||
d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor, direction="ttb")
|
||||
# ttb multiline text does not support anchors at all
|
||||
with pytest.raises(ValueError):
|
||||
d.multiline_text((0, 0), "foo\nbar", anchor="mm", direction="ttb")
|
||||
with pytest.raises(ValueError):
|
||||
d.multiline_textbbox((0, 0), "foo\nbar", anchor="mm", direction="ttb")
|
||||
|
|
|
@ -708,24 +708,18 @@ class ImageDraw:
|
|||
str,
|
||||
list[tuple[tuple[float, float], AnyStr]],
|
||||
]:
|
||||
if direction == "ttb":
|
||||
msg = "ttb direction is unsupported for multiline text"
|
||||
raise ValueError(msg)
|
||||
|
||||
if anchor is None:
|
||||
anchor = "la"
|
||||
anchor = "lt" if direction == "ttb" else "la"
|
||||
elif len(anchor) != 2:
|
||||
msg = "anchor must be a 2 character string"
|
||||
raise ValueError(msg)
|
||||
elif anchor[1] in "tb":
|
||||
elif anchor[1] in "tb" and direction != "ttb":
|
||||
msg = "anchor not supported for multiline text"
|
||||
raise ValueError(msg)
|
||||
|
||||
if font is None:
|
||||
font = self._getfont(font_size)
|
||||
|
||||
widths = []
|
||||
max_width: float = 0
|
||||
lines = text.split("\n" if isinstance(text, str) else b"\n")
|
||||
line_spacing = (
|
||||
self.textbbox((0, 0), "A", font, stroke_width=stroke_width)[3]
|
||||
|
@ -733,67 +727,75 @@ class ImageDraw:
|
|||
+ spacing
|
||||
)
|
||||
|
||||
for line in lines:
|
||||
line_width = self.textlength(
|
||||
line,
|
||||
font,
|
||||
direction=direction,
|
||||
features=features,
|
||||
language=language,
|
||||
embedded_color=embedded_color,
|
||||
)
|
||||
widths.append(line_width)
|
||||
max_width = max(max_width, line_width)
|
||||
|
||||
top = xy[1]
|
||||
if anchor[1] == "m":
|
||||
top -= (len(lines) - 1) * line_spacing / 2.0
|
||||
elif anchor[1] == "d":
|
||||
top -= (len(lines) - 1) * line_spacing
|
||||
|
||||
parts = []
|
||||
for idx, line in enumerate(lines):
|
||||
if direction == "ttb":
|
||||
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
|
||||
if align in ("left", "justify"):
|
||||
pass
|
||||
elif align == "center":
|
||||
left += width_difference / 2.0
|
||||
elif align == "right":
|
||||
left += width_difference
|
||||
else:
|
||||
msg = 'align must be "left", "center", "right" or "justify"'
|
||||
raise ValueError(msg)
|
||||
|
||||
if align == "justify" and width_difference != 0:
|
||||
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:
|
||||
for line in lines:
|
||||
parts.append(((left, top), line))
|
||||
left += line_spacing
|
||||
else:
|
||||
widths = []
|
||||
max_width: float = 0
|
||||
for line in lines:
|
||||
line_width = self.textlength(
|
||||
line,
|
||||
font,
|
||||
direction=direction,
|
||||
features=features,
|
||||
language=language,
|
||||
embedded_color=embedded_color,
|
||||
)
|
||||
widths.append(line_width)
|
||||
max_width = max(max_width, line_width)
|
||||
|
||||
top += line_spacing
|
||||
if anchor[1] == "m":
|
||||
top -= (len(lines) - 1) * line_spacing / 2.0
|
||||
elif anchor[1] == "d":
|
||||
top -= (len(lines) - 1) * line_spacing
|
||||
|
||||
for idx, line in enumerate(lines):
|
||||
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
|
||||
if align in ("left", "justify"):
|
||||
pass
|
||||
elif align == "center":
|
||||
left += width_difference / 2.0
|
||||
elif align == "right":
|
||||
left += width_difference
|
||||
else:
|
||||
msg = 'align must be "left", "center", "right" or "justify"'
|
||||
raise ValueError(msg)
|
||||
|
||||
if align == "justify" and width_difference != 0:
|
||||
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))
|
||||
|
||||
top += line_spacing
|
||||
|
||||
return font, anchor, parts
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user