or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

constants-enums.mddrawing-context.mdgeometry.mdindex.mdpatterns.mdsurfaces.mdtext-fonts.md

geometry.mddocs/

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

```