or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

annotations-forms.mddocument-creation-modification.mddocument-operations.mddocument-rendering.mdgeometry-transformations.mdindex.mdpage-content-extraction.mdtable-extraction.md

geometry-transformations.mddocs/

0

# Geometry and Transformations

1

2

Coordinate system handling with matrices, rectangles, points, and quads for precise positioning and transformations. PyMuPDF provides comprehensive geometry classes essential for layout manipulation, coordinate calculations, and spatial operations.

3

4

## Capabilities

5

6

### Matrix Transformations

7

8

2D transformation matrices for scaling, rotation, translation, and general coordinate transformations.

9

10

```python { .api }

11

class Matrix:

12

def __init__(self, a: float = 1.0, b: float = 0.0, c: float = 0.0,

13

d: float = 1.0, e: float = 0.0, f: float = 0.0):

14

"""

15

Create transformation matrix.

16

17

Parameters:

18

- a, b, c, d, e, f: matrix coefficients [a c e; b d f; 0 0 1]

19

Default creates identity matrix

20

"""

21

22

def prerotate(self, deg: float) -> Matrix:

23

"""

24

Pre-multiply with rotation matrix.

25

26

Parameters:

27

- deg: rotation angle in degrees (counterclockwise)

28

29

Returns:

30

Self for method chaining

31

"""

32

33

def prescale(self, sx: float, sy: float = None) -> Matrix:

34

"""

35

Pre-multiply with scaling matrix.

36

37

Parameters:

38

- sx: x-direction scale factor

39

- sy: y-direction scale factor (defaults to sx for uniform scaling)

40

41

Returns:

42

Self for method chaining

43

"""

44

45

def pretranslate(self, tx: float, ty: float) -> Matrix:

46

"""

47

Pre-multiply with translation matrix.

48

49

Parameters:

50

- tx: x-direction translation

51

- ty: y-direction translation

52

53

Returns:

54

Self for method chaining

55

"""

56

57

def preshear(self, sx: float, sy: float) -> Matrix:

58

"""

59

Pre-multiply with shear matrix.

60

61

Parameters:

62

- sx: x-direction shear factor

63

- sy: y-direction shear factor

64

65

Returns:

66

Self for method chaining

67

"""

68

69

def concat(self, matrix: Matrix) -> Matrix:

70

"""

71

Concatenate with another matrix.

72

73

Parameters:

74

- matrix: matrix to concatenate

75

76

Returns:

77

Self for method chaining

78

"""

79

80

def invert(self) -> Matrix:

81

"""

82

Invert the matrix.

83

84

Returns:

85

Inverted Matrix object

86

87

Raises:

88

RuntimeError if matrix is not invertible

89

"""

90

91

def norm(self) -> float:

92

"""

93

Calculate matrix norm (Euclidean length).

94

95

Returns:

96

Matrix norm value

97

"""

98

99

@property

100

def a(self) -> float:

101

"""Matrix coefficient a (x-scale)."""

102

103

@property

104

def b(self) -> float:

105

"""Matrix coefficient b (y-skew)."""

106

107

@property

108

def c(self) -> float:

109

"""Matrix coefficient c (x-skew)."""

110

111

@property

112

def d(self) -> float:

113

"""Matrix coefficient d (y-scale)."""

114

115

@property

116

def e(self) -> float:

117

"""Matrix coefficient e (x-translation)."""

118

119

@property

120

def f(self) -> float:

121

"""Matrix coefficient f (y-translation)."""

122

123

@property

124

def is_rectilinear(self) -> bool:

125

"""True if matrix preserves axis alignment."""

126

127

# Identity matrix constant

128

IdentityMatrix: Matrix

129

```

130

131

### Rectangle Operations

132

133

Rectangle representation with comprehensive geometric operations.

134

135

