Python interface for cairo graphics library providing 2D vector graphics, drawing operations, and rendering to multiple output formats
—
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.
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."""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."""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."""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."""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 fontText 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."""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")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")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")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")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")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