Palign is a Python package that helps to render and align text in Pillow.
Full documentation is online at palign.dev.
Text
draws text of a given Style
at a set of coordinates:
from PIL import Image, ImageDraw
from PIL.ImageFont import truetype
from palign import Style, Text
# Create a Pillow Image and Draw as usual:
image = Image.new("RGB", (270, 60))
draw = ImageDraw.Draw(image)
# Create a Style to describe the font:
style = Style(
font=truetype("tests/font/ChelseaMarket-Regular.ttf", 42),
)
# Create a text renderer:
text = Text(draw, style)
# Draw "Hello world!" at (0, 0):
text.draw("Hello world!", (0, 0))
# Same the image via Pillow:
image.save("./docs/images/example-0.png", "png")
Style
can also describe borders, colour and tracking:
from PIL import Image, ImageDraw
from PIL.ImageFont import truetype
from palign import Style, Text
image = Image.new("RGB", (410, 410), (255, 255, 255))
draw = ImageDraw.Draw(image)
style = Style(
color=(0, 0, 0),
font=truetype("tests/font/ChelseaMarket-Regular.ttf", 42),
)
text = Text(draw, style)
# Pass in a style to merge into the renderer's base style:
text.draw(
"Red!",
(0, 0),
style=Style(color=(255, 0, 0)),
)
text.draw(
"More tracking!",
(0, 60),
style=Style(tracking=2),
)
text.draw(
"Less tracking!",
(0, 120),
style=Style(tracking=-5),
)
text.draw(
"Highlight!",
(0, 180),
style=Style(background=(100, 255, 255)),
)
text.draw(
"Rounded highlight!",
(0, 240),
style=Style(background=(100, 255, 255), border_radius=20),
)
text.draw(
"Border!",
(0, 300),
style=Style(border_color=(255, 0, 0), border_width=3),
)
text.draw(
"Rounded border!",
(0, 360),
style=Style(border_color=(255, 0, 0), border_radius=20, border_width=3),
)
image.save("./docs/images/example-1.png", "png")
If you specify a region to render within (rather than just a point to render at) then text can aligned:
from PIL import Image, ImageDraw
from PIL.ImageFont import truetype
from palign import Alignment, Percent, Style, Text, make_image_region
# Create an image region:
image_region = make_image_region(300, 720)
# Pass the image region's size into Image.new:
image = Image.new("RGB", image_region.size, (255, 255, 255))
draw = ImageDraw.Draw(image)
# We're going to build a region to render the first block of text into.
#
# We want this region to fill the entire width of the image, with a little
# margin on every edge for comfort.
#
# So, let's start by creating a subregion with that margin by contracting in:
margin_region = image_region.expand(-10)
# Now we'll create a subregion that starts in the top-left corner, fills 100%
# of the available width and is 70 pixels tall:
text_region = margin_region.region2(0, 0, Percent(100), 70).resolve()
# Note that we need to .resolve() the region to resolve the relative values to
# absolutes.
style = Style(
border_color=(200, 200, 200),
border_radius=3,
border_width=1,
color=(0, 0, 0),
font=truetype("tests/font/ChelseaMarket-Regular.ttf", 21),
)
text = Text(draw, style)
for vertical in Alignment:
for horizontal in Alignment:
alignment = Style(horizontal=horizontal, vertical=vertical)
match horizontal:
case Alignment.Near:
horizontal_name = "Left"
case Alignment.Center:
horizontal_name = "Center"
case Alignment.Far:
horizontal_name = "Right"
match vertical:
case Alignment.Near:
vertical_name = "Top"
case Alignment.Center:
vertical_name = "Center"
case Alignment.Far:
vertical_name = "Bottom"
t = f"{vertical_name} {horizontal_name}"
text.draw(t, text_region, style=alignment)
# Translate the region down by (text_region.height + 10) pixels for
# the next block:
text_region += (0, text_region.height + 10)
image.save("./docs/images/example-2.png", "png")
To align text in a grid, use a Grid
:
from PIL import Image, ImageDraw
from PIL.ImageFont import truetype
from palign import Alignment, Grid, Style, make_image_region
image_region = make_image_region(600, 400)
image = Image.new("RGB", image_region.size, (255, 255, 255))
draw = ImageDraw.Draw(image)
style = Style(
color=(0, 0, 0),
font=truetype("tests/font/ChelseaMarket-Regular.ttf", 26),
)
column_count = 3
row_count = 3
grid = Grid(
column_count,
row_count,
image_region.expand(-10),
style=style,
)
for vertical_index, vertical in enumerate(Alignment):
for horizontal_index, horizontal in enumerate(Alignment):
match horizontal:
case Alignment.Near:
horizontal_name = "Left"
case Alignment.Center:
horizontal_name = "Center"
case Alignment.Far:
horizontal_name = "Right"
match vertical:
case Alignment.Near:
vertical_name = "Top"
case Alignment.Center:
vertical_name = "Center"
case Alignment.Far:
vertical_name = "Bottom"
t = f"{vertical_name}\n{horizontal_name}"
grid[horizontal_index, vertical_index].text = t
grid[horizontal_index, vertical_index].style.horizontal = horizontal
grid[horizontal_index, vertical_index].style.vertical = vertical
def color_bit(column: int) -> int:
return 155 + int((100 / column_count) * column)
for x in range(column_count):
for y in range(row_count):
red = color_bit(x) if y == 0 else 255
green = color_bit(x) if y == 1 else 255
blue = color_bit(x) if y == 2 else 255
grid[x, y].style.background = (red, green, blue)
grid.draw(draw)
image.save("./docs/images/grid.png", "png")
For detailed usage information, see the Style
, Text
and Grid
classes.
Palign requires Python 3.9 or later.
pip install palign
Please raise bugs, request new features and ask questions at github.com/cariad/palign/issues.
See CONTRIBUTING.md for contribution guidelines.
Palign is © 2022 Cariad Eccleston and released under the MIT License at github.com/cariad/palign.
Chelsea Market is copyright © 2011 Font Diner and redistributed under the SIL Open Font License 1.1.
Hello! 👋 I'm Cariad Eccleston and I'm a freelance backend and infrastructure engineer in the United Kingdom. You can find me at cariad.earth, github.com/cariad, linkedin.com/in/cariad and on Mastodon at @cariad@tech.lgbt.