```python { .api }

136

class Rect:

137

def __init__(self, x0: float, y0: float, x1: float, y1: float):

138

"""

139

Create rectangle from coordinates.

140

141

Parameters:

142

- x0: left coordinate

143

- y0: top coordinate

144

- x1: right coordinate

145

- y1: bottom coordinate

146

"""

147

148

def normalize(self) -> Rect:

149

"""

150

Normalize rectangle (ensure x0 <= x1 and y0 <= y1).

151

152

Returns:

153

Self for method chaining

154

"""

155

156

def transform(self, matrix: Matrix) -> Rect:

157

"""

158

Transform rectangle by matrix.

159

160

Parameters:

161

- matrix: transformation matrix

162

163

Returns:

164

New transformed Rect object

165

"""

166

167

def intersect(self, rect: Rect) -> Rect:

168

"""

169

Calculate intersection with another rectangle.

170

171

Parameters:

172

- rect: rectangle to intersect with

173

174

Returns:

175

Intersection Rect (may be empty)

176

"""

177

178

def include_point(self, point: Point) -> Rect:

179

"""

180

Expand rectangle to include point.

181

182

Parameters:

183

- point: point to include

184

185

Returns:

186

Self for method chaining

187

"""

188

189

def include_rect(self, rect: Rect) -> Rect:

190

"""

191

Expand rectangle to include another rectangle.

192

193

Parameters:

194

- rect: rectangle to include

195

196

Returns:

197

Self for method chaining

198

"""

199

200

def round(self) -> IRect:

201

"""

202

Round coordinates to integers.

203

204

Returns:

205

IRect with integer coordinates

206

"""

207

208

def morph(self, fixpoint: Point, matrix: Matrix) -> Rect:

209

"""

210

Transform around a fixed point.

211

212

Parameters:

213

- fixpoint: fixed point for transformation

214

- matrix: transformation matrix

215

216

Returns:

217

New transformed Rect object

218

"""

219

220

@property

221

def x0(self) -> float:

222

"""Left coordinate."""

223

224

@property

225

def y0(self) -> float:

226

"""Top coordinate."""

227

228

@property

229

def x1(self) -> float:

230

"""Right coordinate."""

231

232

@property

233

def y1(self) -> float:

234

"""Bottom coordinate."""

235

236

@property

237

def width(self) -> float:

238

"""Rectangle width."""

239

240

@property

241

def height(self) -> float:

242

"""Rectangle height."""

243

244

@property

245

def tl(self) -> Point:

246

"""Top-left corner point."""

247

248

@property

249

def tr(self) -> Point:

250

"""Top-right corner point."""

251

252

@property

253

def bl(self) -> Point:

254

"""Bottom-left corner point."""

255

256

@property

257

def br(self) -> Point:

258

"""Bottom-right corner point."""

259

260

@property

261

def quad(self) -> Quad:

262

"""Rectangle as Quad object."""

263

264

@property

265

def is_empty(self) -> bool:

266

"""True if rectangle is empty."""

267

268

@property

269

def is_infinite(self) -> bool:

270

"""True if rectangle is infinite."""

271

272

# Rectangle constants

273

EMPTY_RECT: Rect # Empty rectangle

274

INFINITE_RECT: Rect # Infinite rectangle

275

```

276

277

### Integer Rectangle Operations

278

279

Integer-coordinate rectangle for pixel-perfect operations.

280

281

