Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 20274b9

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 684a1ea commit 20274b9

File tree

2 files changed

+66
-49
lines changed

2 files changed

+66
-49
lines changed

lib/matplotlib/dviread.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -968,10 +968,36 @@ 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+
return re.findall(r"(?<=/)[A-za-z0-9._]+", array)
999+
1000+
9751001
@lru_cache()
9761002
def find_tex_file(filename, format=None):
9771003
"""

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+
"No usable font file found for {} ({}). "
348+
"The font may lack a Type-1 version."
349+
.format(font_bunch.psname, texname))
369350

370351
font = get_font(font_bunch.filename)
371352

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
353+
if font_bunch.encoding:
354+
# If psfonts.map specifies an encoding, use it: it gives us a
355+
# mapping of glyph indices to Adobe glyph names; use it to convert
356+
# dvi indices to glyph names and use the FreeType-synthesized
357+
# unicode charmap to convert glyph names to glyph indices (with
358+
# FT_Get_Name_Index/get_name_index), and load the glyph using
359+
# FT_Load_Glyph/load_glyph. (That charmap has a coverage at least
360+
# as good as, and possibly better than, the native charmaps.)
361+
enc = dviread._parse_enc(font_bunch.encoding)
380362
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 = {}
363+
# If psfonts.map specifies no encoding, the indices directly map to
364+
# the font's builtin charmap (see the pdftex manual, section 6.1
365+
# -- Map files); so don't use the FreeType-synthesized charmap but
366+
# the native ones (we can't directly identify it but it's typically
367+
# an Adobe charmap), and directly load the dvi glyph indices using
368+
# FT_Load_Char/load_char.
369+
for charmap_name, charmap_code in [("ADOBE_CUSTOM", 1094992451),
370+
("ADOBE_STANDARD", 1094995778)]:
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)