Skip to main content

piltext

codecov PyPi Version

A Python library for creating PNG images from text using Pillow. Features include precise text positioning, automatic font scaling, grid layouts, and percentage visualizations (dials and waffle charts).

Features

  • 🎨 Text Rendering - Draw text with customizable fonts, sizes, colors, and styles
  • 📐 Auto-Fit Scaling - Automatically scale text to fit within bounding boxes
  • Anchor Positioning - Precise text alignment (left/center/right, top/middle/bottom)
  • 🔤 Font Management - Load local fonts or download from Google Fonts
  • 📊 Grid Layouts - Create multi-cell grids with merged cells
  • 📈 Visualizations - Built-in dial gauges and waffle charts for percentages
  • ⚙️ TOML Configuration - Define complex layouts with simple config files
  • 🖼️ Image Transformations - Mirror, rotate, and invert images

Installation

From PyPI

pip install piltext

From Source

git clone https://github.com/holgern/piltext.git
cd piltext
python3 setup.py install

Quick Start

Simple Text Rendering

from piltext import FontManager, ImageDrawer

# Initialize with font manager
fm = FontManager(default_font_size=20)
image = ImageDrawer(400, 200, font_manager=fm)

# Draw text
image.draw_text("Hello World", (50, 50), font_size=32, fill="black")
image.finalize()
image.show()

Using TOML Configuration

Create config.toml:

[fonts]
default_size = 24

[image]
width = 400
height = 200

[grid]
rows = 2
columns = 2
margin_x = 5
margin_y = 5

[[grid.texts]]
start = [0, 0]
text = "Hello"
anchor = "mm"

[[grid.texts]]
start = [0, 1]
text = "World"
anchor = "mm"

Render from command line:

piltext render config.toml --output output.png

Or from Python:

from piltext import ConfigLoader

loader = ConfigLoader("config.toml")
image = loader.render(output_path="output.png")

Python API

Font Management

from piltext import FontManager

# Initialize with default font size
fm = FontManager(default_font_size=20)

# Download Google Fonts
font = fm.download_google_font("ofl", "roboto", "Roboto[wdth,wght].ttf")
fm.default_font_name = font

# List available fonts
print(fm.list_available_fonts())

# Get font variations (for variable fonts)
print(fm.get_variation_names())

Drawing Text

from piltext import FontManager, ImageDrawer

fm = FontManager(default_font_size=20)
image = ImageDrawer(480, 280, font_manager=fm)

# Basic text drawing
w, h, font_size = image.draw_text("Hello", (10, 10), font_size=24)

# Auto-fit text to bounding box (no font_size specified)
w, h, font_size = image.draw_text(
    "Auto-Fit Text",
    start=(10, 50),
    end=(200, 100),
    anchor="mm"  # Center the text
)

# Fixed font size with anchor
w, h, font_size = image.draw_text(
    "Fixed Size",
    (400, 250),
    font_size=20,
    anchor="rb"  # Right-bottom
)

image.finalize()
image.show()

Anchor Positioning

Anchors control text alignment using a two-character code: [horizontal][vertical]

Horizontal: l (left), m (middle), r (right) Vertical: t (top), m (middle), b (bottom), s (baseline)

Common anchors:

  • lt - Left-top (default)
  • mm - Centered
  • rb - Right-bottom
  • mt - Middle-top
  • lb - Left-bottom

Auto-fit behavior:

  • When font_size is not specified: Text scales to fit the bounding box, then positions using the anchor
  • When font_size is specified: Text uses fixed size and positions using the anchor (no scaling)

Grid Layouts

from piltext import FontManager, ImageDrawer, TextGrid

fm = FontManager(default_font_size=20)
image = ImageDrawer(480, 280, font_manager=fm)

# Create grid
grid = TextGrid(4, 3, image, margin_x=5, margin_y=5)

# Merge cells: ((start_row, start_col), (end_row, end_col))
merge_list = [
    ((0, 0), (0, 2)),  # Merge row 0, columns 0-2
    ((1, 1), (2, 2)),  # Merge rows 1-2, columns 1-2
]
grid.merge_bulk(merge_list)

# Add text to cells
grid.set_text((0, 0), "Header", anchor="mm", font_size=28)
grid.set_text((1, 0), "Cell 1", anchor="lt")
grid.set_text((1, 1), "Large Cell", anchor="mm")

