Skip to content

Commit 02c5f49

Browse files
committed
Fix notdef handling when subsetting Type 1 fonts
There's no guarantee that `.notdef` will appear as character code 0 in the encoding table [1], so always inserting it there may clobber an existing glyph. And in fact, with the strange encoding of Computer Modern, its symbol font _does_ have an entry in 0 for the minus sign. We only actually need `.notdef` in the `todo` list of charstrings to simulate, not in the `encoding` dictionary (which is only used again in `_postscript_encoding`, and that specifically ignores `.notdef`), so move it there. Also, fix an inefficiency when simulating charstrings: once we simulate a glyph, it should be added to the `done` set immediately, otherwise it remains on the `todo` set, and gets simulated a second time (or possibly more since the set may `pop` in any order.) [1] See the Adobe Type 1 Font Format specification in https://adobe-type-tools.github.io/font-tech-notes/pdfs/T1_SPEC.pdf which says that "A Type 1 font program must have a `.notdef` character defined in its CharStrings dictionary, even if it is not referenced by the encoding vector." but does _not_ say it must be 0.
1 parent 1cba207 commit 02c5f49

4 files changed

Lines changed: 8 additions & 7 deletions

File tree

lib/matplotlib/_type1font.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -866,17 +866,16 @@ def subset(self, characters, name_prefix):
866866
encoding = {code: glyph
867867
for code, glyph in self.prop['Encoding'].items()
868868
if code in characters}
869-
encoding[0] = '.notdef'
870869
# todo and done include strings (glyph names)
871-
todo = set(encoding.values())
870+
todo = {'.notdef', *encoding.values()}
872871
done = set()
873872
seen_subrs = {0, 1, 2, 3}
874873
while todo:
875874
glyph = todo.pop()
876875
called_glyphs, called_subrs = _CharstringSimulator(self).run(glyph)
876+
done.add(glyph)
877877
todo.update(called_glyphs - done)
878878
seen_subrs.update(called_subrs)
879-
done.add(glyph)
880879

881880
charstrings = self._subset_charstrings(done)
882881
subrs = self._subset_subrs(seen_subrs)
@@ -949,7 +948,8 @@ def _charstring_tokens(data):
949948
def _postscript_encoding(self, encoding):
950949
"""Return a PostScript encoding array for the encoding."""
951950
return '\n'.join([
952-
'/Encoding 256 array\n0 1 255 { 1 index exch /.notdef put} for',
951+
'/Encoding 256 array',
952+
'0 1 255 { 1 index exch /.notdef put} for',
953953
*(
954954
f'dup {i} /{glyph} put'
955955
for i, glyph in sorted(encoding.items())
7.65 KB
Binary file not shown.
200 Bytes
Loading

lib/matplotlib/tests/test_usetex.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@ def test_usetex():
2626
kwargs = {"verticalalignment": "baseline", "size": 24,
2727
"bbox": dict(pad=0, edgecolor="k", facecolor="none")}
2828
ax.text(0.2, 0.7,
29-
# the \LaTeX macro exercises character sizing and placement,
29+
# The \LaTeX macro exercises character sizing and placement,
3030
# \left[ ... \right\} draw some variable-height characters,
31-
# \sqrt and \frac draw horizontal rules, \mathrm changes the font
32-
r'\LaTeX\ $\left[\int\limits_e^{2e}'
31+
# \sqrt and \frac draw horizontal rules, \mathrm changes the font,
32+
# the minus sign hits character code 0 in the Type 1 font that LaTeX uses.
33+
r'\LaTeX\ $\left[\int\limits_e^{-2e}'
3334
r'\sqrt\frac{\log^3 x}{x}\,\mathrm{d}x \right\}$',
3435
**kwargs)
3536
ax.text(0.2, 0.3, "lg", **kwargs)

0 commit comments

Comments
 (0)