Last active
December 27, 2022 11:05
-
-
Save ZechCodes/c531080710f1714011ad0bdb013fb4f1 to your computer and use it in GitHub Desktop.
Simple class for writing multiline textcentered on a PIL image with even linespacing.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
PIL Line Writer | |
Simple class for writing multiline text centered on an image with even line | |
spacing. | |
To use create a line writer object by passing a PIL image object and a padding | |
value. Then to write text call the LineWriter's text method with the text you | |
would like centered on the image over multiple lines. It also takes an optional | |
line height argument which will scale the line heights. Additionally you can | |
pass any keyword settings that are valid for ImageDraw.draw.text. | |
Example: | |
image = Image.new("RGBA", (300, 300), "yellow") | |
writer = LineWriter(image, 60) | |
writer.text( | |
"To the inspiration of necessity, we owe half the wise, beautiful, and useful blessings of the world.", | |
1.5, | |
fill="black" | |
) | |
image.show() | |
""" | |
from PIL import Image, ImageDraw | |
class LineWriter: | |
""" Simple class for writing multiline text centered on a PIL image with | |
even line spacing. """ | |
def __init__(self, image: Image.Image, padding: int, offset_x: int = 0, offset_y: int = 0): | |
self._image = image | |
self._draw = ImageDraw.Draw(image) | |
self._padding = padding | |
self._last_line_y = 0 | |
self._offset_x = offset_x | |
self._offset_y = offset_y | |
@property | |
def height(self) -> int: | |
""" Height of the image. """ | |
return self._image.size[1] | |
@property | |
def last_line_y(self) -> int: | |
""" Gives the y position after the last line drawn. """ | |
return self._last_line_y | |
@property | |
def width(self) -> int: | |
""" Width of the image. """ | |
return self._image.size[0] | |
def line_height(self, font) -> int: | |
""" Height of our standard line. """ | |
return self._draw.textsize("hj", font=font)[0] | |
def text(self, message: str, line_height: float=1.0, **settings): | |
""" Writes the text centered on the image. Takes a line height argument | |
for scaling the line height and also takes all valid keywords for | |
ImageDraw.Draw.text. """ | |
lines = self._build_lines(message, settings.get("font")) | |
height = self.line_height(settings.get("font")) * line_height | |
self._last_line_y = (self.height - len(lines) * height) // 2 | |
for line in lines: | |
width, _ = self._draw.textsize(line, settings.get("font")) | |
self._draw.text( | |
( | |
(self.width - width) // 2 + self._offset_x, | |
self.last_line_y + self._offset_y | |
), | |
line, | |
**settings | |
) | |
self._last_line_y += height | |
def _build_lines(self, message: str, font): | |
""" Breaks a line of text up into a series of lines that fit onto the | |
image with the correct padding. """ | |
lines = [] | |
line = [] | |
for word in message.split(" "): | |
test_line = list(line) | |
test_line.append(word) | |
width, _ = self._draw.textsize(" ".join(test_line), font=font) | |
if width > self.width - self._padding * 2: | |
lines.append(" ".join(line)) | |
line = [word] | |
else: | |
line = test_line | |
if line: | |
lines.append(" ".join(line)) | |
return lines | |
if __name__ == "__main__": | |
image = Image.new("RGBA", (300, 300), "yellow") | |
writer = LineWriter(image, 60) | |
writer.text( | |
"To the inspiration of necessity, we owe half the wise, beautiful, and useful blessings of the world.", | |
1.5, | |
fill="black" | |
) | |
image.show() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment