or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

color-management.mdcolor-utilities.mdcore-image.mddrawing.mdenhancement.mdfilters.mdfonts.mdimage-sequences.mdimage-statistics.mdindex.mdmath-operations.mdoperations.md

fonts.mddocs/

0

# Font Handling and Text

1

2

TrueType and OpenType font support for rendering text on images with full typography control including size, spacing, alignment, and styling options. The ImageFont module provides comprehensive font loading and text rendering capabilities.

3

4

## Capabilities

5

6

### Font Loading

7

8

Load and manage different types of fonts.

9

10

```python { .api }

11

def load_default(size=None):

12

"""

13

Load the default PIL font.

14

15

Parameters:

16

- size (float): Font size (if None, uses default size)

17

18

Returns:

19

FreeTypeFont | ImageFont: Default font object

20

"""

21

22

def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):

23

"""

24

Load a TrueType or OpenType font from file.

25

26

Parameters:

27

- font (str | bytes | Path): Font filename, bytes, or pathlib.Path

28

- size (float): Font size in points

29

- index (int): Font index for font collections (TTC files)

30

- encoding (str): Character encoding

31

- layout_engine (int): Text layout engine (LAYOUT_BASIC or LAYOUT_RAQM)

32

33

Returns:

34

FreeTypeFont: Font object

35

36

Raises:

37

OSError: If font file cannot be loaded

38

"""

39

40

def load(filename):

41

"""

42

Load a bitmap font from file.

43

44

Parameters:

45

- filename (str): Font filename (.pil format)

46

47

Returns:

48

ImageFont: Bitmap font object

49

"""

50

51

def load_path(filename):

52

"""

53

Load a bitmap font, searching in font path.

54

55

Parameters:

56

- filename (str | bytes): Font filename

57

58

Returns:

59

ImageFont: Bitmap font object

60

"""

61

```

62

63

### Font Classes

64

65

Font object types for different font formats.

66

67

```python { .api }

68

class FreeTypeFont:

69

"""TrueType/OpenType font support with advanced typography features."""

70

71

@property

72

def family(self) -> str:

73

"""Font family name."""

74

75

@property

76

def style(self) -> str:

77

"""Font style name."""

78

79

@property

80

def size(self) -> float:

81

"""Font size in points."""

82

83

def getsize(self, text, direction=None, features=None, language=None, stroke_width=0):

84

"""

85

Get the size of given text (deprecated, use getbbox).

86

87

Parameters:

88

- text (str): Text to measure

89

- direction (str): Text direction ("ltr", "rtl", "ttb")

90

- features (list): OpenType features

91

- language (str): Language code

92

- stroke_width (int): Text stroke width

93

94

Returns:

95

tuple: Text size as (width, height)

96

"""

97

98

def getbbox(self, text, anchor=None, direction=None, features=None, language=None, stroke_width=0, embedded_color=False):

99

"""

100

Get the bounding box of given text.

101

102

Parameters:

103

- text (str): Text to measure

104

- anchor (str): Text anchor point

105

- direction (str): Text direction

106

- features (list): OpenType features

107

- language (str): Language code

108

- stroke_width (int): Text stroke width

109

- embedded_color (bool): Whether to use embedded color fonts

110

111

Returns:

112

tuple: Bounding box as (left, top, right, bottom)

113

"""

114

115

def getlength(self, text, direction=None, features=None, language=None, embedded_color=False):

116

"""

117

Get the length of given text.

118

119

Parameters:

120

- text (str): Text to measure

121

- direction (str): Text direction

122

- features (list): OpenType features

123

- language (str): Language code

124

- embedded_color (bool): Whether to use embedded color fonts

125

126

Returns:

127

float: Text length in pixels

128

"""

129

130

def getmask(self, text, mode="", direction=None, features=None, language=None, stroke_width=0, anchor=None, ink=0, start=None):

131

"""

132

Create a text mask.

133

134

Parameters:

135

- text (str): Text to render

136

- mode (str): Mask mode

137

- direction (str): Text direction

138

- features (list): OpenType features

139

- language (str): Language code

140

- stroke_width (int): Text stroke width

141

- anchor (str): Text anchor point

142

- ink (int): Ink parameter for bitmap fonts

143

- start (tuple): Starting offset

144

145

Returns:

146

Image: Text mask

147

"""

148

149

def font_variant(self, font=None, size=None, index=None, encoding=None, layout_engine=None):

150

"""

151

Create a variant of this font with different parameters.

152

153

Parameters:

154

- font (str): Font filename (uses current if None)

155

- size (float): Font size (uses current if None)

156

- index (int): Font index (uses current if None)

157

- encoding (str): Character encoding (uses current if None)

158

- layout_engine (int): Layout engine (uses current if None)

159

160

Returns:

161

FreeTypeFont: New font variant

162

"""

163

164

def set_variation_by_name(self, name):

165

"""

166

Set font variation by name (for variable fonts).

167

168

Parameters:

169

- name (str): Variation name

170

"""

171

172

def set_variation_by_axes(self, axes):

173

"""

174

Set font variation by axes (for variable fonts).

175

176

Parameters:

177

- axes (dict): Axis values as {axis_name: value}

178

"""

179

180

def get_variation_names(self):

181

"""

182

Get available variation names (for variable fonts).

183

184

Returns:

185

list: Available variation names

186

"""

187

188

def get_variation_axes(self):

189

"""

190

Get available variation axes (for variable fonts).

191

192

Returns:

193

list: Available axes information

194

"""

195

196

class ImageFont:

197

"""Bitmap font support for simple text rendering."""

198

199

def getsize(self, text):

200

"""

201

Get the size of given text.

202

203

Parameters:

204

- text (str): Text to measure

205

206

Returns:

207

tuple: Text size as (width, height)

208

"""

209

210

def getmask(self, text, mode="", direction=None, features=None, language=None, stroke_width=0, anchor=None, ink=0, start=None):

211

"""

212

Create a text mask.

213

214

Parameters:

215

- text (str): Text to render

216

- mode (str): Mask mode

217

218

Returns:

219

Image: Text mask

220

"""

221

222

class TransposedFont:

223

"""Font wrapper that applies a transformation to rendered text."""

224

225

def __init__(self, font, orientation=None):

226

"""

227

Create a transposed font.

228

229

Parameters:

230

- font (FreeTypeFont | ImageFont): Base font

231

- orientation (int): Text orientation

232

"""

233

```

