CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-pycairo

Python interface for cairo graphics library providing 2D vector graphics, drawing operations, and rendering to multiple output formats

Pending
Overview
Eval results
Files

text-fonts.mddocs/

Text and Fonts

Cairo provides comprehensive text rendering capabilities with support for multiple font backends, advanced typography features, and precise text measurement. The text system includes font selection, rendering options, glyph-level control, and metrics calculation for professional typography and layout applications.

Capabilities

Font Face Management

class FontFace:
    def __init__(self) -> None:
        """Base font face class (typically not instantiated directly)."""
        
    def get_type(self) -> FontType:
        """Get font face type (TOY, FT, WIN32, QUARTZ, USER, DWRITE)."""

class ToyFontFace(FontFace):
    def __init__(self, family: str, slant: FontSlant = FontSlant.NORMAL, weight: FontWeight = FontWeight.NORMAL) -> None:
        """Create simple font face from family name.
        
        Args:
            family: Font family name (e.g., "Arial", "Times New Roman")
            slant: Font slant (NORMAL, ITALIC, OBLIQUE)
            weight: Font weight (NORMAL, BOLD)
        """
        
    def get_family(self) -> str:
        """Get font family name."""
        
    def get_slant(self) -> FontSlant:
        """Get font slant."""
        
    def get_weight(self) -> FontWeight:
        """Get font weight."""

Font Rendering Options

class FontOptions:
    def __init__(self) -> None:
        """Create font options with default settings."""
        
    def copy(self) -> FontOptions:
        """Create copy of font options."""
        
    def merge(self, other: FontOptions) -> None:
        """Merge another font options into this one."""
        
    def hash(self) -> int:
        """Get hash value for font options."""
        
    def equal(self, other: FontOptions) -> bool:
        """Check equality with another font options."""
        
    def set_antialias(self, antialias: Antialias) -> None:
        """Set antialiasing mode for font rendering."""
        
    def get_antialias(self) -> Antialias:
        """Get antialiasing mode."""
        
    def set_subpixel_order(self, subpixel_order: SubpixelOrder) -> None:
        """Set subpixel order for LCD text rendering."""
        
    def get_subpixel_order(self) -> SubpixelOrder:
        """Get subpixel order."""
        
    def set_hint_style(self, hint_style: HintStyle) -> None:
        """Set font hinting style."""
        
    def get_hint_style(self) -> HintStyle:
        """Get font hinting style."""
        
    def set_hint_metrics(self, hint_metrics: HintMetrics) -> None:
        """Set font metrics hinting."""
        
    def get_hint_metrics(self) -> HintMetrics:
        """Get font metrics hinting."""
        
    def set_variations(self, variations: str) -> None:
        """Set font variations string for variable fonts."""
        
    def get_variations(self) -> str:
        """Get font variations string."""
        
    def set_color_mode(self, color_mode: ColorMode) -> None:
        """Set color font rendering mode."""
        
    def get_color_mode(self) -> ColorMode:
        """Get color font rendering mode."""
        
    def set_color_palette(self, palette_index: int) -> None:
        """Set color palette index for color fonts."""
        
    def get_color_palette(self) -> int:
        """Get color palette index."""
        
    def set_custom_palette_color(self, index: int, red: float, green: float, blue: float, alpha: float) -> None:
        """Set custom color for palette entry."""
        
    def get_custom_palette_color(self, index: int) -> tuple[float, float, float, float]:
        """Get custom color for palette entry."""

Scaled Fonts

