Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e6add6a

Browse files
committed
DOC: Make ftface_props example generate a font metrics figure
The example only printed to stdout with no visual output, so Sphinx Gallery could not auto-generate a thumbnail. Added a matplotlib figure that visualises the font metrics (ascender, descender, bbox, underline position/thickness) normalised to units_per_EM using the same font loaded in the example. Closes #17479
1 parent e6a833f commit e6add6a

1 file changed

Lines changed: 105 additions & 24 deletions

File tree

galleries/examples/misc/ftface_props.py

Lines changed: 105 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,19 @@
1010

1111
import os
1212

13+
import matplotlib.pyplot as plt
14+
1315
import matplotlib
16+
from matplotlib.font_manager import FontProperties
1417
import matplotlib.ft2font as ft
1518

16-
font = ft.FT2Font(
17-
# Use a font shipped with Matplotlib.
18-
os.path.join(matplotlib.get_data_path(),
19-
'fonts/ttf/DejaVuSans-Oblique.ttf'))
19+
# Use a font shipped with Matplotlib.
20+
font_path = os.path.join(
21+
matplotlib.get_data_path(),
22+
'fonts/ttf/DejaVuSans-Oblique.ttf'
23+
)
24+
25+
font = ft.FT2Font(font_path)
2026

2127
print('Num instances: ', font.num_named_instances) # number of named instances in file
2228
print('Num faces: ', font.num_faces) # number of faces in file
@@ -26,26 +32,24 @@
2632
print('PS name: ', font.postscript_name) # the postscript name
2733
print('Num fixed: ', font.num_fixed_sizes) # number of embedded bitmaps
2834

29-
# the following are only available if face.scalable
30-
if font.scalable:
31-
# the face global bounding box (xmin, ymin, xmax, ymax)
32-
print('Bbox: ', font.bbox)
33-
# number of font units covered by the EM
34-
print('EM: ', font.units_per_EM)
35-
# the ascender in 26.6 units
36-
print('Ascender: ', font.ascender)
37-
# the descender in 26.6 units
38-
print('Descender: ', font.descender)
39-
# the height in 26.6 units
40-
print('Height: ', font.height)
41-
# maximum horizontal cursor advance
42-
print('Max adv width: ', font.max_advance_width)
43-
# same for vertical layout
44-
print('Max adv height: ', font.max_advance_height)
45-
# vertical position of the underline bar
46-
print('Underline pos: ', font.underline_position)
47-
# vertical thickness of the underline
48-
print('Underline thickness:', font.underline_thickness)
35+
# the face global bounding box (xmin, ymin, xmax, ymax)
36+
print('Bbox: ', font.bbox)
37+
# number of font units covered by the EM
38+
print('EM: ', font.units_per_EM)
39+
# the ascender in 26.6 units
40+
print('Ascender: ', font.ascender)
41+
# the descender in 26.6 units
42+
print('Descender: ', font.descender)
43+
# the height in 26.6 units
44+
print('Height: ', font.height)
45+
# maximum horizontal cursor advance
46+
print('Max adv width: ', font.max_advance_width)
47+
# same for vertical layout
48+
print('Max adv height: ', font.max_advance_height)
49+
# vertical position of the underline bar
50+
print('Underline pos: ', font.underline_position)
51+
# vertical thickness of the underline
52+
print('Underline thickness:', font.underline_thickness)
4953

5054
for flag in ft.StyleFlags:
5155
name = flag.name.replace('_', ' ').title() + ':'
@@ -54,3 +58,80 @@
5458
for flag in ft.FaceFlags:
5559
name = flag.name.replace('_', ' ').title() + ':'
5660
print(f"{name:17}", flag in font.face_flags)
61+
62+
# ── Visualise font metrics ────────────────────────────────────────────────────
63+
# Normalise all metrics to units_per_EM so values are in the range [-1, 1].
64+
# This figure is used by Sphinx Gallery to auto-generate the gallery thumbnail.
65+
u = font.units_per_EM
66+
asc = font.ascender / u
67+
desc = font.descender / u
68+
bbox_ymax = font.bbox[3] / u
69+
bbox_ymin = font.bbox[1] / u
70+
ul_pos = font.underline_position / u
71+
ul_thick = font.underline_thickness / u
72+
73+
fig, ax = plt.subplots(figsize=(8, 6))
74+
75+
# Metric lines drawn FIRST (lower zorder) so text renders on top of them.
76+
metrics = [
77+
("bbox top (ymax)", bbox_ymax, "tab:green"),
78+
("ascender", asc, "tab:blue"),
79+
("baseline (y=0)", 0, "black"),
80+
("underline_position", ul_pos, "tab:orange"),
81+
("descender", desc, "tab:red"),
82+
("bbox bottom (ymin)", bbox_ymin, "tab:purple"),
83+
]
84+
85+
# Lines span from left edge to 72% of axes width — crossing through the glyph.
86+
# Labels sit at 75%, clearly to the right of the lines.
87+
for label, y, color in metrics:
88+
ax.plot(
89+
[0.02, 0.72], [y, y],
90+
color=color, linewidth=1.5, linestyle='--', alpha=0.9, zorder=2)
91+
# default position
92+
y_pos = y
93+
94+
# adjust only bbox labels
95+
if "bbox top" in label:
96+
y_pos = y - 0.015
97+
elif "bbox bottom" in label:
98+
y_pos = y + 0.015
99+
100+
ax.text(
101+
0.75, y_pos, label, color=color, va='center',
102+
fontsize=9, fontweight='medium', ha='left', zorder=2)
103+
104+
# Underline thickness — shaded band between underline_position and its lower edge.
105+
ax.fill_between([0.02, 0.72],
106+
ul_pos - ul_thick,
107+
ul_pos,
108+
color='tab:orange',
109+
alpha=0.22,
110+
label=f'underline_thickness = {font.underline_thickness}',
111+
zorder=1)
112+
113+
# Bounding box (font.bbox) as a rectangle. Drawn after lines, before text.
114+
ax.add_patch(plt.Rectangle(
115+
(0.02, bbox_ymin), 0.70, (bbox_ymax - bbox_ymin),
116+
fill=False, edgecolor='black', linestyle='-',
117+
linewidth=1.5, alpha=0.6, zorder=3,
118+
label='font.bbox'
119+
))
120+
121+
# Render "Ag" on top of everything — zorder=10 ensures no line covers the text.
122+
# 'A' shows ascender/cap-height, 'g' shows descender.
123+
fp = FontProperties(fname=font_path)
124+
ax.text(0.30, 0.0, "Ag", fontproperties=fp, fontsize=150,
125+
va='baseline', ha='center', color='black', zorder=10)
126+
127+
ax.set_xlim(0, 1.35)
128+
ax.set_ylim(bbox_ymin - 0.10, bbox_ymax + 0.15)
129+
ax.set_title(
130+
f"Font metrics — {font.family_name} {font.style_name}\n"
131+
f"(values normalised to units_per_EM = {font.units_per_EM})",
132+
fontsize=11.5, pad=15
133+
)
134+
ax.legend(fontsize=8, loc='lower right', frameon=False)
135+
ax.axis('off')
136+
plt.tight_layout(pad=1.5)
137+
plt.show()

0 commit comments

Comments
 (0)