```python { .api }

282

class IRect:

283

def __init__(self, x0: int, y0: int, x1: int, y1: int):

284

"""

285

Create integer rectangle.

286

287

Parameters:

288

- x0: left coordinate

289

- y0: top coordinate

290

- x1: right coordinate

291

- y1: bottom coordinate

292

"""

293

294

def normalize(self) -> IRect:

295

"""

296

Normalize rectangle coordinates.

297

298

Returns:

299

Self for method chaining

300

"""

301

302

def intersect(self, irect: IRect) -> IRect:

303

"""

304

Calculate intersection with another integer rectangle.

305

306

Parameters:

307

- irect: rectangle to intersect with

308

309

Returns:

310

Intersection IRect

311

"""

312

313

def include_point(self, point: Point) -> IRect:

314

"""

315

Expand to include point.

316

317

Parameters:

318

- point: point to include

319

320

Returns:

321

Self for method chaining

322

"""

323

324

def include_rect(self, irect: IRect) -> IRect:

325

"""

326

Expand to include another rectangle.

327

328

Parameters:

329

- irect: rectangle to include

330

331

Returns:

332

Self for method chaining

333

"""

334

335

@property

336

def x0(self) -> int:

337

"""Left coordinate."""

338

339

@property

340

def y0(self) -> int:

341

"""Top coordinate."""

342

343

@property

344

def x1(self) -> int:

345

"""Right coordinate."""

346

347

@property

348

def y1(self) -> int:

349

"""Bottom coordinate."""

350

351

@property

352

def width(self) -> int:

353

"""Rectangle width."""

354

355

@property

356

def height(self) -> int:

357

"""Rectangle height."""

358

359

@property

360

def rect(self) -> Rect:

361

"""Convert to float Rect."""

362

363

@property

364

def is_empty(self) -> bool:

365

"""True if rectangle is empty."""

366

367

@property

368

def is_infinite(self) -> bool:

369

"""True if rectangle is infinite."""

370

371

# Integer rectangle constants

372

EMPTY_IRECT: IRect # Empty integer rectangle

373

INFINITE_IRECT: IRect # Infinite integer rectangle

374

```

375

376

### Point Operations

377

378

2D point representation with distance and transformation capabilities.

379

380

```python { .api }

381

class Point:

382

def __init__(self, x: float, y: float):

383

"""

384

Create point.

385

386

Parameters:

387

- x: x coordinate

388

- y: y coordinate

389

"""

390

391

def distance_to(self, point: Point) -> float:

392

"""

393

Calculate distance to another point.

394

395

Parameters:

396

- point: target point

397

398

Returns:

399

Euclidean distance

400

"""

401

402

def transform(self, matrix: Matrix) -> Point:

403

"""

404

Transform point by matrix.

405

406

Parameters:

407

- matrix: transformation matrix

408

409

Returns:

410

New transformed Point object

411

"""

412

413

def unit_vector(self, point: Point) -> Point:

414

"""

415

Calculate unit vector to another point.

416

417

Parameters:

418

- point: target point

419

420

Returns:

421

Unit vector Point

422

"""

423

424

@property

425

def x(self) -> float:

426

"""X coordinate."""

427

428

@property

429

def y(self) -> float:

430

"""Y coordinate."""

431

```

432

433

### Quadrilateral Operations

434

435

Four-sided polygon representation for text highlighting and selections.

436

437

```python { .api }

438

class Quad:

439

def __init__(self, ul: Point, ur: Point, ll: Point, lr: Point):

440

"""

441

Create quadrilateral from four corner points.

442

443

Parameters:

444

- ul: upper-left point

445

- ur: upper-right point

446

- ll: lower-left point

447

- lr: lower-right point

448

"""

449

450

def transform(self, matrix: Matrix) -> Quad:

451

"""

452

Transform quadrilateral by matrix.

453

454

Parameters:

455

- matrix: transformation matrix

456

457

Returns:

458

New transformed Quad object

459

"""

460

461

def morph(self, fixpoint: Point, matrix: Matrix) -> Quad:

462

"""

463

Transform around fixed point.

464

465

Parameters:

466

- fixpoint: transformation center

467

- matrix: transformation matrix

468

469

Returns:

470

New transformed Quad object

471

"""

472

473

@property

474

def ul(self) -> Point:

475

"""Upper-left corner point."""

476

477

@property

478

def ur(self) -> Point:

479

"""Upper-right corner point."""

480

481

@property

482

def ll(self) -> Point:

483

"""Lower-left corner point."""

484

485

@property

486

def lr(self) -> Point:

487

"""Lower-right corner point."""

488

489

@property

490

def rect(self) -> Rect:

491

"""Bounding rectangle of quadrilateral."""

492

493

@property

494

def is_empty(self) -> bool:

495

"""True if quadrilateral is empty."""

496

497

@property

498

def is_convex(self) -> bool:

499

"""True if quadrilateral is convex."""

500

501

@property

502

def is_rectangular(self) -> bool:

503

"""True if quadrilateral is rectangular."""

504

505

# Quad constants

506

EMPTY_QUAD: Quad # Empty quadrilateral

507

INFINITE_QUAD: Quad # Infinite quadrilateral

508

```

