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

Skip to content

Commit d4b9701

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 369618a commit d4b9701

File tree

2 files changed

+71
-50
lines changed

2 files changed

+71
-50
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+
"""
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]
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 & 49 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
"""
2821
A class that convert a given text to a path using ttf fonts.
@@ -297,12 +290,8 @@ def get_texmanager(self):
297290

298291
def get_glyphs_tex(self, prop, s, glyph_map=None,
299292
return_new_glyphs_only=False):
300-
"""
301-
convert the string *s* to vertices and codes using matplotlib's usetex
302-
mode.
303-
"""
304-
305-
# codes are modstly borrowed from pdf backend.
293+
"""Convert the string *s* to vertices and codes using usetex mode."""
294+
# Mostly borrowed from pdf backend.
306295

307296
dvifile = self.get_texmanager().make_dvi(s, self.FONT_SCALE)
308297
with dviread.Dvi(dvifile, self.DPI) as dvi:
@@ -320,29 +309,20 @@ def get_glyphs_tex(self, prop, s, glyph_map=None,
320309

321310
# Gather font information and do some setup for combining
322311
# characters into strings.
323-
# oldfont, seq = None, []
324312
for x1, y1, dvifont, glyph, width in page.text:
325313
font, enc = self._get_ps_font_and_encoding(dvifont.texname)
326314
char_id = self._get_char_id_ps(font, glyph)
327315

328316
if char_id not in glyph_map:
329317
font.clear()
330318
font.set_size(self.FONT_SCALE, self.DPI)
331-
if enc:
332-
charcode = enc.get(glyph, None)
333-
else:
334-
charcode = glyph
335-
336-
ft2font_flag = LOAD_TARGET_LIGHT
337-
if charcode is not None:
338-
glyph0 = font.load_char(charcode, flags=ft2font_flag)
319+
# See comments in _get_ps_font_and_encoding.
320+
if enc is not None:
321+
index = font.get_name_index(enc[glyph])
322+
font.load_glyph(index, flags=LOAD_TARGET_LIGHT)
339323
else:
340-
_log.warning("The glyph (%d) of font (%s) cannot be "
341-
"converted with the encoding. Glyph may "
342-
"be wrong.", glyph, font.fname)
343-
344-
glyph0 = font.load_char(glyph, flags=ft2font_flag)
345-
324+
index = glyph
325+
font.load_char(index, flags=LOAD_TARGET_LIGHT)
346326
glyph_map_new[char_id] = self.glyph_to_path(font)
347327

348328
glyph_ids.append(char_id)
@@ -370,31 +350,41 @@ def _get_ps_font_and_encoding(texname):
370350
font_bunch = tex_font_map[texname]
371351
if font_bunch.filename is None:
372352
raise ValueError(
373-
("No usable font file found for %s (%s). "
374-
"The font may lack a Type-1 version.")
375-
% (font_bunch.psname, texname))
353+
"No usable font file found for {} ({}). "
354+
"The font may lack a Type-1 version."
355+
.format(font_bunch.psname, texname))
376356

377357
font = get_font(font_bunch.filename)
378358

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

399389
return font, enc
400390

0 commit comments

Comments
 (0)