Skip to content
/ palign Public

Python package that helps to render and align text in Pillow

License

Notifications You must be signed in to change notification settings

cariad/palign

Repository files navigation

Palign

codecov

Palign is a Python package that helps to render and align text in Pillow.

Full documentation is online at palign.dev.

Examples

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")

Hello world!

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")

Text of various styles

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")

Text aligned horizontally and vertically

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.

Installation

Palign requires Python 3.9 or later.

pip install palign

Support

Please raise bugs, request new features and ask questions at github.com/cariad/palign/issues.

Contributions

See CONTRIBUTING.md for contribution guidelines.

The Project

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.

The Author

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.

About

Python package that helps to render and align text in Pillow

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project