image.finalize()
image.show()

Bulk Text Setting

text_list = [
    {"start": (0, 0), "text": "Header", "font_size": 28, "anchor": "mm"},
    {"start": (1, 0), "text": "Cell 1", "anchor": "lt"},
    {"start": (1, 1), "text": "Cell 2", "anchor": "mm", "fill": 128},
]
grid.set_text_list(text_list)

Visualizations

Dial Gauge

from piltext import ImageDial

dial = ImageDial(
    percentage=0.75,
    size=300,
    fg_color="#4CAF50",
    track_color="#e0e0e0",
    show_value=True
)
dial.show()

Waffle Chart

from piltext import ImageSquares

squares = ImageSquares(
    percentage=0.65,
    max_squares=100,
    size=300,
    fg_color="#2196F3",
    empty_color="#e0e0e0"
)
squares.show()

TOML Configuration Reference

Complete Example

[fonts]
default_size = 20
default_name = "Roboto-Bold"
directories = ["/path/to/fonts"]

[[fonts.download]]
part1 = "ofl"
part2 = "roboto"
font_name = "Roboto[wdth,wght].ttf"

[image]
width = 480
height = 280
mode = "RGB"
background = "white"
inverted = false
mirror = false
orientation = 0

[grid]
rows = 4
columns = 3
margin_x = 5
margin_y = 5

merge = [
  [[0, 0], [0, 2]],  # Header row
  [[1, 1], [2, 2]]   # Large cell
]

[[grid.texts]]
start = [0, 0]
text = "Header"
anchor = "mm"
font_size = 28
fill = 0

[[grid.texts]]
start = [1, 0]
text = "Data"
anchor = "lt"
font_variation = "Bold"

# Embed dial in grid cell
[[grid.texts]]
start = [1, 1]
anchor = "mm"

[grid.texts.dial]
percentage = 0.75
fg_color = "#4CAF50"
show_value = true

# Embed waffle chart in grid cell
[[grid.texts]]
start = [3, 2]
anchor = "mm"

[grid.texts.squares]
percentage = 0.60
rows = 5
columns = 5
fg_color = "#2196F3"

Configuration Sections

Fonts

[fonts]
default_size = 20              # Default font size in pixels
default_name = "Roboto-Bold"   # Default font name
directories = ["/path/to/fonts"]  # Custom font directories (optional)

# Download fonts before rendering (optional)
# From Google Fonts
[[fonts.download]]
part1 = "ofl"                  # Google Fonts: license type
part2 = "roboto"               # Google Fonts: font family
font_name = "Roboto[wdth,wght].ttf"

# Or from direct URL
[[fonts.download]]
url = "https://example.com/font.ttf"

Image

[image]
width = 480                    # Image width in pixels
height = 280                   # Image height in pixels
mode = "RGB"                   # PIL mode: RGB, RGBA, L, 1
background = "white"           # Background color
inverted = false               # Invert colors
mirror = false                 # Mirror horizontally
orientation = 0                # Rotation angle (0, 90, 180, 270)

Grid

[grid]
rows = 4                       # Number of rows
columns = 3                    # Number of columns
margin_x = 5                   # Horizontal margin in pixels
margin_y = 5                   # Vertical margin in pixels

# Merge cells (optional)
merge = [
  [[0, 0], [2, 0]],            # Merge rows 0-2, column 0
]

# Text content
[[grid.texts]]
start = [0, 0]                 # Cell position (or merged cell index)
text = "Content"               # Text to display
font_name = "Roboto"           # Font name (optional)
font_size = 24                 # Font size (optional, triggers auto-fit if omitted)
font_variation = "Bold"        # Font variation (optional)
fill = 0                       # Text color (optional, default: 0 for black)
anchor = "mm"                  # Anchor position (optional, default: "lt")

Standalone Dial

[dial]
percentage = 0.75              # Value from 0.0 to 1.0
size = 300                     # Image size in pixels
radius = 120                   # Dial radius (optional, auto-calculated)
fg_color = "#4CAF50"           # Arc color for filled portion
track_color = "#e0e0e0"        # Background arc color
bg_color = "white"             # Background color
thickness = 20                 # Arc thickness in pixels
font_name = "Roboto-Bold"      # Font for labels (optional)
font_size = 24                 # Font size (optional)
show_needle = true             # Show needle pointer
show_ticks = true              # Show tick marks
show_value = true              # Show percentage in center
start_angle = -135             # Start angle in degrees
end_angle = 135                # End angle in degrees

