Skip to content

Commit fb90f95

Browse files
committed
Added wrap()
1 parent b59e031 commit fb90f95

File tree

2 files changed

+83
-0
lines changed

2 files changed

+83
-0
lines changed

Tests/test_imagetext.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,20 @@ def test_stroke() -> None:
8181
assert_image_similar_tofile(
8282
im, "Tests/images/imagedraw_stroke_" + suffix + ".png", 3.1
8383
)
84+
85+
86+
def test_wrap() -> None:
87+
# No wrap required
88+
text = ImageText.Text("Hello World!")
89+
text.wrap(100)
90+
assert text.text == "Hello World!"
91+
92+
# Wrap word to a new line
93+
text = ImageText.Text("Hello World!")
94+
text.wrap(50)
95+
assert text.text == "Hello\nWorld!"
96+
97+
# Split word across lines
98+
text = ImageText.Text("Hello World!")
99+
text.wrap(25)
100+
assert text.text == "Hello\nWorl\nd!"

src/PIL/ImageText.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from __future__ import annotations
22

3+
from typing import cast
4+
35
from . import ImageFont
46
from ._typing import _Ink
57

@@ -88,6 +90,70 @@ def _get_fontmode(self) -> str:
8890
else:
8991
return "L"
9092

93+
def wrap(self, width: int) -> None:
94+
str_type = isinstance(self.text, str)
95+
wrapped_lines = []
96+
emptystring = "" if str_type else b""
97+
fontmode = self._get_fontmode()
98+
for line in self.text.splitlines():
99+
wrapped_line = emptystring
100+
words = line.split()
101+
while words:
102+
word = words[0]
103+
104+
new_wrapped_line: str | bytes
105+
if wrapped_line:
106+
if str_type:
107+
new_wrapped_line = (
108+
cast(str, wrapped_line) + " " + cast(str, word)
109+
)
110+
else:
111+
new_wrapped_line = (
112+
cast(bytes, wrapped_line) + b" " + cast(bytes, word)
113+
)
114+
else:
115+
new_wrapped_line = word
116+
117+
def get_width(text) -> float:
118+
left, _, right, _ = self.font.getbbox(
119+
text,
120+
fontmode,
121+
self.direction,
122+
self.features,
123+
self.language,
124+
self.stroke_width,
125+
)
126+
return right - left
127+
128+
if get_width(new_wrapped_line) > width:
129+
if wrapped_line:
130+
wrapped_lines.append(wrapped_line)
131+
wrapped_line = emptystring
132+
else:
133+
# This word is too long for a single line, so split the word
134+
characters = word
135+
i = len(characters)
136+
while i > 1 and get_width(characters[:i]) > width:
137+
i -= 1
138+
wrapped_line = characters[:i]
139+
if str_type:
140+
cast(list[str], words)[0] = cast(str, characters[i:])
141+
else:
142+
cast(list[bytes], words)[0] = cast(bytes, characters[i:])
143+
else:
144+
words.pop(0)
145+
wrapped_line = new_wrapped_line
146+
if wrapped_line:
147+
wrapped_lines.append(wrapped_line)
148+
if str_type:
149+
self.text = "\n".join(
150+
[line for line in wrapped_lines if isinstance(line, str)]
151+
)
152+
else:
153+
self.text = b"\n".join(
154+
[line for line in wrapped_lines if isinstance(line, bytes)]
155+
)
156+
91157
def get_length(self) -> float:
92158
"""
93159
Returns length (in pixels with 1/64 precision) of text.

0 commit comments

Comments
 (0)