Skip to content

Instantly share code, notes, and snippets.

@ZechCodes
Last active December 27, 2022 11:05
Show Gist options
  • Save ZechCodes/c531080710f1714011ad0bdb013fb4f1 to your computer and use it in GitHub Desktop.
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.
"""
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