Standalone Waffle Chart

[squares]
percentage = 0.65              # Value from 0.0 to 1.0
max_squares = 100              # Total number of squares
size = 300                     # Image size in pixels
rows = 10                      # Number of rows (optional)
columns = 10                   # Number of columns (optional)
fg_color = "#4CAF50"           # Fill color
empty_color = "#e0e0e0"        # Empty square color
bg_color = "white"             # Background color
gap = 2                        # Gap between squares in pixels
border_width = 1               # Border width
border_color = "#cccccc"       # Border color
show_partial = true            # Partially fill last square

Priority: dial > squares > grid > image

CLI Commands

Render Images

# Render from TOML config
piltext render config.toml -o output.png

# Render with specific output
piltext render examples/example_dial.toml --output my_dial.png

Font Management

# List available fonts
piltext font list

# Download Google Font
piltext font download ofl roboto Roboto-Regular.ttf

# List font variations
piltext font variations "Roboto[wdth,wght]"

Examples

See the examples/ directory for complete examples:

  • example_simple.toml - Basic text grid
  • example_grid_with_visualizations.toml - Grid with embedded charts
  • example_dial.toml - Standalone dial gauge
  • example_squares.toml - Standalone waffle chart
  • example_config.toml - Complete configuration example

Development

Running Tests

pytest
pytest --cov=piltext --cov-report=term

Type Checking

mypy piltext

Linting

ruff check --fix --config=.ruff.toml

Pre-commit Hooks

Install pre-commit:

pip install pre-commit
# or
brew install pre-commit

Install hooks:

pre-commit install

Run hooks:

pre-commit run --all-files

Update hooks:

pre-commit autoupdate
pre-commit run --show-diff-on-failure --color=always --all-files

Documentation

Full API documentation is available at Read the Docs.

Build documentation locally:

cd docs
python make.py

License

MIT

Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch
  3. Add tests for new functionality
  4. Ensure all tests pass and linting/type checking succeeds
  5. Submit a pull request

Changelog

Recent Improvements

  • ✅ Fixed auto-fit behavior with custom anchors (text with rb, mm, etc. now renders correctly)
  • ✅ Added comprehensive test coverage (48%+ overall, 100% for core modules)
  • ✅ Full mypy type checking support
  • ✅ Improved documentation with detailed examples

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

piltext-0.5.0.tar.gz (375.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

piltext-0.5.0-py3-none-any.whl (57.6 kB view details)

Uploaded Python 3

File details

Details for the file piltext-0.5.0.tar.gz.

File metadata

  • Download URL: piltext-0.5.0.tar.gz
  • Upload date:
  • Size: 375.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.24

File hashes

Hashes for piltext-0.5.0.tar.gz
Algorithm Hash digest
SHA256 659be2d40ee78c9ae110716cb2abbe832793fd30e9023699531d67e849e07528
MD5 09e29e7f6b1c61fd44abc68844be4767
BLAKE2b-256 376e7a5b89eeca5b9454273670aa3ec21f5906274f9cdb8a0ae735c36c58217d

See more details on using hashes here.

File details

Details for the file piltext-0.5.0-py3-none-any.whl.

File metadata

  • Download URL: piltext-0.5.0-py3-none-any.whl
  • Upload date:
  • Size: 57.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.24

File hashes

Hashes for piltext-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f5c48514035c753053182d1d776b078a2348b7b9e411ed278e4dcb60590d1cdc
MD5 d56e49e382527674522ff24e5e789da6
BLAKE2b-256 a5ac1fa009a34d57a2c98db6596d452754076b7f9de4969037a4d2e7457187e2

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.5.0 This release

2 files

0.4.0

2 files

0.3.0

2 files

0.2.3

2 files

0.2.2

2 files

0.2.1

2 files

0.2.0

2 files

0.1.6

2 files

0.1.5

2 files

0.1.4

2 files

0.1.3

2 files

0.1.2

2 files

0.1.1

2 files

0.1.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page