234

235

### Layout Engines

236

237

Constants for text layout engines.

238

239

```python { .api }

240

LAYOUT_BASIC: int # Basic text layout engine

241

LAYOUT_RAQM: int # Advanced text layout engine (supports complex scripts)

242

```

243

244

## Usage Examples

245

246

### Basic Font Loading and Text Rendering

247

248

```python

249

from PIL import Image, ImageDraw, ImageFont

250

251

# Create a blank image

252

img = Image.new("RGB", (400, 200), "white")

253

draw = ImageDraw.Draw(img)

254

255

# Load different fonts

256

try:

257

# Load system fonts

258

title_font = ImageFont.truetype("arial.ttf", 36)

259

body_font = ImageFont.truetype("arial.ttf", 18)

260

bold_font = ImageFont.truetype("arialbd.ttf", 24)

261

except OSError:

262

# Fallback to default font if system fonts not available

263

title_font = ImageFont.load_default()

264

body_font = ImageFont.load_default()

265

bold_font = ImageFont.load_default()

266

267

# Draw text with different fonts

268

draw.text((200, 50), "Main Title", font=title_font, fill="black", anchor="mm")

269

draw.text((200, 100), "Subtitle text", font=bold_font, fill="blue", anchor="mm")

270

draw.text((200, 140), "Body text with default styling", font=body_font, fill="gray", anchor="mm")

271

272

img.save("font_example.png")

273

```

274

275

### Advanced Typography Features

276

277

```python

278

from PIL import Image, ImageDraw, ImageFont

279

280

img = Image.new("RGB", (600, 400), "white")

281

draw = ImageDraw.Draw(img)

282

283

# Load font with specific size

284

font = ImageFont.truetype("times.ttf", 24)

285

286

# Text with different anchors

287

text = "Anchor Demo"

288

y_positions = [50, 100, 150, 200]

289

anchors = ["lt", "mt", "rt", "mm"]

290

anchor_names = ["left-top", "middle-top", "right-top", "middle-middle"]

291

292

for y, anchor, name in zip(y_positions, anchors, anchor_names):

293

# Draw reference line

294

draw.line([(0, y), (600, y)], fill="lightgray", width=1)

295

draw.line([(300, y-20), (300, y+20)], fill="lightgray", width=1)

296

297

# Draw text with anchor

298

draw.text((300, y), text, font=font, fill="black", anchor=anchor)

299

300

# Label the anchor type

301

draw.text((10, y), f"{anchor} ({name})", font=ImageFont.load_default(), fill="red")

302

303

img.save("anchor_demo.png")

304

```