509

510

## Usage Examples

511

512

### Basic Matrix Operations

513

514

```python

515

import pymupdf

516

517

# Create identity matrix

518

mat = pymupdf.Matrix()

519

print(f"Identity: {mat.a}, {mat.d}") # Should be 1, 1

520

521

# Scale by 2x

522

mat.prescale(2.0)

523

print(f"Scaled: {mat.a}, {mat.d}") # Should be 2, 2

524

525

# Rotate by 45 degrees

526

mat.prerotate(45)

527

528

# Translate by (100, 50)

529

mat.pretranslate(100, 50)

530

531

# Transform a point

532

point = pymupdf.Point(0, 0)

533

transformed_point = point.transform(mat)

534

print(f"Transformed point: ({transformed_point.x}, {transformed_point.y})")

535

```

536

537

### Rectangle Manipulations

538

539

```python

540

import pymupdf

541

542

# Create rectangle

543

rect = pymupdf.Rect(10, 10, 100, 50)

544

print(f"Original: {rect.width} x {rect.height}")

545

546

# Scale rectangle

547

scale_matrix = pymupdf.Matrix(2, 1.5) # 2x width, 1.5x height

548

scaled_rect = rect.transform(scale_matrix)

549

print(f"Scaled: {scaled_rect.width} x {scaled_rect.height}")

550

551

# Find intersection

552

rect1 = pymupdf.Rect(0, 0, 100, 100)

553

rect2 = pymupdf.Rect(50, 50, 150, 150)

554

intersection = rect1.intersect(rect2)

555

print(f"Intersection: {intersection}")

556

557

# Include point to expand rectangle

558

rect = pymupdf.Rect(0, 0, 100, 100)

559

point = pymupdf.Point(200, 200)

560

expanded = rect.include_point(point)

561

print(f"Expanded: {expanded}")

562

```

563

564

### Complex Transformations

565

566

```python

567

import pymupdf

568

569

def create_transformation_matrix(scale_x: float, scale_y: float,

570

rotation: float, tx: float, ty: float) -> pymupdf.Matrix:

571

"""Create combined transformation matrix."""

572

mat = pymupdf.Matrix()

573

mat.prescale(scale_x, scale_y)

574

mat.prerotate(rotation)

575

mat.pretranslate(tx, ty)

576

return mat

577

578

# Create complex transformation

579

transform = create_transformation_matrix(

580

scale_x=1.5, scale_y=2.0,

581

rotation=30,

582

tx=100, ty=50

583

)

584

585

# Apply to various geometry objects

586

point = pymupdf.Point(50, 25)

587

rect = pymupdf.Rect(0, 0, 100, 50)

588

quad = rect.quad

589

590

transformed_point = point.transform(transform)

591

transformed_rect = rect.transform(transform)

592

transformed_quad = quad.transform(transform)

593

594

print(f"Original point: ({point.x}, {point.y})")

595

print(f"Transformed point: ({transformed_point.x:.2f}, {transformed_point.y:.2f})")

596

```

597

598

### Working with Page Coordinates

599

600

```python

601

import pymupdf

602

603

doc = pymupdf.open("document.pdf")

604

page = doc.load_page(0)

605

606

# Get page dimensions

607

page_rect = page.rect

608

print(f"Page size: {page_rect.width} x {page_rect.height}")

609

610

# Convert between coordinate systems

611

# PDF coordinates: origin at bottom-left

612

# Screen coordinates: origin at top-left

613

def pdf_to_screen(point: pymupdf.Point, page_height: float) -> pymupdf.Point:

614

"""Convert PDF coordinates to screen coordinates."""

615

return pymupdf.Point(point.x, page_height - point.y)

616

617

def screen_to_pdf(point: pymupdf.Point, page_height: float) -> pymupdf.Point:

618

"""Convert screen coordinates to PDF coordinates."""

619

return pymupdf.Point(point.x, page_height - point.y)

620

621

# Example conversion

622

pdf_point = pymupdf.Point(100, 100)

623

screen_point = pdf_to_screen(pdf_point, page_rect.height)

624

print(f"PDF point: ({pdf_point.x}, {pdf_point.y})")

625

print(f"Screen point: ({screen_point.x}, {screen_point.y})")

626

627

doc.close()

628

```