class ScaledFont:
    def __init__(self, font_face: FontFace, font_matrix: Matrix, ctm: Matrix, options: FontOptions) -> None:
        """Create scaled font from font face and matrices.
        
        Args:
            font_face: Font face to scale
            font_matrix: Font transformation matrix
            ctm: Current transformation matrix
            options: Font rendering options
        """
        
    def get_type(self) -> FontType:
        """Get scaled font type."""
        
    def get_reference_count(self) -> int:
        """Get reference count."""
        
    def get_font_face(self) -> FontFace:
        """Get underlying font face."""
        
    def get_font_options(self) -> FontOptions:
        """Get font options."""
        
    def get_font_matrix(self) -> Matrix:
        """Get font transformation matrix."""
        
    def get_ctm(self) -> Matrix:
        """Get current transformation matrix."""
        
    def get_scale_matrix(self) -> Matrix:
        """Get combined scale matrix."""
        
    def extents(self) -> tuple[float, float, float, float, float]:
        """Get font extents (ascent, descent, height, max_x_advance, max_y_advance)."""
        
    def text_extents(self, text: str) -> TextExtents:
        """Get text extents for given string."""
        
    def glyph_extents(self, glyphs: list[Glyph]) -> TextExtents:
        """Get glyph extents for glyph array."""
        
    def text_to_glyphs(self, x: float, y: float, text: str) -> tuple[list[Glyph], list[TextCluster]]:
        """Convert text to glyphs with cluster information."""
        
    def get_color_palette(self, palette_index: int) -> list[tuple[float, float, float, float]]:
        """Get color palette for color fonts."""
        
    def get_type(self) -> FontType:
        """Get scaled font type."""
        
    def get_reference_count(self) -> int:
        """Get reference count for the scaled font."""

Text Measurement Classes

class TextExtents:
    def __init__(self, x_bearing: float, y_bearing: float, width: float, height: float, x_advance: float, y_advance: float) -> None:
        """Text measurement data.
        
        Args:
            x_bearing: Horizontal distance from origin to leftmost part
            y_bearing: Vertical distance from origin to topmost part
            width: Width of text
            height: Height of text
            x_advance: Horizontal advance to next glyph position
            y_advance: Vertical advance to next glyph position
        """
        
    @property
    def x_bearing(self) -> float:
        """X bearing value."""
        
    @property
    def y_bearing(self) -> float:
        """Y bearing value."""
        
    @property
    def width(self) -> float:
        """Text width."""
        
    @property
    def height(self) -> float:
        """Text height."""
        
    @property
    def x_advance(self) -> float:
        """X advance value."""
        
    @property
    def y_advance(self) -> float:
        """Y advance value."""

class Glyph:
    def __init__(self, index: int, x: float, y: float) -> None:
        """Glyph positioning data.
        
        Args:
            index: Glyph index in font
            x: X coordinate for glyph placement
            y: Y coordinate for glyph placement
        """
        
    @property
    def index(self) -> int:
        """Glyph index."""
        
    @property
    def x(self) -> float:
        """X coordinate."""
        
    @property
    def y(self) -> float:
        """Y coordinate."""

class TextCluster:
    def __init__(self, num_bytes: int, num_glyphs: int) -> None:
        """Text cluster mapping data.
        
        Args:
            num_bytes: Number of UTF-8 bytes in cluster
            num_glyphs: Number of glyphs in cluster
        """
        
    @property
    def num_bytes(self) -> int:
        """Number of bytes."""
        
    @property
    def num_glyphs(self) -> int:
        """Number of glyphs."""

Font Type Enumeration

class FontType:
    """Font face implementation types."""
    TOY: int = 0        # Simple toy font
    FT: int = 1         # FreeType font
    WIN32: int = 2      # Win32 font
    QUARTZ: int = 3     # Quartz font  
    USER: int = 4       # User font
    DWRITE: int = 5     # DirectWrite font

Context Text Methods

Text rendering methods available on Context objects:

# Font selection and configuration
def select_font_face(self, family: str, slant: FontSlant, weight: FontWeight) -> None:
    """Select font face by family name and style."""

def set_font_size(self, size: float) -> None:
    """Set font size in user units."""

def set_font_matrix(self, matrix: Matrix) -> None:
    """Set font transformation matrix."""

def get_font_matrix(self) -> Matrix:
    """Get font transformation matrix."""

def set_font_options(self, options: FontOptions) -> None:
    """Set font rendering options."""

def get_font_options(self) -> FontOptions:
    """Get font rendering options."""

def set_font_face(self, font_face: FontFace) -> None:
    """Set font face."""