305

306

### Text Measurement and Layout

307

308

```python

309

from PIL import Image, ImageDraw, ImageFont

310

311

# Create image

312

img = Image.new("RGB", (500, 300), "white")

313

draw = ImageDraw.Draw(img)

314

315

# Load font

316

font = ImageFont.truetype("arial.ttf", 20)

317

318

# Text to measure

319

text = "Sample text for measurement"

320

321

# Get text bounding box

322

bbox = font.getbbox(text)

323

text_width = bbox[2] - bbox[0]

324

text_height = bbox[3] - bbox[1]

325

326

print(f"Text dimensions: {text_width} x {text_height}")

327

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

328

329

# Center text using measurements

330

img_width, img_height = img.size

331

x = (img_width - text_width) // 2

332

y = (img_height - text_height) // 2

333

334

# Draw text

335

draw.text((x, y), text, font=font, fill="black")

336

337

# Draw bounding box for visualization

338

draw.rectangle([x + bbox[0], y + bbox[1], x + bbox[2], y + bbox[3]],

339

outline="red", width=1)

340

341

img.save("text_measurement.png")

342

```

343

344

### Multiline Text and Formatting

345

346

```python

347

from PIL import Image, ImageDraw, ImageFont

348

349

img = Image.new("RGB", (400, 300), "white")

350

draw = ImageDraw.Draw(img)

351

352

font = ImageFont.truetype("arial.ttf", 16)

353

354

# Multiline text with different alignments

355

multiline_text = """This is a sample of multiline text

356

that demonstrates different

357

alignment options and

358

line spacing controls."""

359

360

# Left aligned

361

draw.multiline_text((50, 50), multiline_text, font=font, fill="black",

362

align="left", spacing=8)

363

364

# Center aligned

365

draw.multiline_text((200, 50), multiline_text, font=font, fill="blue",

366

align="center", spacing=12)

367

368

# Right aligned

369

draw.multiline_text((350, 50), multiline_text, font=font, fill="green",

370

align="right", spacing=6)

371

372

img.save("multiline_text.png")

373

```

374

375

### Text Effects and Styling

376

377

```python

378

from PIL import Image, ImageDraw, ImageFont

379

380

img = Image.new("RGB", (500, 200), "white")

381

draw = ImageDraw.Draw(img)

382

383

font = ImageFont.truetype("arial.ttf", 48)

384

text = "STYLED TEXT"

385

386

# Shadow effect

387

shadow_offset = 3

388

draw.text((102 + shadow_offset, 52 + shadow_offset), text, font=font, fill="gray")

389

draw.text((102, 52), text, font=font, fill="black")

390

391

# Outline effect (stroke)

392

if hasattr(font, 'getbbox'): # Check if FreeType font

393

draw.text((100, 100), text, font=font, fill="white",

394

stroke_width=2, stroke_fill="black")

395

396

img.save("text_effects.png")

397

```

398

399

### Working with Different Text Directions

400

401

```python

402

from PIL import Image, ImageDraw, ImageFont

403

404

img = Image.new("RGB", (400, 400), "white")

405

draw = ImageDraw.Draw(img)

406

407

font = ImageFont.truetype("arial.ttf", 24)

408

409

# Left-to-right text (default)

410

draw.text((50, 50), "Left to Right Text", font=font, fill="black", direction="ltr")

411

412

# Right-to-left text (for Arabic, Hebrew, etc.)

413

arabic_text = "النص العربي" # "Arabic text" in Arabic

414

try:

415

draw.text((350, 100), arabic_text, font=font, fill="blue",

416

direction="rtl", anchor="ra")

417

except:

418

# Fallback if font doesn't support Arabic

419

draw.text((350, 100), "RTL Text", font=font, fill="blue",

420

direction="rtl", anchor="ra")

421

422

# Vertical text

423

draw.text((200, 150), "Vertical", font=font, fill="green", direction="ttb")

424

425

img.save("text_directions.png")

426

```

427

428

### Variable Font Support

429

430

