0
# Geometry
1
2
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.
3
4
## Capabilities
5
6
### Path Data
7
8
```python { .api }
9
class Path:
10
def __iter__(self) -> Iterator[tuple[PathDataType, tuple[float, ...]]]:
11
"""Iterate over path elements.
12
13
Yields:
14
Tuple of (path_data_type, coordinates) where:
15
- MOVE_TO: (x, y)
16
- LINE_TO: (x, y)
17
- CURVE_TO: (x1, y1, x2, y2, x3, y3)
18
- CLOSE_PATH: ()
19
"""
20
21
def __eq__(self, other: object) -> bool:
22
"""Test path equality."""
23
24
def __ne__(self, other: object) -> bool:
25
"""Test path inequality."""
26
27
def __lt__(self, other: Path) -> bool:
28
"""Compare paths."""
29
30
def __le__(self, other: Path) -> bool:
31
"""Compare paths."""
32
33
def __gt__(self, other: Path) -> bool:
34
"""Compare paths."""
35
36
def __ge__(self, other: Path) -> bool:
37
"""Compare paths."""
38
```
39
40
### Transformation Matrices
41
42
```python { .api }
43
class Matrix:
44
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:
45
"""Create transformation matrix.
46
47
Args:
48
xx: X scaling component
49
yx: Y skewing component
50
xy: X skewing component
51
yy: Y scaling component
52
x0: X translation component
53
y0: Y translation component
54
"""
55
56
@classmethod
57
def init_identity(cls) -> Matrix:
58
"""Create identity matrix."""
59
60
@classmethod
61
def init_translate(cls, tx: float, ty: float) -> Matrix:
62
"""Create translation matrix."""
63
64
@classmethod
65
def init_scale(cls, sx: float, sy: float) -> Matrix:
66
"""Create scaling matrix."""
67
68
@classmethod
69
def init_rotate(cls, radians: float) -> Matrix:
70
"""Create rotation matrix."""
71
72
def translate(self, tx: float, ty: float) -> None:
73
"""Apply translation to matrix."""
74
75
def scale(self, sx: float, sy: float) -> None:
76
"""Apply scaling to matrix."""
77
78
def rotate(self, radians: float) -> None:
79
"""Apply rotation to matrix."""
80
81
def invert(self) -> None:
82
"""Invert the matrix in-place."""
83
84
def multiply(self, other: Matrix) -> Matrix:
85
"""Multiply this matrix by another."""
86
87
def transform_distance(self, dx: float, dy: float) -> tuple[float, float]:
88
"""Transform distance vector (ignores translation)."""
89
90
def transform_point(self, x: float, y: float) -> tuple[float, float]:
91
"""Transform point coordinates."""
92
93
@property
94
def xx(self) -> float:
95
"""X scaling component."""
96
97
@xx.setter
98
def xx(self, value: float) -> None:
99
"""Set X scaling component."""
100
101
@property
102
def yx(self) -> float:
103
"""Y skewing component."""
104
105
@yx.setter
106
def yx(self, value: float) -> None:
107
"""Set Y skewing component."""
108
109
@property
110
def xy(self) -> float:
111
"""X skewing component."""
112
113
@xy.setter
114
def xy(self, value: float) -> None:
115
"""Set X skewing component."""
116
117
@property
118
def yy(self) -> float:
119
"""Y scaling component."""
120
121
@yy.setter
122
def yy(self, value: float) -> None:
123
"""Set Y scaling component."""
124
125
@property
126
def x0(self) -> float:
127
"""X translation component."""
128
129
@x0.setter
130
def x0(self, value: float) -> None:
131
"""Set X translation component."""
132
133
@property
134
def y0(self) -> float:
135
"""Y translation component."""
136
137
@y0.setter
138
def y0(self, value: float) -> None:
139
"""Set Y translation component."""
140
```
141
142
### Rectangles
143
144
```python { .api }
145
class Rectangle:
146
def __init__(self, x: float, y: float, width: float, height: float) -> None:
147
"""Create rectangle.
148
149
Args:
150
x: X coordinate of left edge
151
y: Y coordinate of top edge
152
width: Rectangle width
153
height: Rectangle height
154
"""
155
156
@property
157
def x(self) -> float:
158
"""X coordinate of left edge."""
159
160
@property
161
def y(self) -> float:
162
"""Y coordinate of top edge."""
163
164
@property
165
def width(self) -> float:
166
"""Rectangle width."""
167
168
@property
169
def height(self) -> float:
170
"""Rectangle height."""
171
172
class RectangleInt:
173
def __init__(self, x: int, y: int, width: int, height: int) -> None:
174
"""Create integer rectangle.
175
176
Args:
177
x: X coordinate of left edge
178
y: Y coordinate of top edge
179
width: Rectangle width
180
height: Rectangle height
181
"""
182
183
@property
184
def x(self) -> int:
185
"""X coordinate of left edge."""
186
187
@property
188
def y(self) -> int:
189
"""Y coordinate of top edge."""
190
191
@property
192
def width(self) -> int:
193
"""Rectangle width."""
194
195
@property
196
def height(self) -> int:
197
"""Rectangle height."""
198
```
199
200
### Regions
201
202
```python { .api }
203
class Region:
204
def __init__(self) -> None:
205
"""Create empty region."""
206
207
@classmethod
208
def create_rectangle(cls, rectangle: RectangleInt) -> Region:
209
"""Create region from rectangle."""
210
211
@classmethod
212
def create_rectangles(cls, rectangles: list[RectangleInt]) -> Region:
213
"""Create region from list of rectangles."""
214
215
def copy(self) -> Region:
216
"""Create copy of region."""
217
218
def get_extents(self) -> RectangleInt:
219
"""Get bounding rectangle of region."""
220
221
def num_rectangles(self) -> int:
222
"""Get number of rectangles in region."""
223
224
def get_rectangle(self, nth: int) -> RectangleInt:
225
"""Get nth rectangle in region."""
226
227
def is_empty(self) -> bool:
228
"""Check if region is empty."""
229
230
def translate(self, dx: int, dy: int) -> None:
231
"""Translate region by given offset."""
232
233
def intersect(self, other: Region) -> RegionOverlap:
234
"""Intersect this region with another."""
235
236
def intersect_rectangle(self, rectangle: RectangleInt) -> RegionOverlap:
237
"""Intersect region with rectangle."""
238
239
def subtract(self, other: Region) -> None:
240
"""Subtract another region from this region."""
241
242
def subtract_rectangle(self, rectangle: RectangleInt) -> None:
243
"""Subtract rectangle from region."""
244
245
def union(self, other: Region) -> None:
246
"""Unite this region with another."""
247
248
def union_rectangle(self, rectangle: RectangleInt) -> None:
249
"""Unite region with rectangle."""
250
251
def xor(self, other: Region) -> None:
252
"""XOR this region with another."""
253
254
def xor_rectangle(self, rectangle: RectangleInt) -> None:
255
"""XOR region with rectangle."""
256
257
def contains_point(self, x: int, y: int) -> bool:
258
"""Check if point is in region."""
259
260
def contains_rectangle(self, rectangle: RectangleInt) -> RegionOverlap:
261
"""Check if rectangle overlaps region."""
262
263
def equal(self, other: Region) -> bool:
264
"""Check if regions are equal."""
265
```
266
267
## Usage Examples
268
269
### Working with Paths
270
271
```python
272
import cairo
273
274
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 400, 300)
275
ctx = cairo.Context(surface)
276
277
# Create a path
278
ctx.move_to(50, 50)
279
ctx.line_to(150, 50)
280
ctx.curve_to(200, 50, 200, 100, 150, 100)
281
ctx.line_to(50, 100)
282
ctx.close_path()
283
284
# Copy the path for inspection
285
path = ctx.copy_path()
286
287
# Iterate through path elements
288
print("Path elements:")
289
for path_type, coords in path:
290
if path_type == cairo.PATH_MOVE_TO:
291
print(f"MOVE_TO: {coords}")
292
elif path_type == cairo.PATH_LINE_TO:
293
print(f"LINE_TO: {coords}")
294
elif path_type == cairo.PATH_CURVE_TO:
295
print(f"CURVE_TO: {coords}")
296
elif path_type == cairo.PATH_CLOSE_PATH:
297
print("CLOSE_PATH")
298
299
# Use the path
300
ctx.set_source_rgb(0.8, 0.2, 0.2)
301
ctx.fill_preserve()
302
ctx.set_source_rgb(0, 0, 0)
303
ctx.set_line_width(2)
304
ctx.stroke()
305
306
surface.write_to_png("path_example.png")
307
```
308
309
### Matrix Transformations
310
311
```python
312
import cairo
313
import math
314
315
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 400, 300)
316
ctx = cairo.Context(surface)
317
318
# Original shape
319
def draw_shape(ctx):
320
ctx.rectangle(0, 0, 60, 40)
321
ctx.fill()
322
323
# Draw original
324
ctx.set_source_rgb(0.8, 0.2, 0.2)
325
ctx.save()
326
ctx.translate(50, 50)
327
draw_shape(ctx)
328
ctx.restore()
329
330
# Using matrix transformations
331
matrix = cairo.Matrix()
332
333
# Translation
334
matrix.translate(150, 50)
335
ctx.save()
336
ctx.transform(matrix)
337
ctx.set_source_rgb(0.2, 0.8, 0.2)
338
draw_shape(ctx)
339
ctx.restore()
340
341
# Rotation
342
matrix = cairo.Matrix.init_rotate(math.pi / 4)
343
matrix.translate(250, 80)
344
ctx.save()
345
ctx.transform(matrix)
346
ctx.set_source_rgb(0.2, 0.2, 0.8)
347
draw_shape(ctx)
348
ctx.restore()
349
350
# Scaling
351
matrix = cairo.Matrix.init_scale(1.5, 0.8)
352
matrix.translate(320, 60)
353
ctx.save()
354
ctx.transform(matrix)
355
ctx.set_source_rgb(0.8, 0.8, 0.2)
356
draw_shape(ctx)
357
ctx.restore()
358
359
# Combined transformations
360
matrix = cairo.Matrix()
361
matrix.translate(200, 150)
362
matrix.rotate(math.pi / 6)
363
matrix.scale(1.2, 1.2)
364
ctx.save()
365
ctx.transform(matrix)
366
ctx.set_source_rgb(0.8, 0.2, 0.8)
367
draw_shape(ctx)
368
ctx.restore()
369
370
surface.write_to_png("matrix_transforms.png")
371
```
372
373
### Point and Distance Transformations
374
375
```python
376
import cairo
377
import math
378
379
# Create transformation matrix
380
matrix = cairo.Matrix()
381
matrix.translate(100, 100)
382
matrix.rotate(math.pi / 4)
383
matrix.scale(2, 1.5)
384
385
# Transform points
386
original_point = (50, 30)
387
transformed_point = matrix.transform_point(*original_point)
388
389
print(f"Original point: {original_point}")
390
print(f"Transformed point: {transformed_point}")
391
392
# Transform distances (no translation)
393
distance = (20, 15)
394
transformed_distance = matrix.transform_distance(*distance)
395
396
print(f"Original distance: {distance}")
397
print(f"Transformed distance: {transformed_distance}")
398
399
# Matrix inversion
400
try:
401
inverse_matrix = cairo.Matrix(matrix.xx, matrix.yx, matrix.xy, matrix.yy, matrix.x0, matrix.y0)
402
inverse_matrix.invert()
403
404
# Transform back
405
back_to_original = inverse_matrix.transform_point(*transformed_point)
406
print(f"Back to original: {back_to_original}")
407
408
except cairo.Error as e:
409
print(f"Matrix inversion failed: {e}")
410
```
411
412
### Working with Rectangles
413
414
```python
415
import cairo
416
417
# Create rectangles
418
rect1 = cairo.Rectangle(50, 50, 100, 80)
419
rect2 = cairo.Rectangle(120, 80, 100, 80)
420
421
print(f"Rectangle 1: x={rect1.x}, y={rect1.y}, w={rect1.width}, h={rect1.height}")
422
print(f"Rectangle 2: x={rect2.x}, y={rect2.y}, w={rect2.width}, h={rect2.height}")
423
424
# Integer rectangles for regions
425
int_rect1 = cairo.RectangleInt(10, 10, 50, 40)
426
int_rect2 = cairo.RectangleInt(40, 30, 50, 40)
427
428
# Draw rectangles
429
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 300, 200)
430
ctx = cairo.Context(surface)
431
432
ctx.set_source_rgba(0.8, 0.2, 0.2, 0.7)
433
ctx.rectangle(rect1.x, rect1.y, rect1.width, rect1.height)
434
ctx.fill()
435
436
ctx.set_source_rgba(0.2, 0.2, 0.8, 0.7)
437
ctx.rectangle(rect2.x, rect2.y, rect2.width, rect2.height)
438
ctx.fill()
439
440
surface.write_to_png("rectangles.png")
441
```
442
443
### Region Operations
444
445
```python
446
import cairo
447
448
# Create regions from rectangles
449
rect1 = cairo.RectangleInt(20, 20, 60, 40)
450
rect2 = cairo.RectangleInt(50, 30, 60, 40)
451
rect3 = cairo.RectangleInt(80, 10, 40, 80)
452
453
region1 = cairo.Region.create_rectangle(rect1)
454
region2 = cairo.Region.create_rectangle(rect2)
455
region3 = cairo.Region.create_rectangle(rect3)
456
457
print(f"Region 1 has {region1.num_rectangles()} rectangles")
458
print(f"Region 1 extents: {region1.get_extents()}")
459
460
# Union operations
461
union_region = region1.copy()
462
union_region.union(region2)
463
union_region.union(region3)
464
465
print(f"Union region has {union_region.num_rectangles()} rectangles")
466
print(f"Union extents: {union_region.get_extents()}")
467
468
# Intersection
469
intersect_region = region1.copy()
470
overlap = intersect_region.intersect(region2)
471
print(f"Intersection overlap type: {overlap}")
472
print(f"Intersection has {intersect_region.num_rectangles()} rectangles")
473
474
# Point containment
475
test_points = [(30, 30), (70, 50), (120, 20)]
476
for x, y in test_points:
477
in_region1 = region1.contains_point(x, y)
478
in_union = union_region.contains_point(x, y)
479
print(f"Point ({x}, {y}): in region1={in_region1}, in union={in_union}")
480
481
# Visualize regions
482
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 200, 150)
483
ctx = cairo.Context(surface)
484
485
# Draw original rectangles with transparency
486
ctx.set_source_rgba(0.8, 0.2, 0.2, 0.5)
487
ctx.rectangle(rect1.x, rect1.y, rect1.width, rect1.height)
488
ctx.fill()
489
490
ctx.set_source_rgba(0.2, 0.8, 0.2, 0.5)
491
ctx.rectangle(rect2.x, rect2.y, rect2.width, rect2.height)
492
ctx.fill()
493
494
ctx.set_source_rgba(0.2, 0.2, 0.8, 0.5)
495
ctx.rectangle(rect3.x, rect3.y, rect3.width, rect3.height)
496
ctx.fill()
497
498
# Outline union region
499
ctx.set_source_rgb(0, 0, 0)
500
ctx.set_line_width(2)
501
for i in range(union_region.num_rectangles()):
502
rect = union_region.get_rectangle(i)
503
ctx.rectangle(rect.x, rect.y, rect.width, rect.height)
504
ctx.stroke()
505
506
surface.write_to_png("regions.png")
507
```
508
509
### Advanced Path Operations
510
511
```python
512
import cairo
513
514
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 400, 300)
515
ctx = cairo.Context(surface)
516
517
# Create complex path
518
ctx.move_to(50, 100)
519
ctx.line_to(100, 50)
520
ctx.curve_to(150, 50, 200, 100, 150, 150)
521
ctx.line_to(100, 200)
522
ctx.curve_to(50, 200, 0, 150, 50, 100)
523
524
# Get path extents
525
x1, y1, x2, y2 = ctx.path_extents()
526
print(f"Path extents: ({x1}, {y1}) to ({x2}, {y2})")
527
528
# Test point containment
529
test_points = [(75, 100), (125, 75), (200, 100)]
530
for x, y in test_points:
531
in_fill = ctx.in_fill(x, y)
532
in_stroke = ctx.in_stroke(x, y)
533
print(f"Point ({x}, {y}): in_fill={in_fill}, in_stroke={in_stroke}")
534
535
# Mark test points
536
ctx.save()
537
ctx.new_path()
538
ctx.arc(x, y, 3, 0, 2 * 3.14159)
539
if in_fill:
540
ctx.set_source_rgb(0, 1, 0) # Green if inside fill
541
else:
542
ctx.set_source_rgb(1, 0, 0) # Red if outside
543
ctx.fill()
544
ctx.restore()
545
546
# Draw the original path
547
ctx.set_source_rgba(0.2, 0.2, 0.8, 0.5)
548
ctx.fill_preserve()
549
ctx.set_source_rgb(0, 0, 0)
550
ctx.set_line_width(2)
551
ctx.stroke()
552
553
# Draw path extents
554
ctx.set_source_rgb(0.8, 0.2, 0.2)
555
ctx.set_line_width(1)
556
ctx.set_dash([5, 3], 0)
557
ctx.rectangle(x1, y1, x2 - x1, y2 - y1)
558
ctx.stroke()
559
560
surface.write_to_png("path_analysis.png")
561
```