Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 2a82d0c

Browse filesBrowse files
authored
Clean up AFM code (#30121)
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 335e6b4 commit 2a82d0c
Copy full SHA for 2a82d0c

File tree

Expand file treeCollapse file tree

4 files changed

+49
-118
lines changed
Filter options
Expand file treeCollapse file tree

4 files changed

+49
-118
lines changed

‎lib/matplotlib/_afm.py

Copy file name to clipboardExpand all lines: lib/matplotlib/_afm.py
+39-106Lines 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

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/backend_ps.py
+2-5Lines 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

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_afm.py
+8Lines 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

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_text.py
-7Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.