```python

431

from PIL import Image, ImageDraw, ImageFont

432

433

# Load a variable font (if available)

434

try:

435

var_font = ImageFont.truetype("variable_font.ttf", 36)

436

437

img = Image.new("RGB", (600, 300), "white")

438

draw = ImageDraw.Draw(img)

439

440

# Get available variations

441

variations = var_font.get_variation_names()

442

axes = var_font.get_variation_axes()

443

444

print("Available variations:", variations)

445

print("Available axes:", axes)

446

447

# Apply different variations

448

positions = [(100, 50), (100, 100), (100, 150), (100, 200)]

449

450

for i, pos in enumerate(positions):

451

# Create font variant with different weight

452

weight = 200 + i * 200 # Vary from 200 to 800

453

var_font.set_variation_by_axes({"wght": weight})

454

455

draw.text(pos, f"Weight {weight}", font=var_font, fill="black")

456

457

img.save("variable_font.png")

458

459

except OSError:

460

print("Variable font not available")

461

```

462

463

### Font Information and Properties

464

465

```python

466

from PIL import ImageFont

467

468

# Load font and inspect properties

469

font = ImageFont.truetype("arial.ttf", 24)

470

471

print(f"Font family: {font.family}")

472

print(f"Font style: {font.style}")

473

print(f"Font size: {font.size}")

474

475

# Test text measurements

476

test_text = "Hello, World!"

477

bbox = font.getbbox(test_text)

478

length = font.getlength(test_text)

479

480

print(f"Text: '{test_text}'")

481

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

482

print(f"Text length: {length}")

483

484

# Compare with different sizes

485

sizes = [12, 18, 24, 36, 48]

486

for size in sizes:

487

font_size = ImageFont.truetype("arial.ttf", size)

488

bbox = font_size.getbbox(test_text)

489

width = bbox[2] - bbox[0]

490

height = bbox[3] - bbox[1]

491

print(f"Size {size}: {width} x {height} pixels")

492

```

493

494

### Professional Text Layout

495

496

```python

497

from PIL import Image, ImageDraw, ImageFont

498

499

def create_text_layout(title, subtitle, body, width=600, height=400):

500

"""Create a professional text layout with proper spacing."""

501

img = Image.new("RGB", (width, height), "white")

502

draw = ImageDraw.Draw(img)

503

504

# Load fonts

505

title_font = ImageFont.truetype("arial.ttf", 32)

506

subtitle_font = ImageFont.truetype("arial.ttf", 20)

507

body_font = ImageFont.truetype("arial.ttf", 14)

508

509

# Layout parameters

510

margin = 40

511

line_spacing = 10

512

current_y = margin

513

514

# Draw title

515

draw.text((width // 2, current_y), title, font=title_font,

516

fill="black", anchor="mt")

517

title_bbox = title_font.getbbox(title)

518

current_y += (title_bbox[3] - title_bbox[1]) + line_spacing * 2

519

520

# Draw subtitle

521

draw.text((width // 2, current_y), subtitle, font=subtitle_font,

522

fill="blue", anchor="mt")

523

subtitle_bbox = subtitle_font.getbbox(subtitle)

524

current_y += (subtitle_bbox[3] - subtitle_bbox[1]) + line_spacing * 3

525

526

# Draw body text (wrapped)

527

words = body.split()

528

lines = []

529

current_line = []

530

531

for word in words:

532

test_line = " ".join(current_line + [word])

533

test_width = body_font.getlength(test_line)

534

535

if test_width <= width - 2 * margin:

536

current_line.append(word)

537

else:

538

if current_line:

539

lines.append(" ".join(current_line))

540

current_line = [word]

541

else:

542

lines.append(word) # Single word longer than line

543

544

if current_line:

545

lines.append(" ".join(current_line))

546

547

# Draw wrapped text

548

for line in lines:

549

draw.text((margin, current_y), line, font=body_font, fill="black")

550

line_bbox = body_font.getbbox(line)

551

current_y += (line_bbox[3] - line_bbox[1]) + line_spacing

552

553

return img

554

555

# Create professional layout

556

title = "Professional Document"

557

subtitle = "Advanced Typography with Pillow"

558

body = """This is an example of professional text layout using Python's Pillow library.

559

The text is properly wrapped, spaced, and formatted to create a clean, readable document.

560

Multiple font sizes and colors are used to create visual hierarchy."""

561

562

layout = create_text_layout(title, subtitle, body)

563

layout.save("professional_layout.png")

564

```