Skip to content

Commit 07f08de

Browse files
authored
Merge pull request matplotlib#14876 from anntzer/afm
Inline some afm parsing code.
2 parents 5ef93f9 + 31c04ad commit 07f08de

File tree

2 files changed

+64
-53
lines changed

2 files changed

+64
-53
lines changed

lib/matplotlib/afm.py

Lines changed: 17 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -85,25 +85,6 @@ def _to_bool(s):
8585
return True
8686

8787

88-
def _sanity_check(fh):
89-
"""
90-
Check if the file looks like AFM; if it doesn't, raise `RuntimeError`.
91-
"""
92-
# Remember the file position in case the caller wants to
93-
# do something else with the file.
94-
pos = fh.tell()
95-
try:
96-
line = next(fh)
97-
finally:
98-
fh.seek(pos, 0)
99-
# AFM spec, Section 4: The StartFontMetrics keyword [followed by a
100-
# version number] must be the first line in the file, and the
101-
# EndFontMetrics keyword must be the last non-empty line in the
102-
# file. We just check the first line.
103-
if not line.startswith(b'StartFontMetrics'):
104-
raise RuntimeError('Not an AFM file')
105-
106-
10788
def _parse_header(fh):
10889
"""
10990
Reads the font metrics header (up to the char metrics) and returns
@@ -152,18 +133,26 @@ def _parse_header(fh):
152133
}
153134

154135
d = {}
136+
first_line = True
155137
for line in fh:
156138
line = line.rstrip()
157139
if line.startswith(b'Comment'):
158140
continue
159141
lst = line.split(b' ', 1)
160-
161142
key = lst[0]
143+
if first_line:
144+
# AFM spec, Section 4: The StartFontMetrics keyword
145+
# [followed by a version number] must be the first line in
146+
# the file, and the EndFontMetrics keyword must be the
147+
# last non-empty line in the file. We just check the
148+
# first header entry.
149+
if key != b'StartFontMetrics':
150+
raise RuntimeError('Not an AFM file')
151+
first_line = False
162152
if len(lst) == 2:
163153
val = lst[1]
164154
else:
165155
val = b''
166-
167156
try:
168157
converter = header_converters[key]
169158
except KeyError:
@@ -175,8 +164,10 @@ def _parse_header(fh):
175164
_log.error('Value error parsing header in AFM: %s, %s', key, val)
176165
continue
177166
if key == b'StartCharMetrics':
178-
return d
179-
raise RuntimeError('Bad parse')
167+
break
168+
else:
169+
raise RuntimeError('Bad parse')
170+
return d
180171

181172

182173
CharMetrics = namedtuple('CharMetrics', 'width, name, bbox')
@@ -366,40 +357,13 @@ def _parse_optional(fh):
366357
return d[b'StartKernData'], d[b'StartComposites']
367358

368359

369-
def _parse_afm(fh):
370-
"""
371-
Parse the Adobe Font Metrics file in file handle *fh*.
372-
373-
Returns
374-
-------
375-
header : dict
376-
A header dict. See :func:`_parse_header`.
377-
cmetrics_by_ascii : dict
378-
From :func:`_parse_char_metrics`.
379-
cmetrics_by_name : dict
380-
From :func:`_parse_char_metrics`.
381-
kernpairs : dict
382-
From :func:`_parse_kern_pairs`.
383-
composites : dict
384-
From :func:`_parse_composites`
385-
386-
"""
387-
_sanity_check(fh)
388-
header = _parse_header(fh)
389-
cmetrics_by_ascii, cmetrics_by_name = _parse_char_metrics(fh)
390-
kernpairs, composites = _parse_optional(fh)
391-
return header, cmetrics_by_ascii, cmetrics_by_name, kernpairs, composites
392-
393-
394360
class AFM:
395361

396362
def __init__(self, fh):
397363
"""Parse the AFM file in file object *fh*."""
398-
(self._header,
399-
self._metrics,
400-
self._metrics_by_name,
401-
self._kern,
402-
self._composite) = _parse_afm(fh)
364+
self._header = _parse_header(fh)
365+
self._metrics, self._metrics_by_name = _parse_char_metrics(fh)
366+
self._kern, self._composite = _parse_optional(fh)
403367

404368
def get_bbox_char(self, c, isord=False):
405369
if not isord:

lib/matplotlib/tests/test_afm.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
from io import BytesIO
2+
import pytest
3+
import logging
24

35
from matplotlib import afm
46
from matplotlib import font_manager as fm
@@ -88,3 +90,48 @@ def test_font_manager_weight_normalization():
8890
font = afm.AFM(BytesIO(
8991
AFM_TEST_DATA.replace(b"Weight Bold\n", b"Weight Custom\n")))
9092
assert fm.afmFontProperty("", font).weight == "normal"
93+
94+
95+
@pytest.mark.parametrize(
96+
"afm_data",
97+
[
98+
b"""nope
99+
really nope""",
100+
b"""StartFontMetrics 2.0
101+
Comment Comments are ignored.
102+
Comment Creation Date:Mon Nov 13 12:34:11 GMT 2017
103+
FontName MyFont-Bold
104+
EncodingScheme FontSpecific""",
105+
],
106+
)
107+
def test_bad_afm(afm_data):
108+
fh = BytesIO(afm_data)
109+
with pytest.raises(RuntimeError):
110+
header = afm._parse_header(fh)
111+
112+
113+
@pytest.mark.parametrize(
114+
"afm_data",
115+
[
116+
b"""StartFontMetrics 2.0
117+
Comment Comments are ignored.
118+
Comment Creation Date:Mon Nov 13 12:34:11 GMT 2017
119+
Aardvark bob
120+
FontName MyFont-Bold
121+
EncodingScheme FontSpecific
122+
StartCharMetrics 3""",
123+
b"""StartFontMetrics 2.0
124+
Comment Comments are ignored.
125+
Comment Creation Date:Mon Nov 13 12:34:11 GMT 2017
126+
ItalicAngle zero degrees
127+
FontName MyFont-Bold
128+
EncodingScheme FontSpecific
129+
StartCharMetrics 3""",
130+
],
131+
)
132+
def test_malformed_header(afm_data, caplog):
133+
fh = BytesIO(afm_data)
134+
with caplog.at_level(logging.ERROR):
135+
header = afm._parse_header(fh)
136+
137+
assert len(caplog.records) == 1

0 commit comments

Comments
 (0)