CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-pillow

Python Imaging Library (Fork) providing comprehensive image processing capabilities for reading, writing, and manipulating images across dozens of formats.

Pending
Overview
Eval results
Files

fonts.mddocs/

Font Handling and Text

TrueType and OpenType font support for rendering text on images with full typography control including size, spacing, alignment, and styling options. The ImageFont module provides comprehensive font loading and text rendering capabilities.

Capabilities

Font Loading

Load and manage different types of fonts.

def load_default(size=None):
    """
    Load the default PIL font.

    Parameters:
    - size (float): Font size (if None, uses default size)

    Returns:
    FreeTypeFont | ImageFont: Default font object
    """

def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
    """
    Load a TrueType or OpenType font from file.

    Parameters:
    - font (str | bytes | Path): Font filename, bytes, or pathlib.Path
    - size (float): Font size in points
    - index (int): Font index for font collections (TTC files)
    - encoding (str): Character encoding
    - layout_engine (int): Text layout engine (LAYOUT_BASIC or LAYOUT_RAQM)

    Returns:
    FreeTypeFont: Font object

    Raises:
    OSError: If font file cannot be loaded
    """

def load(filename):
    """
    Load a bitmap font from file.

    Parameters:
    - filename (str): Font filename (.pil format)

    Returns:
    ImageFont: Bitmap font object
    """

def load_path(filename):
    """
    Load a bitmap font, searching in font path.

    Parameters:
    - filename (str | bytes): Font filename

    Returns:
    ImageFont: Bitmap font object
    """

Font Classes

Font object types for different font formats.

class FreeTypeFont:
    """TrueType/OpenType font support with advanced typography features."""
    
    @property
    def family(self) -> str:
        """Font family name."""
    
    @property
    def style(self) -> str:
        """Font style name."""
    
    @property
    def size(self) -> float:
        """Font size in points."""
    
    def getsize(self, text, direction=None, features=None, language=None, stroke_width=0):
        """
        Get the size of given text (deprecated, use getbbox).

        Parameters:
        - text (str): Text to measure
        - direction (str): Text direction ("ltr", "rtl", "ttb")
        - features (list): OpenType features
        - language (str): Language code
        - stroke_width (int): Text stroke width

        Returns:
        tuple: Text size as (width, height)
        """
    
    def getbbox(self, text, anchor=None, direction=None, features=None, language=None, stroke_width=0, embedded_color=False):
        """
        Get the bounding box of given text.

        Parameters:
        - text (str): Text to measure
        - anchor (str): Text anchor point
        - direction (str): Text direction
        - features (list): OpenType features
        - language (str): Language code
        - stroke_width (int): Text stroke width
        - embedded_color (bool): Whether to use embedded color fonts

        Returns:
        tuple: Bounding box as (left, top, right, bottom)
        """
    
    def getlength(self, text, direction=None, features=None, language=None, embedded_color=False):
        """
        Get the length of given text.

        Parameters:
        - text (str): Text to measure
        - direction (str): Text direction
        - features (list): OpenType features
        - language (str): Language code
        - embedded_color (bool): Whether to use embedded color fonts

        Returns:
        float: Text length in pixels
        """
    
    def getmask(self, text, mode="", direction=None, features=None, language=None, stroke_width=0, anchor=None, ink=0, start=None):
        """
        Create a text mask.

        Parameters:
        - text (str): Text to render
        - mode (str): Mask mode
        - direction (str): Text direction
        - features (list): OpenType features
        - language (str): Language code
        - stroke_width (int): Text stroke width
        - anchor (str): Text anchor point
        - ink (int): Ink parameter for bitmap fonts
        - start (tuple): Starting offset

        Returns:
        Image: Text mask
        """
    
    def font_variant(self, font=None, size=None, index=None, encoding=None, layout_engine=None):
        """
        Create a variant of this font with different parameters.

        Parameters:
        - font (str): Font filename (uses current if None)
        - size (float): Font size (uses current if None)  
        - index (int): Font index (uses current if None)
        - encoding (str): Character encoding (uses current if None)
        - layout_engine (int): Layout engine (uses current if None)

        Returns:
        FreeTypeFont: New font variant
        """
    
    def set_variation_by_name(self, name):
        """
        Set font variation by name (for variable fonts).

        Parameters:
        - name (str): Variation name
        """
    
    def set_variation_by_axes(self, axes):
        """
        Set font variation by axes (for variable fonts).

        Parameters:
        - axes (dict): Axis values as {axis_name: value}
        """
    
    def get_variation_names(self):
        """
        Get available variation names (for variable fonts).

        Returns:
        list: Available variation names
        """
    
    def get_variation_axes(self):
        """
        Get available variation axes (for variable fonts).

        Returns:
        list: Available axes information
        """

