Python interface for cairo graphics library providing 2D vector graphics, drawing operations, and rendering to multiple output formats
—
Cairo's geometry classes handle spatial data and transformations. These classes provide the foundation for vector paths, coordinate transformations, rectangular regions, and spatial calculations. They enable precise control over shape construction, positioning, and mathematical operations on geometric data.
class Path:
def __iter__(self) -> Iterator[tuple[PathDataType, tuple[float, ...]]]:
"""Iterate over path elements.
Yields:
Tuple of (path_data_type, coordinates) where:
- MOVE_TO: (x, y)
- LINE_TO: (x, y)
- CURVE_TO: (x1, y1, x2, y2, x3, y3)
- CLOSE_PATH: ()
"""
def __eq__(self, other: object) -> bool:
"""Test path equality."""
def __ne__(self, other: object) -> bool:
"""Test path inequality."""
def __lt__(self, other: Path) -> bool:
"""Compare paths."""
def __le__(self, other: Path) -> bool:
"""Compare paths."""
def __gt__(self, other: Path) -> bool:
"""Compare paths."""
def __ge__(self, other: Path) -> bool:
"""Compare paths."""class Matrix:
def __init__(self, xx: float = 1.0, yx: float = 0.0, xy: float = 0.0, yy: float = 1.0, x0: float = 0.0, y0: float = 0.0) -> None:
"""Create transformation matrix.
Args:
xx: X scaling component
yx: Y skewing component
xy: X skewing component
yy: Y scaling component
x0: X translation component
y0: Y translation component
"""
@classmethod
def init_identity(cls) -> Matrix:
"""Create identity matrix."""
@classmethod
def init_translate(cls, tx: float, ty: float) -> Matrix:
"""Create translation matrix."""
@classmethod
def init_scale(cls, sx: float, sy: float) -> Matrix:
"""Create scaling matrix."""
@classmethod
def init_rotate(cls, radians: float) -> Matrix:
"""Create rotation matrix."""
def translate(self, tx: float, ty: float) -> None:
"""Apply translation to matrix."""
def scale(self, sx: float, sy: float) -> None:
"""Apply scaling to matrix."""
def rotate(self, radians: float) -> None:
"""Apply rotation to matrix."""
def invert(self) -> None:
"""Invert the matrix in-place."""
def multiply(self, other: Matrix) -> Matrix:
"""Multiply this matrix by another."""
def transform_distance(self, dx: float, dy: float) -> tuple[float, float]:
"""Transform distance vector (ignores translation)."""
def transform_point(self, x: float, y: float) -> tuple[float, float]:
"""Transform point coordinates."""
@property
def xx(self) -> float:
"""X scaling component."""
@xx.setter
def xx(self, value: float) -> None:
"""Set X scaling component."""
@property
def yx(self) -> float:
"""Y skewing component."""
@yx.setter
def yx(self, value: float) -> None:
"""Set Y skewing component."""
@property
def xy(self) -> float:
"""X skewing component."""
@xy.setter
def xy(self, value: float) -> None:
"""Set X skewing component."""
@property
def yy(self) -> float:
"""Y scaling component."""
@yy.setter
def yy(self, value: float) -> None:
"""Set Y scaling component."""
@property
def x0(self) -> float:
"""X translation component."""
@x0.setter
def x0(self, value: float) -> None:
"""Set X translation component."""
@property
def y0(self) -> float:
"""Y translation component."""
@y0.setter
def y0(self, value: float) -> None:
"""Set Y translation component."""class Rectangle:
def __init__(self, x: float, y: float, width: float, height: float) -> None:
"""Create rectangle.
Args:
x: X coordinate of left edge
y: Y coordinate of top edge
width: Rectangle width
height: Rectangle height
"""
@property
def x(self) -> float:
"""X coordinate of left edge."""
@property
def y(self) -> float:
"""Y coordinate of top edge."""
@property
def width(self) -> float:
"""Rectangle width."""
@property
def height(self) -> float:
"""Rectangle height."""
class RectangleInt:
def __init__(self, x: int, y: int, width: int, height: int) -> None:
"""Create integer rectangle.
Args:
x: X coordinate of left edge
y: Y coordinate of top edge
width: Rectangle width
height: Rectangle height
"""
@property
def x(self) -> int:
"""X coordinate of left edge."""
@property
def y(self) -> int:
"""Y coordinate of top edge."""
@property
def width(self) -> int:
"""Rectangle width."""
@property
def height(self) -> int:
"""Rectangle height."""class Region:
def __init__(self) -> None:
"""Create empty region."""
@classmethod
def create_rectangle(cls, rectangle: RectangleInt) -> Region:
"""Create region from rectangle."""
@classmethod
def create_rectangles(cls, rectangles: list[RectangleInt]) -> Region:
"""Create region from list of rectangles."""
def copy(self) -> Region:
"""Create copy of region."""
def get_extents(self) -> RectangleInt:
"""Get bounding rectangle of region."""
def num_rectangles(self) -> int:
"""Get number of rectangles in region."""
def get_rectangle(self, nth: int) -> RectangleInt:
"""Get nth rectangle in region."""
def is_empty(self) -> bool:
"""Check if region is empty."""
def translate(self, dx: int, dy: int) -> None:
"""Translate region by given offset."""
def intersect(self, other: Region) -> RegionOverlap:
"""Intersect this region with another."""
def intersect_rectangle(self, rectangle: RectangleInt) -> RegionOverlap:
"""Intersect region with rectangle."""
def subtract(self, other: Region) -> None:
"""Subtract another region from this region."""
def subtract_rectangle(self, rectangle: RectangleInt) -> None:
"""Subtract rectangle from region."""
def union(self, other: Region) -> None:
"""Unite this region with another."""
def union_rectangle(self, rectangle: RectangleInt) -> None:
"""Unite region with rectangle."""
def xor(self, other: Region) -> None:
"""XOR this region with another."""
def xor_rectangle(self, rectangle: RectangleInt) -> None:
"""XOR region with rectangle."""
def contains_point(self, x: int, y: int) -> bool:
"""Check if point is in region."""
def contains_rectangle(self, rectangle: RectangleInt) -> RegionOverlap:
"""Check if rectangle overlaps region."""
def equal(self, other: Region) -> bool:
"""Check if regions are equal."""import cairo
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 400, 300)
ctx = cairo.Context(surface)
# Create a path
ctx.move_to(50, 50)
ctx.line_to(150, 50)
ctx.curve_to(200, 50, 200, 100, 150, 100)
ctx.line_to(50, 100)
ctx.close_path()
# Copy the path for inspection
path = ctx.copy_path()
# Iterate through path elements
print("Path elements:")
for path_type, coords in path:
if path_type == cairo.PATH_MOVE_TO:
print(f"MOVE_TO: {coords}")
elif path_type == cairo.PATH_LINE_TO:
print(f"LINE_TO: {coords}")
elif path_type == cairo.PATH_CURVE_TO:
print(f"CURVE_TO: {coords}")
elif path_type == cairo.PATH_CLOSE_PATH:
print("CLOSE_PATH")
# Use the path
ctx.set_source_rgb(0.8, 0.2, 0.2)
ctx.fill_preserve()
ctx.set_source_rgb(0, 0, 0)
ctx.set_line_width(2)
ctx.stroke()
surface.write_to_png("path_example.png")import cairo
import math
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 400, 300)
ctx = cairo.Context(surface)
# Original shape
def draw_shape(ctx):
ctx.rectangle(0, 0, 60, 40)
ctx.fill()
# Draw original
ctx.set_source_rgb(0.8, 0.2, 0.2)
ctx.save()
ctx.translate(50, 50)
draw_shape(ctx)
ctx.restore()
# Using matrix transformations
matrix = cairo.Matrix()
# Translation
matrix.translate(150, 50)
ctx.save()
ctx.transform(matrix)
ctx.set_source_rgb(0.2, 0.8, 0.2)
draw_shape(ctx)
ctx.restore()
# Rotation
matrix = cairo.Matrix.init_rotate(math.pi / 4)
matrix.translate(250, 80)
ctx.save()
ctx.transform(matrix)
ctx.set_source_rgb(0.2, 0.2, 0.8)
draw_shape(ctx)
ctx.restore()
# Scaling
matrix = cairo.Matrix.init_scale(1.5, 0.8)
matrix.translate(320, 60)
ctx.save()
ctx.transform(matrix)
ctx.set_source_rgb(0.8, 0.8, 0.2)
draw_shape(ctx)
ctx.restore()
# Combined transformations
matrix = cairo.Matrix()
matrix.translate(200, 150)
matrix.rotate(math.pi / 6)
matrix.scale(1.2, 1.2)
ctx.save()
ctx.transform(matrix)
ctx.set_source_rgb(0.8, 0.2, 0.8)
draw_shape(ctx)
ctx.restore()
surface.write_to_png("matrix_transforms.png")import cairo
import math
# Create transformation matrix
matrix = cairo.Matrix()
matrix.translate(100, 100)
matrix.rotate(math.pi / 4)
matrix.scale(2, 1.5)
# Transform points
original_point = (50, 30)
transformed_point = matrix.transform_point(*original_point)
print(f"Original point: {original_point}")
print(f"Transformed point: {transformed_point}")
# Transform distances (no translation)
distance = (20, 15)
transformed_distance = matrix.transform_distance(*distance)
print(f"Original distance: {distance}")
print(f"Transformed distance: {transformed_distance}")
# Matrix inversion
try:
inverse_matrix = cairo.Matrix(matrix.xx, matrix.yx, matrix.xy, matrix.yy, matrix.x0, matrix.y0)
inverse_matrix.invert()
# Transform back
back_to_original = inverse_matrix.transform_point(*transformed_point)
print(f"Back to original: {back_to_original}")
except cairo.Error as e:
print(f"Matrix inversion failed: {e}")import cairo
# Create rectangles
rect1 = cairo.Rectangle(50, 50, 100, 80)
rect2 = cairo.Rectangle(120, 80, 100, 80)
print(f"Rectangle 1: x={rect1.x}, y={rect1.y}, w={rect1.width}, h={rect1.height}")
print(f"Rectangle 2: x={rect2.x}, y={rect2.y}, w={rect2.width}, h={rect2.height}")
# Integer rectangles for regions
int_rect1 = cairo.RectangleInt(10, 10, 50, 40)
int_rect2 = cairo.RectangleInt(40, 30, 50, 40)
# Draw rectangles
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 300, 200)
ctx = cairo.Context(surface)
ctx.set_source_rgba(0.8, 0.2, 0.2, 0.7)
ctx.rectangle(rect1.x, rect1.y, rect1.width, rect1.height)
ctx.fill()
ctx.set_source_rgba(0.2, 0.2, 0.8, 0.7)
ctx.rectangle(rect2.x, rect2.y, rect2.width, rect2.height)
ctx.fill()
surface.write_to_png("rectangles.png")import cairo
# Create regions from rectangles
rect1 = cairo.RectangleInt(20, 20, 60, 40)
rect2 = cairo.RectangleInt(50, 30, 60, 40)
rect3 = cairo.RectangleInt(80, 10, 40, 80)
region1 = cairo.Region.create_rectangle(rect1)
region2 = cairo.Region.create_rectangle(rect2)
region3 = cairo.Region.create_rectangle(rect3)
print(f"Region 1 has {region1.num_rectangles()} rectangles")
print(f"Region 1 extents: {region1.get_extents()}")
# Union operations
union_region = region1.copy()
union_region.union(region2)
union_region.union(region3)
print(f"Union region has {union_region.num_rectangles()} rectangles")
print(f"Union extents: {union_region.get_extents()}")
# Intersection
intersect_region = region1.copy()
overlap = intersect_region.intersect(region2)
print(f"Intersection overlap type: {overlap}")
print(f"Intersection has {intersect_region.num_rectangles()} rectangles")
# Point containment
test_points = [(30, 30), (70, 50), (120, 20)]
for x, y in test_points:
in_region1 = region1.contains_point(x, y)
in_union = union_region.contains_point(x, y)
print(f"Point ({x}, {y}): in region1={in_region1}, in union={in_union}")
# Visualize regions
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 200, 150)
ctx = cairo.Context(surface)
# Draw original rectangles with transparency
ctx.set_source_rgba(0.8, 0.2, 0.2, 0.5)
ctx.rectangle(rect1.x, rect1.y, rect1.width, rect1.height)
ctx.fill()
ctx.set_source_rgba(0.2, 0.8, 0.2, 0.5)
ctx.rectangle(rect2.x, rect2.y, rect2.width, rect2.height)
ctx.fill()
ctx.set_source_rgba(0.2, 0.2, 0.8, 0.5)
ctx.rectangle(rect3.x, rect3.y, rect3.width, rect3.height)
ctx.fill()
# Outline union region
ctx.set_source_rgb(0, 0, 0)
ctx.set_line_width(2)
for i in range(union_region.num_rectangles()):
rect = union_region.get_rectangle(i)
ctx.rectangle(rect.x, rect.y, rect.width, rect.height)
ctx.stroke()
surface.write_to_png("regions.png")import cairo
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 400, 300)
ctx = cairo.Context(surface)
# Create complex path
ctx.move_to(50, 100)
ctx.line_to(100, 50)
ctx.curve_to(150, 50, 200, 100, 150, 150)
ctx.line_to(100, 200)
ctx.curve_to(50, 200, 0, 150, 50, 100)
# Get path extents
x1, y1, x2, y2 = ctx.path_extents()
print(f"Path extents: ({x1}, {y1}) to ({x2}, {y2})")
# Test point containment
test_points = [(75, 100), (125, 75), (200, 100)]
for x, y in test_points:
in_fill = ctx.in_fill(x, y)
in_stroke = ctx.in_stroke(x, y)
print(f"Point ({x}, {y}): in_fill={in_fill}, in_stroke={in_stroke}")
# Mark test points
ctx.save()
ctx.new_path()
ctx.arc(x, y, 3, 0, 2 * 3.14159)
if in_fill:
ctx.set_source_rgb(0, 1, 0) # Green if inside fill
else:
ctx.set_source_rgb(1, 0, 0) # Red if outside
ctx.fill()
ctx.restore()
# Draw the original path
ctx.set_source_rgba(0.2, 0.2, 0.8, 0.5)
ctx.fill_preserve()
ctx.set_source_rgb(0, 0, 0)
ctx.set_line_width(2)
ctx.stroke()
# Draw path extents
ctx.set_source_rgb(0.8, 0.2, 0.2)
ctx.set_line_width(1)
ctx.set_dash([5, 3], 0)
ctx.rectangle(x1, y1, x2 - x1, y2 - y1)
ctx.stroke()
surface.write_to_png("path_analysis.png")Install with Tessl CLI
npx tessl i tessl/pypi-pycairo