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

Skip to content

Commit 51c06c4

Browse files
committed
DOC: use TextPath for glyph so size is in data units, matching metric lines
1 parent a024514 commit 51c06c4

1 file changed

Lines changed: 77 additions & 80 deletions

File tree

galleries/examples/misc/ftface_props.py

Lines changed: 77 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@
1010

1111
import os
1212

13-
import matplotlib.pyplot as plt
14-
1513
import matplotlib
14+
import matplotlib.pyplot as plt
15+
import matplotlib.transforms
1616
from matplotlib.font_manager import FontProperties
17+
from matplotlib.patches import PathPatch, Rectangle
18+
from matplotlib.textpath import TextPath
1719
import matplotlib.ft2font as ft
1820

1921
# Use a font shipped with Matplotlib.
@@ -24,32 +26,22 @@
2426

2527
font = ft.FT2Font(font_path)
2628

27-
print('Num instances: ', font.num_named_instances) # number of named instances in file
28-
print('Num faces: ', font.num_faces) # number of faces in file
29-
print('Num glyphs: ', font.num_glyphs) # number of glyphs in the face
30-
print('Family name: ', font.family_name) # face family name
31-
print('Style name: ', font.style_name) # face style name
32-
print('PS name: ', font.postscript_name) # the postscript name
33-
print('Num fixed: ', font.num_fixed_sizes) # number of embedded bitmaps
34-
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)
29+
print("Num instances: ", font.num_named_instances) # number of named instances in file
30+
print("Num faces: ", font.num_faces) # number of faces in file
31+
print("Num glyphs: ", font.num_glyphs) # number of glyphs in the face
32+
print("Family name: ", font.family_name) # face family name
33+
print("Style name: ", font.style_name) # face style name
34+
print("PS name: ", font.postscript_name) # the postscript name
35+
print("Num fixed: ", font.num_fixed_sizes) # number of embedded bitmaps
36+
print("Bbox: ", font.bbox) # the face global bounding box (xmin, ymin, xmax, ymax)
37+
print("EM: ", font.units_per_EM) # number of font units covered by the EM
38+
print("Ascender: ", font.ascender) # the ascender in 26.6 units
39+
print("Descender: ", font.descender) # the descender in 26.6 units
40+
print("Height: ", font.height) # the height in 26.6 units
41+
print("Max adv width: ", font.max_advance_width) # maximum horizontal cursor advance
42+
print("Max adv height: ", font.max_advance_height) # same for vertical layout
43+
print("Underline pos: ", font.underline_position) # vertical position of the underline bar
44+
print("Underline thickness:", font.underline_thickness) # vertical thickness of the underline
5345

5446
for flag in ft.StyleFlags:
5547
name = flag.name.replace('_', ' ').title() + ':'
@@ -59,79 +51,84 @@
5951
name = flag.name.replace('_', ' ').title() + ':'
6052
print(f"{name:17}", flag in font.face_flags)
6153

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.
54+
# Normalise all vertical metrics to units_per_EM so all y-values sit in [-1, 1].
6555
u = font.units_per_EM
66-
asc = font.ascender / u
67-
desc = font.descender / u
56+
asc = font.ascender / u
57+
desc = font.descender / u
6858
bbox_ymax = font.bbox[3] / u
6959
bbox_ymin = font.bbox[1] / u
70-
ul_pos = font.underline_position / u
71-
ul_thick = font.underline_thickness / u
60+
ul_pos = font.underline_position / u
61+
ul_thick = font.underline_thickness / u
7262

7363
fig, ax = plt.subplots(figsize=(8, 6))
7464

75-
# Metric lines drawn FIRST (lower zorder) so text renders on top of them.
65+
fp = FontProperties(fname=font_path)
66+
tp = TextPath((0, 0), "Ag", size=1, prop=fp)
67+
text_bb = tp.get_extents()
68+
69+
# Centre the glyph at a fixed x position, then read back where it actually lands.
70+
GLYPH_CENTER_X = 0.70
71+
x_offset = GLYPH_CENTER_X - (text_bb.x0 + text_bb.width / 2)
72+
73+
# True left/right edges of the rendered glyph in data coordinates.
74+
glyph_x0 = text_bb.x0 + x_offset
75+
glyph_x1 = text_bb.x1 + x_offset
76+
77+
# Lines, rectangle and labels are all derived from these real glyph bounds.
78+
H_MARGIN = 0.05 # horizontal padding around glyph
79+
LINE_X0 = glyph_x0 - H_MARGIN # lines start here
80+
LINE_X1 = glyph_x1 + H_MARGIN # lines end here (always past glyph edge)
81+
LABEL_X = LINE_X1 + 0.08 # metric labels start here
82+
7683
metrics = [
7784
("bbox top (ymax)", bbox_ymax, "tab:green"),
7885
("ascender", asc, "tab:blue"),
79-
("baseline (y=0)", 0, "black"),
86+
("y = 0 (origin)", 0, "black"),
8087
("underline_position", ul_pos, "tab:orange"),
8188
("descender", desc, "tab:red"),
8289
("bbox bottom (ymin)", bbox_ymin, "tab:purple"),
8390
]
8491

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.
8792
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,
93+
ax.plot([LINE_X0, LINE_X1], [y, y],
94+
color=color, linewidth=1.5, linestyle='--', alpha=0.9, zorder=2)
95+
# Nudge bbox-edge labels slightly away from the rectangle border.
96+
y_text = (y - 0.015 if "bbox top" in label else
97+
y + 0.015 if "bbox bottom" in label else y)
98+
ax.text(LABEL_X, y_text, label,
99+
color=color, va='center', ha='left',
100+
fontsize=9, fontweight='medium', zorder=2)
101+
102+
# Underline thickness: shaded band from (ul_pos − ul_thick) to ul_pos.
103+
ax.fill_between([LINE_X0, LINE_X1],
104+
ul_pos - ul_thick, ul_pos,
105+
color='tab:orange', alpha=0.22,
110106
label=f'underline_thickness = {font.underline_thickness}',
111107
zorder=1)
112108

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'
109+
# font.bbox visualised as a rectangle. x-span matches the line region so the
110+
# box always contains the glyph and aligns with the metric lines exactly.
111+
ax.add_patch(Rectangle(
112+
(LINE_X0, bbox_ymin), LINE_X1 - LINE_X0, bbox_ymax - bbox_ymin,
113+
fill=False, edgecolor='black', linewidth=1.5, linestyle='-',
114+
alpha=0.6, zorder=3, label='font.bbox',
119115
))
120116

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)
117+
# Glyph path — translate only (scale = 1.0 implicit); high zorder so it sits
118+
# on top of the reference lines.
119+
ax.add_patch(PathPatch(
120+
tp,
121+
transform=matplotlib.transforms.Affine2D().translate(x_offset, 0) + ax.transData,
122+
color='black',
123+
zorder=10,
124+
))
126125

127-
ax.set_xlim(0, 1.35)
126+
# x-limit: start at 0, end with enough room for the longest label.
127+
ax.set_xlim(LINE_X0 - 0.05, LABEL_X + 0.75)
128128
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)
129+
ax.set_title(f"Font metrics — {font.family_name} {font.style_name}",
130+
fontsize=11.5, pad=15)
131+
ax.legend(fontsize=8, loc='upper right', bbox_to_anchor=(1.02, 0.95), frameon=False)
135132
ax.axis('off')
136133
plt.tight_layout(pad=1.5)
137-
plt.show()
134+
plt.show()

0 commit comments

Comments
 (0)