https://github.com/cariad/palign
Python package that helps to render and align text in Pillow
https://github.com/cariad/palign
image-processing pillow python
Last synced: about 1 year ago
JSON representation
Python package that helps to render and align text in Pillow
- Host: GitHub
- URL: https://github.com/cariad/palign
- Owner: cariad
- License: mit
- Created: 2022-11-21T11:32:40.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2022-12-05T08:43:18.000Z (over 3 years ago)
- Last Synced: 2025-03-02T12:41:23.518Z (over 1 year ago)
- Topics: image-processing, pillow, python
- Language: Python
- Homepage: https://palign.dev
- Size: 1.08 MB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
README
# Palign
[](https://codecov.io/gh/cariad/palign)
**Palign** is a Python package that helps to render and align text in [Pillow](https://python-pillow.org/).
Full documentation is online at **[palign.dev](https://palign.dev/)**.
## Examples
[`Text`](https://palign.dev/text/) draws text of a given [`Style`](https://palign.dev/style/) at a set of coordinates:
```python
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`](https://palign.dev/style/) can also describe borders, colour and tracking:
```python
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:
```python
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`](https://palign.dev/grid/):
```python
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`](https://palign.dev/style/), [`Text`](https://palign.dev/text/) and [`Grid`](https://palign.dev/grid/) classes.
## Installation
**Palign** requires Python 3.9 or later.
```console
pip install palign
```
## Support
Please raise bugs, request new features and ask questions at [github.com/cariad/palign/issues](https://github.com/cariad/palign/issues).
## Contributions
See [CONTRIBUTING.md](https://github.com/cariad/palign/blob/main/CONTRIBUTING.md) for contribution guidelines.
## The Project
**Palign** is © 2022 Cariad Eccleston and released under the [MIT License](https://github.com/cariad/palign/blob/main/LICENSE) at [github.com/cariad/palign](https://github.com/cariad/palign).
**Chelsea Market** is copyright © 2011 Font Diner and redistributed under the [SIL Open Font License 1.1](https://github.com/cariad/palign/blob/main/tests/font/OFL.txt).
## 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](https://cariad.earth), [github.com/cariad](https://github.com/cariad), [linkedin.com/in/cariad](https://linkedin.com/in/cariad) and on Mastodon at [@cariad@tech.lgbt](https://tech.lgbt/@cariad).