0
# Text and Fonts
1
2
Cairo provides comprehensive text rendering capabilities with support for multiple font backends, advanced typography features, and precise text measurement. The text system includes font selection, rendering options, glyph-level control, and metrics calculation for professional typography and layout applications.
3
4
## Capabilities
5
6
### Font Face Management
7
8
```python { .api }
9
class FontFace:
10
def __init__(self) -> None:
11
"""Base font face class (typically not instantiated directly)."""
12
13
def get_type(self) -> FontType:
14
"""Get font face type (TOY, FT, WIN32, QUARTZ, USER, DWRITE)."""
15
16
class ToyFontFace(FontFace):
17
def __init__(self, family: str, slant: FontSlant = FontSlant.NORMAL, weight: FontWeight = FontWeight.NORMAL) -> None:
18
"""Create simple font face from family name.
19
20
Args:
21
family: Font family name (e.g., "Arial", "Times New Roman")
22
slant: Font slant (NORMAL, ITALIC, OBLIQUE)
23
weight: Font weight (NORMAL, BOLD)
24
"""
25
26
def get_family(self) -> str:
27
"""Get font family name."""
28
29
def get_slant(self) -> FontSlant:
30
"""Get font slant."""
31
32
def get_weight(self) -> FontWeight:
33
"""Get font weight."""
34
```
35
36
### Font Rendering Options
37
38
```python { .api }
39
class FontOptions:
40
def __init__(self) -> None:
41
"""Create font options with default settings."""
42
43
def copy(self) -> FontOptions:
44
"""Create copy of font options."""
45
46
def merge(self, other: FontOptions) -> None:
47
"""Merge another font options into this one."""
48
49
def hash(self) -> int:
50
"""Get hash value for font options."""
51
52
def equal(self, other: FontOptions) -> bool:
53
"""Check equality with another font options."""
54
55
def set_antialias(self, antialias: Antialias) -> None:
56
"""Set antialiasing mode for font rendering."""
57
58
def get_antialias(self) -> Antialias:
59
"""Get antialiasing mode."""
60
61
def set_subpixel_order(self, subpixel_order: SubpixelOrder) -> None:
62
"""Set subpixel order for LCD text rendering."""
63
64
def get_subpixel_order(self) -> SubpixelOrder:
65
"""Get subpixel order."""
66
67
def set_hint_style(self, hint_style: HintStyle) -> None:
68
"""Set font hinting style."""
69
70
def get_hint_style(self) -> HintStyle:
71
"""Get font hinting style."""
72
73
def set_hint_metrics(self, hint_metrics: HintMetrics) -> None:
74
"""Set font metrics hinting."""
75
76
def get_hint_metrics(self) -> HintMetrics:
77
"""Get font metrics hinting."""
78
79
def set_variations(self, variations: str) -> None:
80
"""Set font variations string for variable fonts."""
81
82
def get_variations(self) -> str:
83
"""Get font variations string."""
84
85
def set_color_mode(self, color_mode: ColorMode) -> None:
86
"""Set color font rendering mode."""
87
88
def get_color_mode(self) -> ColorMode:
89
"""Get color font rendering mode."""
90
91
def set_color_palette(self, palette_index: int) -> None:
92
"""Set color palette index for color fonts."""
93
94
def get_color_palette(self) -> int:
95
"""Get color palette index."""
96
97
def set_custom_palette_color(self, index: int, red: float, green: float, blue: float, alpha: float) -> None:
98
"""Set custom color for palette entry."""
99
100
def get_custom_palette_color(self, index: int) -> tuple[float, float, float, float]:
101
"""Get custom color for palette entry."""
102
```
103
104
### Scaled Fonts
105
106
```python { .api }
107
class ScaledFont:
108
def __init__(self, font_face: FontFace, font_matrix: Matrix, ctm: Matrix, options: FontOptions) -> None:
109
"""Create scaled font from font face and matrices.
110
111
Args:
112
font_face: Font face to scale
113
font_matrix: Font transformation matrix
114
ctm: Current transformation matrix
115
options: Font rendering options
116
"""
117
118
def get_type(self) -> FontType:
119
"""Get scaled font type."""
120
121
def get_reference_count(self) -> int:
122
"""Get reference count."""
123
124
def get_font_face(self) -> FontFace:
125
"""Get underlying font face."""
126
127
def get_font_options(self) -> FontOptions:
128
"""Get font options."""
129
130
def get_font_matrix(self) -> Matrix:
131
"""Get font transformation matrix."""
132
133
def get_ctm(self) -> Matrix:
134
"""Get current transformation matrix."""
135
136
def get_scale_matrix(self) -> Matrix:
137
"""Get combined scale matrix."""
138
139
def extents(self) -> tuple[float, float, float, float, float]:
140
"""Get font extents (ascent, descent, height, max_x_advance, max_y_advance)."""
141
142
def text_extents(self, text: str) -> TextExtents:
143
"""Get text extents for given string."""
144
145
def glyph_extents(self, glyphs: list[Glyph]) -> TextExtents:
146
"""Get glyph extents for glyph array."""
147
148
def text_to_glyphs(self, x: float, y: float, text: str) -> tuple[list[Glyph], list[TextCluster]]:
149
"""Convert text to glyphs with cluster information."""
150
151
def get_color_palette(self, palette_index: int) -> list[tuple[float, float, float, float]]:
152
"""Get color palette for color fonts."""
153
154
def get_type(self) -> FontType:
155
"""Get scaled font type."""
156
157
def get_reference_count(self) -> int:
158
"""Get reference count for the scaled font."""
159
```
160
161
### Text Measurement Classes
162
163
```python { .api }
164
class TextExtents:
165
def __init__(self, x_bearing: float, y_bearing: float, width: float, height: float, x_advance: float, y_advance: float) -> None:
166
"""Text measurement data.
167
168
Args:
169
x_bearing: Horizontal distance from origin to leftmost part
170
y_bearing: Vertical distance from origin to topmost part
171
width: Width of text
172
height: Height of text
173
x_advance: Horizontal advance to next glyph position
174
y_advance: Vertical advance to next glyph position
175
"""
176
177
@property
178
def x_bearing(self) -> float:
179
"""X bearing value."""
180
181
@property
182
def y_bearing(self) -> float:
183
"""Y bearing value."""
184
185
@property
186
def width(self) -> float:
187
"""Text width."""
188
189
@property
190
def height(self) -> float:
191
"""Text height."""
192
193
@property
194
def x_advance(self) -> float:
195
"""X advance value."""
196
197
@property
198
def y_advance(self) -> float:
199
"""Y advance value."""
200
201
class Glyph:
202
def __init__(self, index: int, x: float, y: float) -> None:
203
"""Glyph positioning data.
204
205
Args:
206
index: Glyph index in font
207
x: X coordinate for glyph placement
208
y: Y coordinate for glyph placement
209
"""
210
211
@property
212
def index(self) -> int:
213
"""Glyph index."""
214
215
@property
216
def x(self) -> float:
217
"""X coordinate."""
218
219
@property
220
def y(self) -> float:
221
"""Y coordinate."""
222
223
class TextCluster:
224
def __init__(self, num_bytes: int, num_glyphs: int) -> None:
225
"""Text cluster mapping data.
226
227
Args:
228
num_bytes: Number of UTF-8 bytes in cluster
229
num_glyphs: Number of glyphs in cluster
230
"""
231
232
@property
233
def num_bytes(self) -> int:
234
"""Number of bytes."""
235
236
@property
237
def num_glyphs(self) -> int:
238
"""Number of glyphs."""
239
```
240
241
### Font Type Enumeration
242
243
```python { .api }
244
class FontType:
245
"""Font face implementation types."""
246
TOY: int = 0 # Simple toy font
247
FT: int = 1 # FreeType font
248
WIN32: int = 2 # Win32 font
249
QUARTZ: int = 3 # Quartz font
250
USER: int = 4 # User font
251
DWRITE: int = 5 # DirectWrite font
252
```
253
254
## Context Text Methods
255
256
Text rendering methods available on Context objects:
257
258
```python { .api }
259
# Font selection and configuration
260
def select_font_face(self, family: str, slant: FontSlant, weight: FontWeight) -> None:
261
"""Select font face by family name and style."""
262
263
def set_font_size(self, size: float) -> None:
264
"""Set font size in user units."""
265
266
def set_font_matrix(self, matrix: Matrix) -> None:
267
"""Set font transformation matrix."""
268
269
def get_font_matrix(self) -> Matrix:
270
"""Get font transformation matrix."""
271
272
def set_font_options(self, options: FontOptions) -> None:
273
"""Set font rendering options."""
274
275
def get_font_options(self) -> FontOptions:
276
"""Get font rendering options."""
277
278
def set_font_face(self, font_face: FontFace) -> None:
279
"""Set font face."""
280
281
def get_font_face(self) -> FontFace:
282
"""Get current font face."""
283
284
def set_scaled_font(self, scaled_font: ScaledFont) -> None:
285
"""Set scaled font."""
286
287
def get_scaled_font(self) -> ScaledFont:
288
"""Get current scaled font."""
289
290
# Text rendering
291
def show_text(self, text: str) -> None:
292
"""Render text at current point."""
293
294
def show_glyphs(self, glyphs: list[Glyph]) -> None:
295
"""Render glyphs at specified positions."""
296
297
def show_text_glyphs(self, text: str, glyphs: list[Glyph], clusters: list[TextCluster], cluster_flags: TextClusterFlags) -> None:
298
"""Render text with glyph and cluster information."""
299
300
# Text measurement
301
def text_extents(self, text: str) -> TextExtents:
302
"""Get text extents using current font."""
303
304
def glyph_extents(self, glyphs: list[Glyph]) -> TextExtents:
305
"""Get glyph extents using current font."""
306
307
def font_extents(self) -> tuple[float, float, float, float, float]:
308
"""Get font extents (ascent, descent, height, max_x_advance, max_y_advance)."""
309
310
# Path operations
311
def text_path(self, text: str) -> None:
312
"""Add text outline to current path."""
313
314
def glyph_path(self, glyphs: list[Glyph]) -> None:
315
"""Add glyph outlines to current path."""
316
```
317
318
## Usage Examples
319
320
### Basic Text Rendering
321
322
```python
323
import cairo
324
325
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 500, 300)
326
ctx = cairo.Context(surface)
327
328
# Set background
329
ctx.set_source_rgb(1, 1, 1)
330
ctx.paint()
331
332
# Basic text rendering
333
ctx.set_source_rgb(0, 0, 0)
334
ctx.select_font_face("Arial", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
335
ctx.set_font_size(24)
336
ctx.move_to(50, 50)
337
ctx.show_text("Hello, Cairo!")
338
339
# Different font styles
340
ctx.select_font_face("Arial", cairo.FONT_SLANT_ITALIC, cairo.FONT_WEIGHT_BOLD)
341
ctx.set_font_size(20)
342
ctx.move_to(50, 100)
343
ctx.show_text("Bold italic text")
344
345
# Different colors
346
ctx.set_source_rgb(0.8, 0.2, 0.2)
347
ctx.select_font_face("Times New Roman", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
348
ctx.set_font_size(18)
349
ctx.move_to(50, 150)
350
ctx.show_text("Red Times text")
351
352
surface.write_to_png("basic_text.png")
353
```
354
355
### Font Options and Hinting
356
357
```python
358
import cairo
359
360
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 600, 400)
361
ctx = cairo.Context(surface)
362
363
ctx.set_source_rgb(1, 1, 1)
364
ctx.paint()
365
366
# Create different font options
367
font_options_none = cairo.FontOptions()
368
font_options_none.set_hint_style(cairo.HINT_STYLE_NONE)
369
370
font_options_slight = cairo.FontOptions()
371
font_options_slight.set_hint_style(cairo.HINT_STYLE_SLIGHT)
372
373
font_options_medium = cairo.FontOptions()
374
font_options_medium.set_hint_style(cairo.HINT_STYLE_MEDIUM)
375
376
font_options_full = cairo.FontOptions()
377
font_options_full.set_hint_style(cairo.HINT_STYLE_FULL)
378
379
# Render text with different hinting
380
ctx.set_source_rgb(0, 0, 0)
381
ctx.select_font_face("Arial", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
382
ctx.set_font_size(16)
383
384
y = 50
385
for label, options in [
386
("No hinting", font_options_none),
387
("Slight hinting", font_options_slight),
388
("Medium hinting", font_options_medium),
389
("Full hinting", font_options_full)
390
]:
391
ctx.set_font_options(options)
392
ctx.move_to(50, y)
393
ctx.show_text(f"{label}: The quick brown fox jumps")
394
y += 30
395
396
# Antialiasing options
397
y += 30
398
for antialias in [cairo.ANTIALIAS_NONE, cairo.ANTIALIAS_GRAY, cairo.ANTIALIAS_SUBPIXEL]:
399
font_options = cairo.FontOptions()
400
font_options.set_antialias(antialias)
401
ctx.set_font_options(font_options)
402
ctx.move_to(50, y)
403
ctx.show_text(f"Antialias {antialias}: Sample text")
404
y += 30
405
406
surface.write_to_png("font_options.png")
407
```
408
409
### Text Measurement and Layout
410
411
```python
412
import cairo
413
414
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 500, 300)
415
ctx = cairo.Context(surface)
416
417
ctx.set_source_rgb(1, 1, 1)
418
ctx.paint()
419
420
# Set up font
421
ctx.select_font_face("Arial", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
422
ctx.set_font_size(24)
423
424
text = "Cairo Text"
425
426
# Get text extents
427
text_extents = ctx.text_extents(text)
428
font_ascent, font_descent, font_height, font_max_x_advance, font_max_y_advance = ctx.font_extents()
429
430
print(f"Text extents:")
431
print(f" x_bearing: {text_extents.x_bearing}")
432
print(f" y_bearing: {text_extents.y_bearing}")
433
print(f" width: {text_extents.width}")
434
print(f" height: {text_extents.height}")
435
print(f" x_advance: {text_extents.x_advance}")
436
print(f" y_advance: {text_extents.y_advance}")
437
438
print(f"Font extents:")
439
print(f" ascent: {font_ascent}")
440
print(f" descent: {font_descent}")
441
print(f" height: {font_height}")
442
443
# Position text
444
x, y = 100, 150
445
ctx.move_to(x, y)
446
447
# Draw text
448
ctx.set_source_rgb(0, 0, 0)
449
ctx.show_text(text)
450
451
# Draw text extents box
452
ctx.set_source_rgba(0.8, 0.2, 0.2, 0.5)
453
ctx.rectangle(x + text_extents.x_bearing, y + text_extents.y_bearing,
454
text_extents.width, text_extents.height)
455
ctx.fill()
456
457
# Draw baseline
458
ctx.set_source_rgb(0, 0.8, 0)
459
ctx.set_line_width(1)
460
ctx.move_to(x - 20, y)
461
ctx.line_to(x + text_extents.x_advance + 20, y)
462
ctx.stroke()
463
464
# Draw advance point
465
ctx.set_source_rgb(0, 0, 0.8)
466
ctx.arc(x + text_extents.x_advance, y, 3, 0, 2 * 3.14159)
467
ctx.fill()
468
469
# Right-aligned text using extents
470
right_text = "Right aligned"
471
right_extents = ctx.text_extents(right_text)
472
ctx.move_to(450 - right_extents.width, 200)
473
ctx.set_source_rgb(0.4, 0.4, 0.4)
474
ctx.show_text(right_text)
475
476
surface.write_to_png("text_measurement.png")
477
```
478
479
### Text Paths and Effects
480
481
```python
482
import cairo
483
484
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 500, 300)
485
ctx = cairo.Context(surface)
486
487
ctx.set_source_rgb(1, 1, 1)
488
ctx.paint()
489
490
# Create text path
491
ctx.select_font_face("Arial", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD)
492
ctx.set_font_size(48)
493
ctx.move_to(50, 100)
494
ctx.text_path("TEXT PATH")
495
496
# Fill with gradient
497
gradient = cairo.LinearGradient(50, 60, 400, 60)
498
gradient.add_color_stop_rgb(0, 1, 0, 0)
499
gradient.add_color_stop_rgb(0.5, 1, 1, 0)
500
gradient.add_color_stop_rgb(1, 0, 0, 1)
501
502
ctx.set_source(gradient)
503
ctx.fill_preserve()
504
505
# Stroke outline
506
ctx.set_source_rgb(0, 0, 0)
507
ctx.set_line_width(2)
508
ctx.stroke()
509
510
# Outlined text effect
511
ctx.move_to(50, 180)
512
ctx.text_path("OUTLINED")
513
514
# Fill
515
ctx.set_source_rgb(1, 1, 1)
516
ctx.fill_preserve()
517
518
# Stroke
519
ctx.set_source_rgb(0.2, 0.2, 0.8)
520
ctx.set_line_width(3)
521
ctx.stroke()
522
523
# Shadow effect
524
shadow_text = "SHADOW"
525
ctx.select_font_face("Arial", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD)
526
ctx.set_font_size(36)
527
528
# Draw shadow
529
ctx.move_to(52, 242)
530
ctx.set_source_rgba(0, 0, 0, 0.5)
531
ctx.show_text(shadow_text)
532
533
# Draw main text
534
ctx.move_to(50, 240)
535
ctx.set_source_rgb(0.8, 0.2, 0.2)
536
ctx.show_text(shadow_text)
537
538
surface.write_to_png("text_effects.png")
539
```
540
541
### Advanced Font Features
542
543
```python
544
import cairo
545
546
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 600, 400)
547
ctx = cairo.Context(surface)
548
549
ctx.set_source_rgb(1, 1, 1)
550
ctx.paint()
551
552
# Using ToyFontFace
553
toy_font = cairo.ToyFontFace("Georgia", cairo.FONT_SLANT_ITALIC, cairo.FONT_WEIGHT_BOLD)
554
ctx.set_font_face(toy_font)
555
ctx.set_font_size(24)
556
ctx.move_to(50, 50)
557
ctx.set_source_rgb(0, 0, 0)
558
ctx.show_text("ToyFontFace: Georgia Bold Italic")
559
560
# Font matrix transformation
561
matrix = cairo.Matrix()
562
matrix.scale(1.5, 0.8) # Stretch horizontally, compress vertically
563
matrix.rotate(0.1) # Slight rotation
564
565
ctx.set_font_matrix(matrix)
566
ctx.move_to(50, 100)
567
ctx.show_text("Transformed font matrix")
568
569
# Reset font matrix
570
ctx.set_font_matrix(cairo.Matrix())
571
572
# Working with ScaledFont
573
font_face = cairo.ToyFontFace("Arial")
574
font_matrix = cairo.Matrix()
575
font_matrix.scale(20, 20)
576
ctm = cairo.Matrix()
577
options = cairo.FontOptions()
578
579
scaled_font = cairo.ScaledFont(font_face, font_matrix, ctm, options)
580
ctx.set_scaled_font(scaled_font)
581
582
ctx.move_to(50, 150)
583
ctx.show_text("ScaledFont example")
584
585
# Get font extents from scaled font
586
font_extents = scaled_font.extents()
587
print(f"ScaledFont extents: ascent={font_extents[0]}, descent={font_extents[1]}")
588
589
# Text to glyphs conversion (if supported)
590
try:
591
text = "Hello"
592
glyphs, clusters = scaled_font.text_to_glyphs(50, 200, text)
593
594
# Render using glyphs directly
595
ctx.move_to(50, 200)
596
ctx.show_glyphs(glyphs)
597
598
print(f"Text '{text}' converted to {len(glyphs)} glyphs and {len(clusters)} clusters")
599
600
except cairo.Error as e:
601
print(f"Glyph conversion not supported: {e}")
602
ctx.move_to(50, 200)
603
ctx.show_text("Glyph conversion not available")
604
605
surface.write_to_png("advanced_fonts.png")
606
```
607
608
### Multi-line Text Layout
609
610
```python
611
import cairo
612
613
def draw_multiline_text(ctx, text, x, y, line_height):
614
"""Draw multi-line text with proper line spacing."""
615
lines = text.split('\n')
616
for i, line in enumerate(lines):
617
ctx.move_to(x, y + i * line_height)
618
ctx.show_text(line)
619
620
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 500, 400)
621
ctx = cairo.Context(surface)
622
623
ctx.set_source_rgb(1, 1, 1)
624
ctx.paint()
625
626
# Multi-line text example
627
ctx.select_font_face("Arial", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
628
ctx.set_font_size(16)
629
ctx.set_source_rgb(0, 0, 0)
630
631
multiline_text = """This is a multi-line text example.
632
Each line is drawn separately using
633
the line height calculated from font
634
extents to ensure proper spacing
635
between lines."""
636
637
# Get font metrics for line spacing
638
font_ascent, font_descent, font_height, _, _ = ctx.font_extents()
639
line_height = font_height + 2 # Small extra spacing
640
641
draw_multiline_text(ctx, multiline_text, 50, 50, line_height)
642
643
# Justified text example
644
ctx.set_font_size(14)
645
words = ["The", "quick", "brown", "fox", "jumps", "over", "lazy", "dog"]
646
line_width = 300
647
x_start = 50
648
y_start = 200
649
650
# Calculate word spacing for justification
651
total_word_width = sum(ctx.text_extents(word).width for word in words)
652
space_width = ctx.text_extents(" ").width
653
available_space = line_width - total_word_width
654
extra_space_per_gap = available_space / (len(words) - 1) if len(words) > 1 else 0
655
656
# Draw justified text
657
x = x_start
658
for i, word in enumerate(words):
659
ctx.move_to(x, y_start)
660
ctx.show_text(word)
661
662
word_width = ctx.text_extents(word).width
663
x += word_width
664
665
if i < len(words) - 1: # Not the last word
666
x += space_width + extra_space_per_gap
667
668
# Draw bounding box for justified text
669
ctx.set_source_rgba(0.8, 0.2, 0.2, 0.3)
670
ctx.rectangle(x_start, y_start - font_ascent, line_width, font_height)
671
ctx.fill()
672
673
surface.write_to_png("multiline_text.png")
674
```