Skip to content

Commit d85bc91

Browse files
committed
Use true font.bbox bounds in ftface_props example
1 parent 1e67f53 commit d85bc91

File tree

1 file changed

+81
-51
lines changed

1 file changed

+81
-51
lines changed

galleries/examples/misc/ftface_props.py

Lines changed: 81 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,7 @@
2020
import matplotlib.transforms
2121

2222
# Use a font shipped with Matplotlib.
23-
font_path = os.path.join(
24-
matplotlib.get_data_path(),
25-
'fonts/ttf/DejaVuSans-Oblique.ttf'
26-
)
23+
font_path = os.path.join(matplotlib.get_data_path(), "fonts/ttf/DejaVuSans-Oblique.ttf")
2724

2825
font = ft.FT2Font(font_path)
2926

@@ -36,20 +33,20 @@
3633
print("Num fixed: ", font.num_fixed_sizes) # number of embedded bitmaps
3734
print("Bbox: ", font.bbox) # global bounding box (xmin, ymin, xmax, ymax)
3835
print("EM: ", font.units_per_EM) # font units per EM
39-
print("Ascender: ", font.ascender) # the ascender in 26.6 units
40-
print("Descender: ", font.descender) # the descender in 26.6 units
41-
print("Height: ", font.height) # the height in 26.6 units
36+
print("Ascender: ", font.ascender) # the ascender in 26.6 units
37+
print("Descender: ", font.descender) # the descender in 26.6 units
38+
print("Height: ", font.height) # the height in 26.6 units
4239
print("Max adv width: ", font.max_advance_width) # max horizontal advance
4340
print("Max adv height: ", font.max_advance_height) # same for vertical layout
4441
print("Underline pos: ", font.underline_position) # underline bar position
4542
print("Underline thickness:", font.underline_thickness) # underline thickness
4643

4744
for flag in ft.StyleFlags:
48-
name = flag.name.replace('_', ' ').title() + ':'
45+
name = flag.name.replace("_", " ").title() + ":"
4946
print(f"{name:17}", flag in font.style_flags)
5047

5148
for flag in ft.FaceFlags:
52-
name = flag.name.replace('_', ' ').title() + ':'
49+
name = flag.name.replace("_", " ").title() + ":"
5350
print(f"{name:17}", flag in font.face_flags)
5451

5552
# Normalise all vertical metrics to units_per_EM so all y-values sit in [-1, 1].
@@ -58,6 +55,8 @@
5855
desc = font.descender / u
5956
bbox_ymax = font.bbox[3] / u
6057
bbox_ymin = font.bbox[1] / u
58+
bbox_xmin = font.bbox[0] / u
59+
bbox_xmax = font.bbox[2] / u
6160
ul_pos = font.underline_position / u
6261
ul_thick = font.underline_thickness / u
6362

@@ -67,19 +66,13 @@
6766
tp = TextPath((0, 0), "Água", size=1, prop=fp)
6867
text_bb = tp.get_extents()
6968

70-
# Centre the glyph at a fixed x position, then read back where it actually lands.
71-
GLYPH_CENTER_X = 0.70
72-
x_offset = GLYPH_CENTER_X - (text_bb.x0 + text_bb.width / 2)
73-
74-
# True left/right edges of the rendered glyph in data coordinates.
75-
glyph_x0 = text_bb.x0 + x_offset
76-
glyph_x1 = text_bb.x1 + x_offset
69+
# Align the glyph's left edge with bbox_xmin.
70+
x_offset = bbox_xmin - text_bb.x0
7771

78-
# Lines, rectangle and labels are all derived from these real glyph bounds.
79-
H_MARGIN = 0.05 # horizontal padding around glyph
80-
LINE_X0 = glyph_x0 - H_MARGIN # lines start here
81-
LINE_X1 = glyph_x1 + H_MARGIN # lines end here (always past glyph edge)
82-
LABEL_X = LINE_X1 + 0.08 # metric labels start here
72+
# Lines, rectangle and labels are derived from the true font bbox.
73+
LINE_X0 = bbox_xmin
74+
LINE_X1 = bbox_xmax
75+
LABEL_X = LINE_X1 + 0.08 # metric labels start here
8376

8477
metrics = [
8578
("bbox top (ymax)", bbox_ymax, "tab:green"),
@@ -91,42 +84,79 @@
9184
]
9285