class ImageFont:
    """Bitmap font support for simple text rendering."""
    
    def getsize(self, text):
        """
        Get the size of given text.

        Parameters:
        - text (str): Text to measure

        Returns:
        tuple: Text size as (width, height)
        """
    
    def getmask(self, text, mode="", direction=None, features=None, language=None, stroke_width=0, anchor=None, ink=0, start=None):
        """
        Create a text mask.

        Parameters:
        - text (str): Text to render
        - mode (str): Mask mode

        Returns:
        Image: Text mask
        """

class TransposedFont:
    """Font wrapper that applies a transformation to rendered text."""
    
    def __init__(self, font, orientation=None):
        """
        Create a transposed font.

        Parameters:
        - font (FreeTypeFont | ImageFont): Base font
        - orientation (int): Text orientation
        """

Layout Engines

Constants for text layout engines.

LAYOUT_BASIC: int  # Basic text layout engine
LAYOUT_RAQM: int   # Advanced text layout engine (supports complex scripts)

Usage Examples

Basic Font Loading and Text Rendering

from PIL import Image, ImageDraw, ImageFont

# Create a blank image
img = Image.new("RGB", (400, 200), "white")
draw = ImageDraw.Draw(img)

# Load different fonts
try:
    # Load system fonts
    title_font = ImageFont.truetype("arial.ttf", 36)
    body_font = ImageFont.truetype("arial.ttf", 18)
    bold_font = ImageFont.truetype("arialbd.ttf", 24)
except OSError:
    # Fallback to default font if system fonts not available
    title_font = ImageFont.load_default()
    body_font = ImageFont.load_default()
    bold_font = ImageFont.load_default()

# Draw text with different fonts
draw.text((200, 50), "Main Title", font=title_font, fill="black", anchor="mm")
draw.text((200, 100), "Subtitle text", font=bold_font, fill="blue", anchor="mm")
draw.text((200, 140), "Body text with default styling", font=body_font, fill="gray", anchor="mm")

img.save("font_example.png")

Advanced Typography Features

from PIL import Image, ImageDraw, ImageFont

img = Image.new("RGB", (600, 400), "white")
draw = ImageDraw.Draw(img)

# Load font with specific size
font = ImageFont.truetype("times.ttf", 24)

# Text with different anchors
text = "Anchor Demo"
y_positions = [50, 100, 150, 200]
anchors = ["lt", "mt", "rt", "mm"]
anchor_names = ["left-top", "middle-top", "right-top", "middle-middle"]

for y, anchor, name in zip(y_positions, anchors, anchor_names):
    # Draw reference line
    draw.line([(0, y), (600, y)], fill="lightgray", width=1)
    draw.line([(300, y-20), (300, y+20)], fill="lightgray", width=1)
    
    # Draw text with anchor
    draw.text((300, y), text, font=font, fill="black", anchor=anchor)
    
    # Label the anchor type
    draw.text((10, y), f"{anchor} ({name})", font=ImageFont.load_default(), fill="red")