def get_font_face(self) -> FontFace:
    """Get current font face."""

def set_scaled_font(self, scaled_font: ScaledFont) -> None:
    """Set scaled font."""

def get_scaled_font(self) -> ScaledFont:
    """Get current scaled font."""

# Text rendering
def show_text(self, text: str) -> None:
    """Render text at current point."""

def show_glyphs(self, glyphs: list[Glyph]) -> None:
    """Render glyphs at specified positions."""

def show_text_glyphs(self, text: str, glyphs: list[Glyph], clusters: list[TextCluster], cluster_flags: TextClusterFlags) -> None:
    """Render text with glyph and cluster information."""

# Text measurement
def text_extents(self, text: str) -> TextExtents:
    """Get text extents using current font."""

def glyph_extents(self, glyphs: list[Glyph]) -> TextExtents:
    """Get glyph extents using current font."""

def font_extents(self) -> tuple[float, float, float, float, float]:
    """Get font extents (ascent, descent, height, max_x_advance, max_y_advance)."""

# Path operations
def text_path(self, text: str) -> None:
    """Add text outline to current path."""

def glyph_path(self, glyphs: list[Glyph]) -> None:
    """Add glyph outlines to current path."""

Usage Examples

Basic Text Rendering

import cairo

surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 500, 300)
ctx = cairo.Context(surface)

# Set background
ctx.set_source_rgb(1, 1, 1)
ctx.paint()

