|
12 | 12 |
|
13 | 13 | import matplotlib |
14 | 14 | import matplotlib.ft2font as ft |
| 15 | +import matplotlib.pyplot as plt |
| 16 | +from matplotlib.font_manager import FontProperties |
15 | 17 |
|
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')) |
| 18 | +# Use a font shipped with Matplotlib. |
| 19 | +font_path = os.path.join( |
| 20 | + matplotlib.get_data_path(), |
| 21 | + 'fonts/ttf/DejaVuSans-Oblique.ttf' |
| 22 | +) |
| 23 | + |
| 24 | +font = ft.FT2Font(font_path) |
20 | 25 |
|
21 | 26 | print('Num instances: ', font.num_named_instances) # number of named instances in file |
22 | 27 | print('Num faces: ', font.num_faces) # number of faces in file |
|
26 | 31 | print('PS name: ', font.postscript_name) # the postscript name |
27 | 32 | print('Num fixed: ', font.num_fixed_sizes) # number of embedded bitmaps |
28 | 33 |
|
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) |
| 34 | +# the face global bounding box (xmin, ymin, xmax, ymax) |
| 35 | +print('Bbox: ', font.bbox) |
| 36 | +# number of font units covered by the EM |
| 37 | +print('EM: ', font.units_per_EM) |
| 38 | +# the ascender in 26.6 units |
| 39 | +print('Ascender: ', font.ascender) |
| 40 | +# the descender in 26.6 units |
| 41 | +print('Descender: ', font.descender) |
| 42 | +# the height in 26.6 units |
| 43 | +print('Height: ', font.height) |
| 44 | +# maximum horizontal cursor advance |
| 45 | +print('Max adv width: ', font.max_advance_width) |
| 46 | +# same for vertical layout |
| 47 | +print('Max adv height: ', font.max_advance_height) |
| 48 | +# vertical position of the underline bar |
| 49 | +print('Underline pos: ', font.underline_position) |
| 50 | +# vertical thickness of the underline |
| 51 | +print('Underline thickness:', font.underline_thickness) |
49 | 52 |
|
50 | 53 | for flag in ft.StyleFlags: |
51 | 54 | name = flag.name.replace('_', ' ').title() + ':' |
|
54 | 57 | for flag in ft.FaceFlags: |
55 | 58 | name = flag.name.replace('_', ' ').title() + ':' |
56 | 59 | print(f"{name:17}", flag in font.face_flags) |
| 60 | + |
| 61 | +# ── Visualise font metrics ──────────────────────────────────────────────────── |
| 62 | +# Normalise all metrics to units_per_EM so values are in the range [-1, 1]. |
| 63 | +# This figure is used by Sphinx Gallery to auto-generate the gallery thumbnail. |
| 64 | +u = font.units_per_EM |
| 65 | +asc = font.ascender / u |
| 66 | +desc = font.descender / u |
| 67 | +bbox_ymax = font.bbox[3] / u |
| 68 | +bbox_ymin = font.bbox[1] / u |
| 69 | +ul_pos = font.underline_position / u |
| 70 | +ul_thick = font.underline_thickness / u |
| 71 | + |
| 72 | +fig, ax = plt.subplots(figsize=(8, 6)) |
| 73 | + |
| 74 | +# Metric lines drawn FIRST (lower zorder) so text renders on top of them. |
| 75 | +metrics = [ |
| 76 | + ("bbox top (ymax)", bbox_ymax, "tab:green"), |
| 77 | + ("ascender", asc, "tab:blue"), |
| 78 | + ("baseline (y=0)", 0, "black"), |
| 79 | + ("underline_position", ul_pos, "tab:orange"), |
| 80 | + ("descender", desc, "tab:red"), |
| 81 | + ("bbox bottom (ymin)", bbox_ymin, "tab:purple"), |
| 82 | +] |
| 83 | + |
| 84 | +# Lines span from left edge to 72% of axes width — crossing through the glyph. |
| 85 | +# Labels sit at 75%, clearly to the right of the lines. |
| 86 | +for label, y, color in metrics: |
| 87 | + ax.plot( |
| 88 | + [0.02, 0.72], [y, y], |
| 89 | + color=color, linewidth=1.5, linestyle='--', alpha=0.9, zorder=2) |
| 90 | + # default position |
| 91 | + y_pos = y |
| 92 | + |
| 93 | + # adjust only bbox labels |
| 94 | + if "bbox top" in label: |
| 95 | + y_pos = y - 0.015 |
| 96 | + elif "bbox bottom" in label: |
| 97 | + y_pos = y + 0.015 |
| 98 | + |
| 99 | + ax.text( |
| 100 | + 0.75, y_pos, label, color=color, va='center', |
| 101 | + fontsize=9, fontweight='medium', ha='left', zorder=2) |
| 102 | + |
| 103 | +# Underline thickness — shaded band between underline_position and its lower edge. |
| 104 | +ax.fill_between([0.02, 0.72], |
| 105 | + ul_pos - ul_thick, |
| 106 | + ul_pos, |
| 107 | + color='tab:orange', |
| 108 | + alpha=0.22, |
| 109 | + label=f'underline_thickness = {font.underline_thickness}', |
| 110 | + zorder=1) |
| 111 | + |
| 112 | +# Bounding box (font.bbox) as a rectangle. Drawn after lines, before text. |
| 113 | +ax.add_patch(plt.Rectangle( |
| 114 | + (0.02, bbox_ymin), 0.70, (bbox_ymax - bbox_ymin), |
| 115 | + fill=False, edgecolor='black', linestyle='-', |
| 116 | + linewidth=1.5, alpha=0.6, zorder=3, |
| 117 | + label='font.bbox' |
| 118 | +)) |
| 119 | + |
| 120 | +# Render "Ag" on top of everything — zorder=10 ensures no line covers the text. |
| 121 | +# 'A' shows ascender/cap-height, 'g' shows descender. |
| 122 | +fp = FontProperties(fname=font_path) |
| 123 | +ax.text(0.30, 0.0, "Ag", fontproperties=fp, fontsize=150, |
| 124 | + va='baseline', ha='center', color='black', zorder=10) |
| 125 | + |
| 126 | +ax.set_xlim(0, 1.35) |
| 127 | +ax.set_ylim(bbox_ymin - 0.10, bbox_ymax + 0.15) |
| 128 | +ax.set_title( |
| 129 | + f"Font metrics — {font.family_name} {font.style_name}\n" |
| 130 | + f"(values normalised to units_per_EM = {font.units_per_EM})", |
| 131 | + fontsize=11.5, pad=15 |
| 132 | +) |
| 133 | +ax.legend(fontsize=8, loc='lower right', frameon=False) |
| 134 | +ax.axis('off') |
| 135 | +plt.tight_layout(pad=1.5) |
| 136 | +plt.show() |
0 commit comments