img.save("anchor_demo.png")

Text Measurement and Layout

from PIL import Image, ImageDraw, ImageFont

# Create image
img = Image.new("RGB", (500, 300), "white")
draw = ImageDraw.Draw(img)

# Load font
font = ImageFont.truetype("arial.ttf", 20)

# Text to measure
text = "Sample text for measurement"

# Get text bounding box
bbox = font.getbbox(text)
text_width = bbox[2] - bbox[0]
text_height = bbox[3] - bbox[1]

print(f"Text dimensions: {text_width} x {text_height}")
print(f"Bounding box: {bbox}")

# Center text using measurements
img_width, img_height = img.size
x = (img_width - text_width) // 2
y = (img_height - text_height) // 2

# Draw text
draw.text((x, y), text, font=font, fill="black")

# Draw bounding box for visualization
draw.rectangle([x + bbox[0], y + bbox[1], x + bbox[2], y + bbox[3]], 
               outline="red", width=1)

img.save("text_measurement.png")

Multiline Text and Formatting

from PIL import Image, ImageDraw, ImageFont

img = Image.new("RGB", (400, 300), "white")
draw = ImageDraw.Draw(img)

font = ImageFont.truetype("arial.ttf", 16)

# Multiline text with different alignments
multiline_text = """This is a sample of multiline text
that demonstrates different
alignment options and
line spacing controls."""

# Left aligned
draw.multiline_text((50, 50), multiline_text, font=font, fill="black", 
                   align="left", spacing=8)

# Center aligned
draw.multiline_text((200, 50), multiline_text, font=font, fill="blue", 
                   align="center", spacing=12)

# Right aligned  
draw.multiline_text((350, 50), multiline_text, font=font, fill="green", 
                   align="right", spacing=6)

img.save("multiline_text.png")

Text Effects and Styling

from PIL import Image, ImageDraw, ImageFont

img = Image.new("RGB", (500, 200), "white")
draw = ImageDraw.Draw(img)

font = ImageFont.truetype("arial.ttf", 48)
text = "STYLED TEXT"

# Shadow effect
shadow_offset = 3
draw.text((102 + shadow_offset, 52 + shadow_offset), text, font=font, fill="gray")
draw.text((102, 52), text, font=font, fill="black")

# Outline effect (stroke)
if hasattr(font, 'getbbox'):  # Check if FreeType font
    draw.text((100, 100), text, font=font, fill="white", 
             stroke_width=2, stroke_fill="black")

img.save("text_effects.png")

Working with Different Text Directions

from PIL import Image, ImageDraw, ImageFont

img = Image.new("RGB", (400, 400), "white")
draw = ImageDraw.Draw(img)

font = ImageFont.truetype("arial.ttf", 24)

# Left-to-right text (default)
draw.text((50, 50), "Left to Right Text", font=font, fill="black", direction="ltr")

# Right-to-left text (for Arabic, Hebrew, etc.)
arabic_text = "النص العربي"  # "Arabic text" in Arabic
try:
    draw.text((350, 100), arabic_text, font=font, fill="blue", 
             direction="rtl", anchor="ra")
except:
    # Fallback if font doesn't support Arabic
    draw.text((350, 100), "RTL Text", font=font, fill="blue", 
             direction="rtl", anchor="ra")

# Vertical text
draw.text((200, 150), "Vertical", font=font, fill="green", direction="ttb")

img.save("text_directions.png")

Variable Font Support

from PIL import Image, ImageDraw, ImageFont

# Load a variable font (if available)
try:
    var_font = ImageFont.truetype("variable_font.ttf", 36)
    
    img = Image.new("RGB", (600, 300), "white")
    draw = ImageDraw.Draw(img)
    
    # Get available variations
    variations = var_font.get_variation_names()
    axes = var_font.get_variation_axes()
    
    print("Available variations:", variations)
    print("Available axes:", axes)
    
    # Apply different variations
    positions = [(100, 50), (100, 100), (100, 150), (100, 200)]
    
    for i, pos in enumerate(positions):
        # Create font variant with different weight
        weight = 200 + i * 200  # Vary from 200 to 800
        var_font.set_variation_by_axes({"wght": weight})
        
        draw.text(pos, f"Weight {weight}", font=var_font, fill="black")
    
    img.save("variable_font.png")

