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

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

2 files changed

Lines changed: 71 additions & 49 deletions

File tree

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]
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)