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
```