except OSError:
    print("Variable font not available")

Font Information and Properties

from PIL import ImageFont

# Load font and inspect properties
font = ImageFont.truetype("arial.ttf", 24)

print(f"Font family: {font.family}")
print(f"Font style: {font.style}")
print(f"Font size: {font.size}")

# Test text measurements
test_text = "Hello, World!"
bbox = font.getbbox(test_text)
length = font.getlength(test_text)

print(f"Text: '{test_text}'")
print(f"Bounding box: {bbox}")
print(f"Text length: {length}")

# Compare with different sizes
sizes = [12, 18, 24, 36, 48]
for size in sizes:
    font_size = ImageFont.truetype("arial.ttf", size)
    bbox = font_size.getbbox(test_text)
    width = bbox[2] - bbox[0]
    height = bbox[3] - bbox[1]
    print(f"Size {size}: {width} x {height} pixels")

Professional Text Layout

from PIL import Image, ImageDraw, ImageFont

def create_text_layout(title, subtitle, body, width=600, height=400):
    """Create a professional text layout with proper spacing."""
    img = Image.new("RGB", (width, height), "white")
    draw = ImageDraw.Draw(img)
    
    # Load fonts
    title_font = ImageFont.truetype("arial.ttf", 32)
    subtitle_font = ImageFont.truetype("arial.ttf", 20)
    body_font = ImageFont.truetype("arial.ttf", 14)
    
    # Layout parameters
    margin = 40
    line_spacing = 10
    current_y = margin
    
    # Draw title
    draw.text((width // 2, current_y), title, font=title_font, 
             fill="black", anchor="mt")
    title_bbox = title_font.getbbox(title)
    current_y += (title_bbox[3] - title_bbox[1]) + line_spacing * 2
    
    # Draw subtitle
    draw.text((width // 2, current_y), subtitle, font=subtitle_font, 
             fill="blue", anchor="mt")
    subtitle_bbox = subtitle_font.getbbox(subtitle)
    current_y += (subtitle_bbox[3] - subtitle_bbox[1]) + line_spacing * 3
    
    # Draw body text (wrapped)
    words = body.split()
    lines = []
    current_line = []
    
    for word in words:
        test_line = " ".join(current_line + [word])
        test_width = body_font.getlength(test_line)
        
        if test_width <= width - 2 * margin:
            current_line.append(word)
        else:
            if current_line:
                lines.append(" ".join(current_line))
                current_line = [word]
            else:
                lines.append(word)  # Single word longer than line
    
    if current_line:
        lines.append(" ".join(current_line))
    
    # Draw wrapped text
    for line in lines:
        draw.text((margin, current_y), line, font=body_font, fill="black")
        line_bbox = body_font.getbbox(line)
        current_y += (line_bbox[3] - line_bbox[1]) + line_spacing
    
    return img

# Create professional layout
title = "Professional Document"
subtitle = "Advanced Typography with Pillow"
body = """This is an example of professional text layout using Python's Pillow library. 
The text is properly wrapped, spaced, and formatted to create a clean, readable document. 
Multiple font sizes and colors are used to create visual hierarchy."""

layout = create_text_layout(title, subtitle, body)
layout.save("professional_layout.png")

Install with Tessl CLI

npx tessl i tessl/pypi-pillow

docs

color-management.md

color-utilities.md

core-image.md

drawing.md

enhancement.md

filters.md

fonts.md

image-sequences.md

image-statistics.md

index.md

math-operations.md

operations.md

tile.json