# Basic text rendering
ctx.set_source_rgb(0, 0, 0)
ctx.select_font_face("Arial", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
ctx.set_font_size(24)
ctx.move_to(50, 50)
ctx.show_text("Hello, Cairo!")

# Different font styles
ctx.select_font_face("Arial", cairo.FONT_SLANT_ITALIC, cairo.FONT_WEIGHT_BOLD)
ctx.set_font_size(20)
ctx.move_to(50, 100)
ctx.show_text("Bold italic text")

# Different colors
ctx.set_source_rgb(0.8, 0.2, 0.2)
ctx.select_font_face("Times New Roman", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
ctx.set_font_size(18)
ctx.move_to(50, 150)
ctx.show_text("Red Times text")

surface.write_to_png("basic_text.png")

Font Options and Hinting

import cairo

surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 600, 400)
ctx = cairo.Context(surface)

ctx.set_source_rgb(1, 1, 1)
ctx.paint()

# Create different font options
font_options_none = cairo.FontOptions()
font_options_none.set_hint_style(cairo.HINT_STYLE_NONE)

font_options_slight = cairo.FontOptions()
font_options_slight.set_hint_style(cairo.HINT_STYLE_SLIGHT)

font_options_medium = cairo.FontOptions()
font_options_medium.set_hint_style(cairo.HINT_STYLE_MEDIUM)

font_options_full = cairo.FontOptions()
font_options_full.set_hint_style(cairo.HINT_STYLE_FULL)

# Render text with different hinting
ctx.set_source_rgb(0, 0, 0)
ctx.select_font_face("Arial", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
ctx.set_font_size(16)

y = 50
for label, options in [
    ("No hinting", font_options_none),
    ("Slight hinting", font_options_slight),
    ("Medium hinting", font_options_medium),
    ("Full hinting", font_options_full)
]:
    ctx.set_font_options(options)
    ctx.move_to(50, y)
    ctx.show_text(f"{label}: The quick brown fox jumps")
    y += 30

# Antialiasing options
y += 30
for antialias in [cairo.ANTIALIAS_NONE, cairo.ANTIALIAS_GRAY, cairo.ANTIALIAS_SUBPIXEL]:
    font_options = cairo.FontOptions()
    font_options.set_antialias(antialias)
    ctx.set_font_options(font_options)
    ctx.move_to(50, y) 
    ctx.show_text(f"Antialias {antialias}: Sample text")
    y += 30

surface.write_to_png("font_options.png")

Text Measurement and Layout

import cairo

surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 500, 300)
ctx = cairo.Context(surface)

ctx.set_source_rgb(1, 1, 1)
ctx.paint()

# Set up font
ctx.select_font_face("Arial", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
ctx.set_font_size(24)

text = "Cairo Text"

# Get text extents
text_extents = ctx.text_extents(text)
font_ascent, font_descent, font_height, font_max_x_advance, font_max_y_advance = ctx.font_extents()

print(f"Text extents:")
print(f"  x_bearing: {text_extents.x_bearing}")
print(f"  y_bearing: {text_extents.y_bearing}")
print(f"  width: {text_extents.width}")
print(f"  height: {text_extents.height}")
print(f"  x_advance: {text_extents.x_advance}")
print(f"  y_advance: {text_extents.y_advance}")

print(f"Font extents:")
print(f"  ascent: {font_ascent}")
print(f"  descent: {font_descent}")
print(f"  height: {font_height}")

# Position text
x, y = 100, 150
ctx.move_to(x, y)

# Draw text
ctx.set_source_rgb(0, 0, 0)
ctx.show_text(text)

# Draw text extents box
ctx.set_source_rgba(0.8, 0.2, 0.2, 0.5)
ctx.rectangle(x + text_extents.x_bearing, y + text_extents.y_bearing, 
              text_extents.width, text_extents.height)
ctx.fill()

# Draw baseline
ctx.set_source_rgb(0, 0.8, 0)
ctx.set_line_width(1)
ctx.move_to(x - 20, y)
ctx.line_to(x + text_extents.x_advance + 20, y)
ctx.stroke()

# Draw advance point
ctx.set_source_rgb(0, 0, 0.8)
ctx.arc(x + text_extents.x_advance, y, 3, 0, 2 * 3.14159)
ctx.fill()

# Right-aligned text using extents
right_text = "Right aligned"
right_extents = ctx.text_extents(right_text)
ctx.move_to(450 - right_extents.width, 200)
ctx.set_source_rgb(0.4, 0.4, 0.4)
ctx.show_text(right_text)

surface.write_to_png("text_measurement.png")

Text Paths and Effects

import cairo

surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 500, 300)
ctx = cairo.Context(surface)

ctx.set_source_rgb(1, 1, 1)
ctx.paint()

# Create text path
ctx.select_font_face("Arial", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD)
ctx.set_font_size(48)
ctx.move_to(50, 100)
ctx.text_path("TEXT PATH")

# Fill with gradient
gradient = cairo.LinearGradient(50, 60, 400, 60)
gradient.add_color_stop_rgb(0, 1, 0, 0)
gradient.add_color_stop_rgb(0.5, 1, 1, 0)
gradient.add_color_stop_rgb(1, 0, 0, 1)

ctx.set_source(gradient)
ctx.fill_preserve()

# Stroke outline
ctx.set_source_rgb(0, 0, 0)
ctx.set_line_width(2)
ctx.stroke()

# Outlined text effect
ctx.move_to(50, 180)
ctx.text_path("OUTLINED")

# Fill
ctx.set_source_rgb(1, 1, 1)
ctx.fill_preserve()

# Stroke
ctx.set_source_rgb(0.2, 0.2, 0.8)
ctx.set_line_width(3)
ctx.stroke()

# Shadow effect
shadow_text = "SHADOW"
ctx.select_font_face("Arial", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD)
ctx.set_font_size(36)

# Draw shadow
ctx.move_to(52, 242)
ctx.set_source_rgba(0, 0, 0, 0.5)
ctx.show_text(shadow_text)

# Draw main text
ctx.move_to(50, 240)
ctx.set_source_rgb(0.8, 0.2, 0.2)
ctx.show_text(shadow_text)

surface.write_to_png("text_effects.png")

Advanced Font Features

import cairo

surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 600, 400)
ctx = cairo.Context(surface)

ctx.set_source_rgb(1, 1, 1)
ctx.paint()