9386
for label, y, color in metrics:
94-
l, = ax.hline(y, color=color, linewidth=1.5, linestyle='--', alpha=0.9, zorder=2)
87+
ax.plot(
88+
[LINE_X0, LINE_X1],
89+
[y, y],
90+
color=color,
91+
linewidth=1.5,
92+
linestyle="--",
93+
alpha=0.9,
94+
zorder=2,
95+
)
9596
# Nudge bbox-edge labels slightly away from the rectangle border.
96-
ax.annotate (LABEL_X, y_text, label, (), xycoords = (1, .5), color=color, va='center', ha='left',
97-
fontsize=9, fontweight='medium', zorder=2)
97+
y_text = (
98+
y - 0.015 if "bbox top" in label else y + 0.015 if "bbox bottom" in label else y
99+
)
100+
ax.text(
101+
LABEL_X,
102+
y_text,
103+
label,
104+
color=color,
105+
va="center",
106+
ha="left",
107+
fontsize=9,
108+
fontweight="medium",
109+
zorder=2,
110+
)
98111

99112
# Underline thickness: shaded band from (ul_pos − ul_thick) to ul_pos.
100-
ax.fill_between([LINE_X0, LINE_X1],
101-
ul_pos - ul_thick, ul_pos,
102-
color='tab:orange', alpha=0.22,
103-
label=f'underline_thickness = {font.underline_thickness}',
104-
zorder=1)
105-
106-
# font.bbox visualised as a rectangle. x-span matches the line region so the
107-
# box always contains the glyph and aligns with the metric lines exactly.
108-
ax.add_patch(Rectangle(
109-
(LINE_X0, bbox_ymin), LINE_X1 - LINE_X0, bbox_ymax - bbox_ymin,
110-
fill=False, edgecolor='black', linewidth=1.5, linestyle='-',
111-
alpha=0.6, zorder=3, label='font.bbox',
112-
))
113+
ax.fill_between(
114+
[LINE_X0, LINE_X1],
115+
ul_pos - ul_thick,
116+
ul_pos,
117+
color="tab:orange",
118+
alpha=0.22,
119+
label=f"underline_thickness = {font.underline_thickness}",
120+
zorder=1,
121+
)
122+
123+
# font.bbox visualised as a rectangle using the true font x/y bounds.
124+
ax.add_patch(
125+
Rectangle(
126+
(bbox_xmin, bbox_ymin),
127+
bbox_xmax - bbox_xmin,
128+
bbox_ymax - bbox_ymin,
129+
fill=False,
130+
edgecolor="black",
131+
linewidth=1.5,
132+
linestyle="-",
133+
alpha=0.6,
134+
zorder=3,
135+
label="font.bbox",
136+
)
137+
)
113138

114139
# Glyph path — translate only (scale = 1.0 implicit); high zorder so it sits
115140
# on top of the reference lines.
116-
ax.add_patch(PathPatch(
117-
tp,
118-
transform=matplotlib.transforms.Affine2D().translate(x_offset, 0) + ax.transData,
119-
color='black',
120-
zorder=10,
121-
))
122-
123-
# x-limit: start at 0, end with enough room for the longest label.
124-
ax.set_xlim(LINE_X0 - 0.05, LABEL_X + 0.75)
141+
ax.add_patch(
142+
PathPatch(
143+
tp,
144+
transform=matplotlib.transforms.Affine2D().translate(x_offset, 0)
145+
+ ax.transData,
146+
color="black",
147+
zorder=10,
148+
)
149+
)
150+
151+
# x-limit: start at the bbox left edge and leave room for labels.
152+
ax.set_xlim(LINE_X0, LABEL_X + 0.75)
125153
ax.set_ylim(bbox_ymin - 0.10, bbox_ymax + 0.15)
126-
ax.set_title(f"Font metrics — {font.family_name} {font.style_name}",
127-
fontsize=11.5, pad=15)
128-
ax.legend(fontsize=8, loc='lower center', bbox_to_anchor=(0.5, -0.12),
129-
frameon=False, ncol=2)
130-
ax.axis('off')
154+
ax.set_title(
155+
f"Font metrics — {font.family_name} {font.style_name}", fontsize=11.5, pad=15
156+
)
157+
ax.legend(
158+
fontsize=8, loc="lower center", bbox_to_anchor=(0.5, -0.12), frameon=False, ncol=2
159+
)
160+
ax.axis("off")
131161
plt.tight_layout(pad=1.5)
132162
plt.show()

0 commit comments

Comments
 (0)