Skip to content

Commit 9f4511f

Browse files
committed
Clean up AFM code
Since AFM is now private, we can delete unused methods without deprecation. Additionally, add `AFM.get_glyph_name` so that the PostScript mathtext code doesn't need to special case AFM files.
1 parent 42f1905 commit 9f4511f

File tree

4 files changed

+49
-118
lines changed

4 files changed

+49
-118
lines changed

lib/matplotlib/_afm.py

Lines changed: 39 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""
2-
A python interface to Adobe Font Metrics Files.
2+
A Python interface to Adobe Font Metrics Files.
33
44
Although a number of other Python implementations exist, and may be more
55
complete than this, it was decided not to go with them because they were
@@ -16,19 +16,11 @@
1616
>>> from pathlib import Path
1717
>>> afm_path = Path(mpl.get_data_path(), 'fonts', 'afm', 'ptmr8a.afm')
1818
>>>
19-
>>> from matplotlib.afm import AFM
19+
>>> from matplotlib._afm import AFM
2020
>>> with afm_path.open('rb') as fh:
2121
... afm = AFM(fh)
22-
>>> afm.string_width_height('What the heck?')
23-
(6220.0, 694)
2422
>>> afm.get_fontname()
2523
'Times-Roman'
26-
>>> afm.get_kern_dist('A', 'f')
27-
0
28-
>>> afm.get_kern_dist('A', 'y')
29-
-92.0
30-
>>> afm.get_bbox_char('!')
31-
[130, -9, 238, 676]
3224
3325
As in the Adobe Font Metrics File Format Specification, all dimensions
3426
are given in units of 1/1000 of the scale factor (point size) of the font
@@ -87,20 +79,23 @@ def _to_bool(s):
8779

