8000 Fix loading of encoded fonts in textpath. · anntzer/matplotlib@2c0f5ec · GitHub
[go: up one dir, main page]

Skip to content

Commit 2c0f5ec

Browse files
committed
Fix loading of encoded fonts in textpath.
Consider the following example. import matplotlib.pyplot as plt plt.rcParams['text.usetex'] = True plt.rcParams['text.latex.preamble'] = r'\usepackage{siunitx}' plt.rcParams['text.hinting_factor'] = 1 plt.text(.5, .5, r'$\si{\degree}$') plt.text(.5, .4, r'ff\textwon') plt.gca().set_axis_off() plt.savefig('/tmp/plot.svg') plt.savefig('/tmp/plot.pdf') plt.savefig('/tmp/plot.png') plt.show() In the svg output, one sees that the \degree and \textwon characters (which come from a different font that the ff ligature) are now correctly loaded, *but* at a too small size -- this still needs to be fixed. (pdf and png output are unaffected.)
1 parent 8be01cc commit 2c0f5ec

File tree

2 files changed

+71
-49
lines changed

2 files changed

+71
-49
lines changed

lib/matplotlib/dviread.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -968,10 +968,41 @@ def _parse(file):
968968
raise ValueError("Cannot locate end of encoding in {}"
969969
.format(file))
970970
data = data[:end]
971-
972971
return re.findall(br'/([^][{}<>\s]+)', data)
973972

974973

