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 07f08de

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

File tree

Expand file treeCollapse file tree

2 files changed

+64
-53
lines changed
Filter options
Expand file treeCollapse file tree

2 files changed

+64
-53
lines changed

‎lib/matplotlib/afm.py

Copy file name to clipboardExpand all lines: lib/matplotlib/afm.py
+17-53Lines 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

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