629

630

### Geometry-Based Text Selection

631

632

```python

633

import pymupdf

634

635

doc = pymupdf.open("document.pdf")

636

page = doc.load_page(0)

637

638

# Search for text and get quads

639

search_term = "important"

640

text_instances = page.search_for(search_term, quads=True)

641

642

for quad in text_instances:

643

print(f"Text quad corners:")

644

print(f" UL: ({quad.ul.x:.1f}, {quad.ul.y:.1f})")

645

print(f" UR: ({quad.ur.x:.1f}, {quad.ur.y:.1f})")

646

print(f" LL: ({quad.ll.x:.1f}, {quad.ll.y:.1f})")

647

print(f" LR: ({quad.lr.x:.1f}, {quad.lr.y:.1f})")

648

649

# Get bounding rectangle

650

bbox = quad.rect

651

print(f" Bounding box: {bbox}")

652

653

# Create highlight annotation

654

highlight = page.add_highlight_annot(quad)

655

highlight.set_colors({"stroke": [1, 1, 0]}) # Yellow

656

highlight.update()

657

658

doc.save("highlighted_text.pdf")

659

doc.close()

660

```

661

662

### Calculating Distances and Areas

663

664

```python

665

import pymupdf

666

667

def calculate_rect_area(rect: pymupdf.Rect) -> float:

668

"""Calculate rectangle area."""

669

return rect.width * rect.height

670

671

def calculate_points_distance(p1: pymupdf.Point, p2: pymupdf.Point) -> float:

672

"""Calculate distance between two points."""

673

return p1.distance_to(p2)

674

675

def calculate_quad_area(quad: pymupdf.Quad) -> float:

676

"""Calculate approximate quadrilateral area using shoelace formula."""

677

points = [quad.ul, quad.ur, quad.lr, quad.ll]

678

area = 0

679

n = len(points)

680

681

for i in range(n):

682

j = (i + 1) % n

683

area += points[i].x * points[j].y

684

area -= points[j].x * points[i].y

685

686

return abs(area) / 2

687

688

# Example calculations

689

rect = pymupdf.Rect(0, 0, 100, 50)

690

print(f"Rectangle area: {calculate_rect_area(rect)}")

691

692

p1 = pymupdf.Point(0, 0)

693

p2 = pymupdf.Point(100, 100)

694

print(f"Distance: {calculate_points_distance(p1, p2):.2f}")

695

696

quad = rect.quad

697

print(f"Quad area: {calculate_quad_area(quad)}")

698

```

699

700

### Matrix Decomposition and Analysis

701

702

```python

703

import pymupdf

704

import math

705

706

def analyze_matrix(matrix: pymupdf.Matrix) -> dict:

707

"""Analyze transformation matrix properties."""

708

# Extract scale factors

709

scale_x = math.sqrt(matrix.a * matrix.a + matrix.b * matrix.b)

710

scale_y = math.sqrt(matrix.c * matrix.c + matrix.d * matrix.d)

711

712

# Extract rotation angle

713

rotation = math.atan2(matrix.b, matrix.a) * 180 / math.pi

714

715

# Extract translation

716

translation_x = matrix.e

717

translation_y = matrix.f

718

719

# Calculate determinant (area scaling factor)

720

determinant = matrix.a * matrix.d - matrix.b * matrix.c

721

722

return {

723

"scale_x": scale_x,

724

"scale_y": scale_y,

725

"rotation": rotation,

726

"translation": (translation_x, translation_y),

727

"determinant": determinant,

728

"is_rectilinear": matrix.is_rectilinear

729

}

730

731

# Create and analyze transformation

732

mat = pymupdf.Matrix()

733

mat.prescale(2, 1.5)

734

mat.prerotate(30)

735

mat.pretranslate(100, 50)

736

737

analysis = analyze_matrix(mat)

738

print("Matrix analysis:")

739

for key, value in analysis.items():

740

print(f" {key}: {value}")

741

```