# Using ToyFontFace
toy_font = cairo.ToyFontFace("Georgia", cairo.FONT_SLANT_ITALIC, cairo.FONT_WEIGHT_BOLD)
ctx.set_font_face(toy_font)
ctx.set_font_size(24)
ctx.move_to(50, 50)
ctx.set_source_rgb(0, 0, 0)
ctx.show_text("ToyFontFace: Georgia Bold Italic")

# Font matrix transformation
matrix = cairo.Matrix()
matrix.scale(1.5, 0.8)  # Stretch horizontally, compress vertically
matrix.rotate(0.1)      # Slight rotation

ctx.set_font_matrix(matrix)
ctx.move_to(50, 100)
ctx.show_text("Transformed font matrix")

# Reset font matrix
ctx.set_font_matrix(cairo.Matrix())

# Working with ScaledFont
font_face = cairo.ToyFontFace("Arial")
font_matrix = cairo.Matrix()
font_matrix.scale(20, 20)
ctm = cairo.Matrix()
options = cairo.FontOptions()

scaled_font = cairo.ScaledFont(font_face, font_matrix, ctm, options)
ctx.set_scaled_font(scaled_font)

ctx.move_to(50, 150)
ctx.show_text("ScaledFont example")

# Get font extents from scaled font
font_extents = scaled_font.extents()
print(f"ScaledFont extents: ascent={font_extents[0]}, descent={font_extents[1]}")

# Text to glyphs conversion (if supported)
try:
    text = "Hello"
    glyphs, clusters = scaled_font.text_to_glyphs(50, 200, text)
    
    # Render using glyphs directly
    ctx.move_to(50, 200)
    ctx.show_glyphs(glyphs)
    
    print(f"Text '{text}' converted to {len(glyphs)} glyphs and {len(clusters)} clusters")
    
except cairo.Error as e:
    print(f"Glyph conversion not supported: {e}")
    ctx.move_to(50, 200)
    ctx.show_text("Glyph conversion not available")

surface.write_to_png("advanced_fonts.png")

Multi-line Text Layout

import cairo

def draw_multiline_text(ctx, text, x, y, line_height):
    """Draw multi-line text with proper line spacing."""
    lines = text.split('\n')
    for i, line in enumerate(lines):
        ctx.move_to(x, y + i * line_height)
        ctx.show_text(line)

surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 500, 400)
ctx = cairo.Context(surface)

ctx.set_source_rgb(1, 1, 1)
ctx.paint()

# Multi-line text example
ctx.select_font_face("Arial", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
ctx.set_font_size(16)
ctx.set_source_rgb(0, 0, 0)

multiline_text = """This is a multi-line text example.
Each line is drawn separately using
the line height calculated from font
extents to ensure proper spacing
between lines."""

# Get font metrics for line spacing
font_ascent, font_descent, font_height, _, _ = ctx.font_extents()
line_height = font_height + 2  # Small extra spacing

draw_multiline_text(ctx, multiline_text, 50, 50, line_height)

# Justified text example
ctx.set_font_size(14)
words = ["The", "quick", "brown", "fox", "jumps", "over", "lazy", "dog"]
line_width = 300
x_start = 50
y_start = 200

# Calculate word spacing for justification
total_word_width = sum(ctx.text_extents(word).width for word in words)
space_width = ctx.text_extents(" ").width
available_space = line_width - total_word_width
extra_space_per_gap = available_space / (len(words) - 1) if len(words) > 1 else 0

# Draw justified text
x = x_start
for i, word in enumerate(words):
    ctx.move_to(x, y_start)
    ctx.show_text(word)
    
    word_width = ctx.text_extents(word).width
    x += word_width
    
    if i < len(words) - 1:  # Not the last word
        x += space_width + extra_space_per_gap

# Draw bounding box for justified text
ctx.set_source_rgba(0.8, 0.2, 0.2, 0.3)
ctx.rectangle(x_start, y_start - font_ascent, line_width, font_height)
ctx.fill()

surface.write_to_png("multiline_text.png")

Install with Tessl CLI

npx tessl i tessl/pypi-pycairo

docs

constants-enums.md

drawing-context.md

geometry.md

index.md

patterns.md

surfaces.md

text-fonts.md

tile.json