974+
# Note: this function should ultimately replace the Encoding class, which
975+
# appears to be mostly broken: because it uses b''.join(), there is no
976+
# whitespace left between glyph names (only slashes) so the final re.findall
977+
# returns a single string with all glyph names. However this does not appear
978+
# to bother backend_pdf, so that needs to be investigated more. (The fixed
979+
# version below is necessary for textpath/backend_svg, though.)
980+
def _parse_enc(path):
981+
r"""
982+
Parses a \*.enc file referenced from a psfonts.map style file.
983+
The format this class understands is a very limited subset of PostScript.
984+
985+
Parameters
986+
----------
987+
path : os.PathLike
988+
989+
Returns
990+
-------
991+
encoding : list
992+
The nth entry of the list is the PostScript glyph name of the nth
993+
glyph.
994+
"""
995+
with open(path, encoding="ascii") as file:
996+
no_comments = "\n".join(line.split("%")[0].rstrip() for line in file)
997+
array = re.search(r"(?s)\[(.*)\]", no_comments).group(1)
998+
lines = [line for line in array.split("\n") if line]
10000 999+
if all(line.startswith("/") for line in lines):
1000+
return [line[1:] for line in lines]
1001+
else:
1002+
raise ValueError(
1003+
"Failed to parse {} as Postscript encoding".format(path))
1004+
1005+
9751006
@lru_cache()
9761007
def find_tex_file(filename, format=None):
9771008
"""

lib/matplotlib/textpath.py

Lines changed: 39 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,6 @@
1616
_log = logging.getLogger(__name__)
1717

1818

19-
@functools.lru_cache(1)
20-
def _get_adobe_standard_encoding():
21-
enc_name = dviread.find_tex_file('8a.enc')
22-
enc = dviread.Encoding(enc_name)
23-
return {c: i for i, c in enumerate(enc.encoding)}
24-
25-
2619
class TextToPath(object):
2720
"""A class that converts strings to paths."""
2821

@@ -291,12 +284,8 @@ def get_texmanager(self):
291284

292285
def get_glyphs_tex(self, prop, s, glyph_map=None,
293286
return_new_glyphs_only=False):
294-
"""
295-
Process string *s* with usetex and convert it to a (vertices, codes)
296-
pair.
297-
"""
298-
299-
# Implementation mostly borrowed from pdf backend.
287+
"""Convert the string *s* to vertices and codes using usetex mode."""
288+
# Mostly borrowed from pdf backend.
300289

301290
dvifile = self.get_texmanager().make_dvi(s, self.FONT_SCALE)
302291
with dviread.Dvi(dvifile, self.DPI) as dvi:
@@ -321,21 +310,13 @@ def get_glyphs_tex(self, prop, s, glyph_map=None,
321310
if char_id not in glyph_map:
322311
font.clear()
323312
font.set_size(self.FONT_SCALE, self.DPI)
324-
if enc:
325-
charcode = enc.get(glyph, None)
326-
else:
327-
charcode = glyph
328-
329-
ft2font_flag = LOAD_TARGET_LIGHT
330-
if charcode is not None:
331-
glyph0 = font.load_char(charcode, flags=ft2font_flag)
313+
# See comments in _get_ps_font_and_encoding.
314+
if enc is not None:
315+
index = font.get_name_index(enc[glyph])
316+
font.load_glyph(index, flags=LOAD_TARGET_LIGHT)
332317
else:
333-
_log.warning("The glyph (%d) of font (%s) cannot be "
334-
"converted with the encoding. Glyph may "
335-
"be wrong.", glyph, font.fname)
336-
337-
glyph0 = font.load_char(glyph, flags=ft2font_flag)
338-
318+
index = glyph
319+
font.load_char(index, flags=LOAD_TARGET_LIGHT)
339320
glyph_map_new[char_id] = font.get_path()
340321

341322
glyph_ids.append(char_id)
@@ -363,31 +344,41 @@ def _get_ps_font_and_encoding(texname):
363344
font_bunch = tex_font_map[texname]
364345
if font_bunch.filename is None:
365346
raise ValueError(
366-
("No usable font file found for %s (%s). "
367-
"The font may lack a Type-1 version.")
368-
% (font_bunch.psname, texname))
347+
f"No usable font file found for {font_bunch.psname} "
348+
f"({texname}). The font may lack a Type-1 version.")
369349

370350
font = get_font(font_bunch.filename)
371351

372-
for charmap_name, charmap_code in [("ADOBE_CUSTOM", 1094992451),
373-
("ADOBE_STANDARD", 1094995778)]:
374-
try:
375-
font.select_charmap(charmap_code)
376-
except (ValueError, RuntimeError):
377-
pass
378-
else:
379-
break
352+
if font_bunch.encoding:
353+
# If psfonts.map specifies an encoding, use it: it gives us a
354+
# mapping of glyph indices to Adobe glyph names; use it to convert
355+
# dvi indices to glyph names and use the FreeType-synthesized
356+
# unicode charmap to convert glyph names to glyph indices (with
357+
# FT_Get_Name_Index/get_name_index), and load the glyph using
358+
# FT_Load_Glyph/load_glyph. (That charmap has a coverage at least
359+
# as good as, and possibly better than, the native charmaps.)
360+
enc = dviread._parse_enc(font_bunch.encoding)
380361
else:
381-
charmap_name = ""
382-
_log.warning("No supported encoding in font (%s).",
383-
font_bunch.filename)
384-
385-
if charmap_name == "ADOBE_STANDARD" and font_bunch.encoding:
386-
enc0 = dviread.Encoding(font_bunch.encoding)
387-
enc = {i: _get_adobe_standard_encoding().get(c, None)
388-
for i, c in enumerate(enc0.encoding)}
389-
else:
390-
enc = {}
362+
# If psfonts.map specifies no encoding, the indices directly
363+
# map to the font's "native" charmap; so don't use the
364+
# FreeType-synthesized charmap but the native ones (we can't
365+
# directly identify it but it's typically an Adobe charmap), and
366+
# directly load the dvi glyph indices using FT_Load_Char/load_char.
367+
for charmap_name, charmap_code in [
368+
("ADOBE_CUSTOM", 1094992451),
369+
("ADOBE_STANDARD", 1094995778),
370+
]:
371+
try:
372+
font.select_charmap(charmap_code)
373+
except (ValueError, RuntimeError):
374+
pass
375+
else:
376+
break
377+
else:
378+
charmap_name = ""
379+
_log.warning("No supported encoding in font (%s).",
380+
font_bunch.filename)
381+
enc = None
391382

392383
return font, enc
393384

0 commit comments

Comments
 (0)
0