Quick Start

Installation

pip install fontstack

Note

FontStack does not bundle fonts. You need to supply your own .ttf, .otf, .ttc, or .otc files. The Noto fonts provide excellent Unicode coverage.

Basic usage

from PIL import Image
from fontstack import FontConfig, FontManager

manager = FontManager(
    default_stack=[
        FontConfig(path="fonts/NotoSans[wdth,wght].ttf"),
        FontConfig(path="fonts/NotoSansArabic[wdth,wght].ttf"),
    ]
)

img = Image.new("RGBA", (800, 100), "white")
manager.draw(img, "Hello مرحبا", position=(20, 20), size=48, weight=700)
img.save("output.png")

Using a font directory

Instead of listing each font, point to a folder:

manager = FontManager(font_dir="fonts/")

Fonts are loaded in alphabetical order by filename - the first file becomes the primary font and later files act as fallbacks.

One-shot rendering

draw_text creates a manager, renders, and returns a cropped image:

from fontstack import draw_text

img = draw_text(
    "Hello 世界 🌍",
    font_dir="fonts/",
    size=48,
    fill="red-blue",
    padding=16,
)
img.save("hello.png")

Anchor points

anchor controls which point of the text block is placed at position. It uses a two-character PIL-style code: the first character selects the horizontal reference (l = left, m = centre, r = right) and the second selects the vertical reference (t = top, m = middle, b = bottom):

lt ── mt ── rt
│           │
lm    mm    rm
│           │
lb ── mb ── rb

The default "lt" (top-left) is fully backward-compatible.

from PIL import Image
from fontstack import FontConfig, FontManager

manager = FontManager(font_dir="fonts/")
img = Image.new("RGBA", (800, 400), "white")

# Centre text on a known point - useful for badges, labels, map pins, etc.
manager.draw(img, "Hello", position=(400, 200), size=48, anchor="mm")

# Bottom-right: text ends exactly at a corner
manager.draw(img, "caption", position=(780, 380), size=24, anchor="rb")

img.save("output.png")

Note

anchor only applies to FontManager.draw(), not to draw_text(). draw_text auto-sizes its canvas to the rendered text and returns a fresh image, so there is no external reference point for an anchor to target.