Skip to content

Commit e98bb83

Browse files
committed
Recognize abbreviations of PostScript code
Type-1 fonts are required to have subroutines with specific contents but their names may vary. They are usually ND, NP and RD but names like | and |- appear too.
1 parent 3d443e5 commit e98bb83

File tree

2 files changed

+24
-5
lines changed

2 files changed

+24
-5
lines changed

lib/matplotlib/tests/test_type1font.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ def test_Type1Font():
4242
assert slanted.prop['ItalicAngle'] == -45
4343
assert font.prop['Encoding'][5] == 'Pi'
4444
assert isinstance(font.prop['CharStrings']['Pi'], bytes)
45+
assert font._abbr['ND'] == 'ND'
4546

4647
differ = difflib.Differ()
4748
diff = list(differ.compare(
@@ -85,6 +86,7 @@ def test_Type1Font_2():
8586
assert font.prop['Encoding'][65] == 'A' # the font uses StandardEncoding
8687
(pos0, pos1), = font._pos['Encoding']
8788
assert font.parts[0][pos0:pos1] == b'/Encoding StandardEncoding'
89+
assert font._abbr['ND'] == '|-'
8890

8991

9092
def test_tokenize():

lib/matplotlib/type1font.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -344,11 +344,14 @@ class Type1Font:
344344
Subrs - array of byte code subroutines
345345
OtherSubrs - bytes object encoding some PostScript code
346346
"""
347-
__slots__ = ('parts', 'decrypted', 'prop', '_pos')
347+
__slots__ = ('parts', 'decrypted', 'prop', '_pos', '_abbr')
348348
# the _pos dict contains (begin, end) indices to parts[0] + decrypted
349349
# so that they can be replaced when transforming the font;
350350
# but since sometimes a definition appears in both parts[0] and decrypted,
351351
# _pos[name] is an array of such pairs
352+
#
353+
# _abbr maps three standard abbreviations to their particular names in
354+
# this font (e.g. 'RD' is named '-|' in some fonts)
352355

353356
def __init__(self, input):
354357
"""
@@ -368,6 +371,7 @@ def __init__(self, input):
368371
self.parts = self._split(data)
369372

370373
self.decrypted = self._decrypt(self.parts[1], 'eexec')
374+
self._abbr = {'RD': 'RD', 'ND': 'ND', 'NP': 'NP'}
371375
self._parse()
372376

373377
def _read(self, file):
@@ -552,10 +556,18 @@ def _parse(self):
552556
break
553557

554558
# sometimes noaccess def and readonly def are abbreviated
555-
if kw.is_name(b'def', b'ND', b'RD', b'|-'):
559+
if kw.is_keyword('def', self._abbr['ND'], self._abbr['NP']):
556560
prop[key] = value
557561
pos.setdefault(key, []).append((keypos, kw.endpos()))
558562

563+
# detect the standard abbreviations
564+
if value == '{noaccess def}':
565+
self._abbr['ND'] = key
566+
elif value == '{noaccess put}':
567+
self._abbr['NP'] = key
568+
elif value == '{string currentfile exch readstring pop}':
569+
self._abbr['RD'] = key
570+
559571
# Fill in the various *Name properties
560572
if 'FontName' not in prop:
561573
prop['FontName'] = (prop.get('FullName') or
@@ -604,9 +616,14 @@ def _parse_subrs(self, tokens, _data):
604616
"Second token following dup in Subrs definition must "
605617
f"be a number, was {nbytes_token}"
606618
)
607-
token = next(tokens) # usually RD or |- but the font can define this to be anything
608-
binary_token = tokens.send(1+nbytes_token.numeric_value())
609-
array[index_token.numeric_value()] = binary_token.value[1:]
619+
token = next(tokens)
620+
if not token.is_keyword(self._abbr['RD']):
621+
raise RuntimeError(
622+
f"Token preceding subr must be {self._abbr['RD']}, "
623+
f"was {token}"
624+
)
625+
binary_token = tokens.send(1+nbytes_token.value())
626+
array[index_token.value()] = binary_token.value()
610627

611628
return array, next(tokens).endpos()
612629

0 commit comments

Comments
 (0)