8880
def _parse_header(fh):
8981
"""
90-
Read the font metrics header (up to the char metrics) and returns
91-
a dictionary mapping *key* to *val*. *val* will be converted to the
92-
appropriate python type as necessary; e.g.:
82+
Read the font metrics header (up to the char metrics).
9383
94-
* 'False'->False
95-
* '0'->0
96-
* '-168 -218 1000 898'-> [-168, -218, 1000, 898]
84+
Returns
85+
-------
86+
dict
87+
A dictionary mapping *key* to *val*. Dictionary keys are:
9788
98-
Dictionary keys are
89+
StartFontMetrics, FontName, FullName, FamilyName, Weight, ItalicAngle,
90+
IsFixedPitch, FontBBox, UnderlinePosition, UnderlineThickness, Version,
91+
Notice, EncodingScheme, CapHeight, XHeight, Ascender, Descender,
92+
StartCharMetrics
9993
100-
StartFontMetrics, FontName, FullName, FamilyName, Weight,
101-
ItalicAngle, IsFixedPitch, FontBBox, UnderlinePosition,
102-
UnderlineThickness, Version, Notice, EncodingScheme, CapHeight,
103-
XHeight, Ascender, Descender, StartCharMetrics
94+
*val* will be converted to the appropriate Python type as necessary, e.g.,:
95+
96+
* 'False' -> False
97+
* '0' -> 0
98+
* '-168 -218 1000 898' -> [-168, -218, 1000, 898]
10499
"""
105100
header_converters = {
106101
b'StartFontMetrics': _to_float,
@@ -185,11 +180,9 @@ def _parse_header(fh):
185180

186181
def _parse_char_metrics(fh):
187182
"""
188-
Parse the given filehandle for character metrics information and return
189-
the information as dicts.
183+
Parse the given filehandle for character metrics information.
190184
191-
It is assumed that the file cursor is on the line behind
192-
'StartCharMetrics'.
185+
It is assumed that the file cursor is on the line behind 'StartCharMetrics'.
193186
194187
Returns
195188
-------
@@ -239,14 +232,15 @@ def _parse_char_metrics(fh):
239232

240233
def _parse_kern_pairs(fh):
241234
"""
242-
Return a kern pairs dictionary; keys are (*char1*, *char2*) tuples and
243-
values are the kern pair value. For example, a kern pairs line like
244-
``KPX A y -50``
245-
246-
will be represented as::
235+
Return a kern pairs dictionary.
247236
248-
d[ ('A', 'y') ] = -50
237+
Returns
238+
-------
239+
dict
240+
Keys are (*char1*, *char2*) tuples and values are the kern pair value. For
241+
example, a kern pairs line like ``KPX A y -50`` will be represented as::
249242
243+
d[ ('A', 'y') ] = -50
250244
"""
251245

252246
line = next(fh)
@@ -279,8 +273,7 @@ def _parse_kern_pairs(fh):
279273

280274
def _parse_composites(fh):
281275
"""
282-
Parse the given filehandle for composites information return them as a
283-
dict.
276+
Parse the given filehandle for composites information.
284277
285278
It is assumed that the file cursor is on the line behind 'StartComposites'.
286279
@@ -363,36 +356,6 @@ def __init__(self, fh):
363356
self._metrics, self._metrics_by_name = _parse_char_metrics(fh)
364357
self._kern, self._composite = _parse_optional(fh)
365358

366-
def get_bbox_char(self, c, isord=False):
367-
if not isord:
368-
c = ord(c)
369-
return self._metrics[c].bbox
370-
371-
def string_width_height(self, s):
372-
"""
373-
Return the string width (including kerning) and string height
374-
as a (*w*, *h*) tuple.
375-
"""
376-
if not len(s):
377-
return 0, 0
378-
total_width = 0
379-
namelast = None
380-
miny = 1e9
381-
maxy = 0
382-
for c in s:
383-
if c == '\n':
384-
continue
385-
wx, name, bbox = self._metrics[ord(c)]
386-
387-
total_width += wx + self._kern.get((namelast, name), 0)
388-
l, b, w, h = bbox
389-
miny = min(miny, b)
390-
maxy = max(maxy, b + h)
391-
392-
namelast = name
393-
394-
return total_width, maxy - miny
395-
396359
def get_str_bbox_and_descent(self, s):
397360
"""Return the string bounding box and the maximal descent."""
398361
if not len(s):
@@ -423,45 +386,29 @@ def get_str_bbox_and_descent(self, s):
423386

424387
return left, miny, total_width, maxy - miny, -miny
425388

426-
def get_str_bbox(self, s):
427-
"""Return the string bounding box."""
428-
return self.get_str_bbox_and_descent(s)[:4]
429-
430-
def get_name_char(self, c, isord=False):
431-
"""Get the name of the character, i.e., ';' is 'semicolon'."""
432-
if not isord:
433-
c = ord(c)
434-
return self._metrics[c].name
389+
def get_glyph_name(self, glyph_ind): # For consistency with FT2Font.
390+
"""Get the name of the glyph, i.e., ord(';') is 'semicolon'."""
391+
return self._metrics[glyph_ind].name
435392

436-
def get_width_char(self, c, isord=False):
393+
def get_char_index(self, c): # For consistency with FT2Font.
437394
"""
438-
Get the width of the character from the character metric WX field.
395+
Return the glyph index corresponding to a character code point.
396+
397+
Note, for AFM fonts, we treat the glyph index the same as the codepoint.
439398
"""
440-
if not isord:
441-
c = ord(c)
399+
return c
400+
401+
def get_width_char(self, c):
402+
"""Get the width of the character code from the character metric WX field."""
442403
return self._metrics[c].width
443404

444405
def get_width_from_char_name(self, name):
445406
"""Get the width of the character from a type1 character name."""
446407
return self._metrics_by_name[name].width
447408

448-
def get_height_char(self, c, isord=False):
449-
"""Get the bounding box (ink) height of character *c* (space is 0)."""
450-
if not isord:
451-
c = ord(c)
452-
return self._metrics[c].bbox[-1]
453-
454-
def get_kern_dist(self, c1, c2):
455-
"""
456-
Return the kerning pair distance (possibly 0) for chars *c1* and *c2*.
457-
"""
458-
name1, name2 = self.get_name_char(c1), self.get_name_char(c2)
459-
return self.get_kern_dist_from_name(name1, name2)
460-
461409
def get_kern_dist_from_name(self, name1, name2):
462410
"""
463-
Return the kerning pair distance (possibly 0) for chars
464-
*name1* and *name2*.
411+
Return the kerning pair distance (possibly 0) for chars *name1* and *name2*.
465412
"""
466413
return self._kern.get((name1, name2), 0)
467414

@@ -493,7 +440,7 @@ def get_familyname(self):
493440
return re.sub(extras, '', name)
494441

495442
@property
496-
def family_name(self):
443+
def family_name(self): # For consistency with FT2Font.
497444
"""The font family name, e.g., 'Times'."""
498445
return self.get_familyname()
499446

@@ -516,17 +463,3 @@ def get_xheight(self):
516463
def get_underline_thickness(self):
517464
"""Return the underline thickness as float."""
518465
return self._header[b'UnderlineThickness']
519-
520-
def get_horizontal_stem_width(self):
521-
"""
522-
Return the standard horizontal stem width as float, or *None* if
523-
not specified in AFM file.
524-
"""
525-
return self._header.get(b'StdHW', None)
526-
527-
def get_vertical_stem_width(self):
528-
"""
529-
Return the standard vertical stem width as float, or *None* if
530-
not specified in AFM file.
531-
"""
532-
return self._header.get(b'StdVW', None)

lib/matplotlib/backends/backend_ps.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424

2525
import matplotlib as mpl
2626
from matplotlib import _api, cbook, _path, _text_helpers
27-
from matplotlib._afm import AFM
2827
from matplotlib.backend_bases import (
2928
_Backend, FigureCanvasBase, FigureManagerBase, RendererBase)
3029
from matplotlib.cbook import is_writable_file_like, file_requires_unicode
@@ -787,7 +786,7 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
787786
width = font.get_width_from_char_name(name)
788787
except KeyError:
789788
name = 'question'
790-
width = font.get_width_char('?')
789+
width = font.get_width_char(ord('?'))
791790
kern = font.get_kern_dist_from_name(last_name, name)
792791
last_name = name
793792
thisx += kern * scale
@@ -835,9 +834,7 @@ def draw_mathtext(self, gc, x, y, s, prop, angle):
835834
lastfont = font.postscript_name, fontsize
836835
self._pswriter.write(
837836
f"/{font.postscript_name} {fontsize} selectfont\n")
838-
glyph_name = (
839-
font.get_name_char(chr(num)) if isinstance(font, AFM) else
840-
font.get_glyph_name(font.get_char_index(num)))
837+
glyph_name = font.get_glyph_name(font.get_char_index(num))
841838
self._pswriter.write(
842839
f"{ox:g} {oy:g} moveto\n"
843840
f"/{glyph_name} glyphshow\n")

lib/matplotlib/tests/test_afm.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,11 @@ def test_malformed_header(afm_data, caplog):
135135
_afm._parse_header(fh)
136136

137137
assert len(caplog.records) == 1
138+
139+
140+
def test_afm_kerning():
141+
fn = fm.findfont("Helvetica", fontext="afm")
142+
with open(fn, 'rb') as fh:
143+
afm = _afm.AFM(fh)
144+
assert afm.get_kern_dist_from_name('A', 'V') == -70.0
145+
assert afm.get_kern_dist_from_name('V', 'A') == -80.0

lib/matplotlib/tests/test_text.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -208,13 +208,6 @@ def test_antialiasing():
208208
mpl.rcParams['text.antialiased'] = False # Should not affect existing text.
209209

210210

211-
def test_afm_kerning():
212-
fn = mpl.font_manager.findfont("Helvetica", fontext="afm")
213-
with open(fn, 'rb') as fh:
214-
afm = mpl._afm.AFM(fh)
215-
assert afm.string_width_height('VAVAVAVAVAVA') == (7174.0, 718)
216-
217-
218211
@image_comparison(['text_contains.png'])
219212
def test_contains():
220213
fig = plt.figure()

0 commit comments

Comments
 (0)