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

geometry.mddocs/

Geometry

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.

Capabilities

Path 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."""

Transformation Matrices

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."""

Rectangles

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."""

Regions

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."""

Usage Examples

Working with Paths

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")

Matrix Transformations

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")

Point and Distance Transformations

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}")

Working with Rectangles

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")

Region Operations

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")

Advanced Path Operations

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

docs

constants-enums.md

drawing-context.md

geometry.md

index.md

patterns.md

surfaces.md

text-fonts.md

tile.json