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

Skip to content

textpath encoding #12928

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 2, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion lib/matplotlib/dviread.py
Original file line number Diff line number Diff line change
Expand Up @@ -968,10 +968,41 @@ def _parse(file):
raise ValueError("Cannot locate end of encoding in {}"
.format(file))
data = data[:end]

return re.findall(br'/([^][{}<>\s]+)', data)


# Note: this function should ultimately replace the Encoding class, which
# appears to be mostly broken: because it uses b''.join(), there is no
# whitespace left between glyph names (only slashes) so the final re.findall
# returns a single string with all glyph names. However this does not appear
# to bother backend_pdf, so that needs to be investigated more. (The fixed
# version below is necessary for textpath/backend_svg, though.)
def _parse_enc(path):
r"""
Parses a \*.enc file referenced from a psfonts.map style file.
The format this class understands is a very limited subset of PostScript.

Parameters
----------
path : os.PathLike

Returns
-------
encoding : list
The nth entry of the list is the PostScript glyph name of the nth
glyph.
"""
with open(path, encoding="ascii") as file:
no_comments = "\n".join(line.split("%")[0].rstrip() for line in file)
array = re.search(r"(?s)\[(.*)\]", no_comments).group(1)
lines = [line for line in array.split("\n") if line]
if all(line.startswith("/") for line in lines):
return [line[1:] for line in lines]
else:
raise ValueError(
"Failed to parse {} as Postscript encoding".format(path))


@lru_cache()
def find_tex_file(filename, format=None):
"""
Expand Down
40 changes: 26 additions & 14 deletions lib/matplotlib/tests/test_backend_svg.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
import numpy as np
from io import BytesIO
import os
import re
import tempfile
import warnings
import xml.parsers.expat

import pytest

import matplotlib as mpl
from matplotlib import dviread
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
from matplotlib.testing.decorators import image_comparison
import matplotlib
from matplotlib import dviread


with warnings.catch_warnings():
warnings.simplefilter('ignore')
needs_usetex = pytest.mark.skipif(
not matplotlib.checkdep_usetex(True),
not mpl.checkdep_usetex(True),
reason="This test needs a TeX installation")


Expand Down Expand Up @@ -107,15 +109,10 @@ def test_bold_font_output_with_none_fonttype():

def _test_determinism_save(filename, usetex):
# This function is mostly copy&paste from "def test_visibility"
# To require no GUI, we use Figure and FigureCanvasSVG
# instead of plt.figure and fig.savefig
from matplotlib.figure import Figure
from matplotlib.backends.backend_svg import FigureCanvasSVG
from matplotlib import rc
rc('svg', hashsalt='asdf')
rc('text', usetex=usetex)
mpl.rc('svg', hashsalt='asdf')
mpl.rc('text', usetex=usetex)

fig = Figure()
fig = Figure() # Require no GUI.
ax = fig.add_subplot(111)

x = np.linspace(0, 4 * np.pi, 50)
Expand All @@ -129,7 +126,7 @@ def _test_determinism_save(filename, usetex):
ax.set_xlabel('A string $1+2+\\sigma$')
ax.set_ylabel('A string $1+2+\\sigma$')

FigureCanvasSVG(fig).print_svg(filename)
fig.savefig(filename, format="svg")


@pytest.mark.parametrize(
Expand Down Expand Up @@ -172,15 +169,30 @@ def test_determinism(filename, usetex):
@needs_usetex
def test_missing_psfont(monkeypatch):
"""An error is raised if a TeX font lacks a Type-1 equivalent"""
from matplotlib import rc

def psfont(*args, **kwargs):
return dviread.PsFont(texname='texfont', psname='Some Font',
effects=None, encoding=None, filename=None)

monkeypatch.setattr(dviread.PsfontsMap, '__getitem__', psfont)
rc('text', usetex=True)
mpl.rc('text', usetex=True)
fig, ax = plt.subplots()
ax.text(0.5, 0.5, 'hello')
with tempfile.TemporaryFile() as tmpfile, pytest.raises(ValueError):
fig.savefig(tmpfile, format='svg')


# Use Computer Modern Sans Serif, not Helvetica (which has no \textwon).
@pytest.mark.style('default')
@needs_usetex
def test_unicode_won():
fig = Figure()
fig.text(.5, .5, r'\textwon', usetex=True)

with BytesIO() as fd:
fig.savefig(fd, format='svg')
buf = fd.getvalue().decode('ascii')

won_id = 'Computer_Modern_Sans_Serif-142'
assert re.search(r'<path d=(.|\s)*?id="{0}"/>'.format(won_id), buf)
assert re.search(r'<use[^/>]*? xlink:href="#{0}"/>'.format(won_id), buf)
94 changes: 46 additions & 48 deletions lib/matplotlib/textpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,6 @@
_log = logging.getLogger(__name__)


@functools.lru_cache(1)
def _get_adobe_standard_encoding():
enc_name = dviread.find_tex_file('8a.enc')
enc = dviread.Encoding(enc_name)
return {c: i for i, c in enumerate(enc.encoding)}


class TextToPath(object):
"""A class that converts strings to paths."""

Expand Down Expand Up @@ -291,12 +284,8 @@ def get_texmanager(self):

def get_glyphs_tex(self, prop, s, glyph_map=None,
return_new_glyphs_only=False):
"""
Process string *s* with usetex and convert it to a (vertices, codes)
pair.
"""

# Implementation mostly borrowed from pdf backend.
"""Convert the string *s* to vertices and codes using usetex mode."""
# Mostly borrowed from pdf backend.

dvifile = self.get_texmanager().make_dvi(s, self.FONT_SCALE)
with dviread.Dvi(dvifile, self.DPI) as dvi:
Expand All @@ -321,21 +310,20 @@ def get_glyphs_tex(self, prop, s, glyph_map=None,
if char_id not in glyph_map:
font.clear()
font.set_size(self.FONT_SCALE, self.DPI)
if enc:
charcode = enc.get(glyph, None)
else:
charcode = glyph

ft2font_flag = LOAD_TARGET_LIGHT
if charcode is not None:
glyph0 = font.load_char(charcode, flags=ft2font_flag)
# See comments in _get_ps_font_and_encoding.
if enc is not None:
if glyph not in enc:
_log.warning(
"The glyph %d of font %s cannot be converted with "
"the encoding; glyph may be wrong.",
glyph, font.fname)
font.load_char(glyph, flags=LOAD_TARGET_LIGHT)
else:
index = font.get_name_index(enc[glyph])
font.load_glyph(index, flags=LOAD_TARGET_LIGHT)
else:
_log.warning("The glyph (%d) of font (%s) cannot be "
"converted with the encoding. Glyph may "
"be wrong.", glyph, font.fname)

glyph0 = font.load_char(glyph, flags=ft2font_flag)

index = glyph
font.load_char(index, flags=LOAD_TARGET_LIGHT)
glyph_map_new[char_id] = font.get_path()

glyph_ids.append(char_id)
Expand Down Expand Up @@ -363,31 +351,41 @@ def _get_ps_font_and_encoding(texname):
font_bunch = tex_font_map[texname]
if font_bunch.filename is None:
raise ValueError(
("No usable font file found for %s (%s). "
"The font may lack a Type-1 version.")
% (font_bunch.psname, texname))
f"No usable font file found for {font_bunch.psname} "
f"({texname}). The font may lack a Type-1 version.")

font = get_font(font_bunch.filename)

for charmap_name, charmap_code in [("ADOBE_CUSTOM", 1094992451),
("ADOBE_STANDARD", 1094995778)]:
try:
font.select_charmap(charmap_code)
except (ValueError, RuntimeError):
pass
else:
break
if font_bunch.encoding:
# If psfonts.map specifies an encoding, use it: it gives us a
# mapping of glyph indices to Adobe glyph names; use it to convert
# dvi indices to glyph names and use the FreeType-synthesized
# unicode charmap to convert glyph names to glyph indices (with
# FT_Get_Name_Index/get_name_index), and load the glyph using
# FT_Load_Glyph/load_glyph. (That charmap has a coverage at least
# as good as, and possibly better than, the native charmaps.)
enc = dviread._parse_enc(font_bunch.encoding)
else:
charmap_name = ""
_log.warning("No supported encoding in font (%s).",
font_bunch.filename)

if charmap_name == "ADOBE_STANDARD" and font_bunch.encoding:
enc0 = dviread.Encoding(font_bunch.encoding)
enc = {i: _get_adobe_standard_encoding().get(c, None)
for i, c in enumerate(enc0.encoding)}
else:
enc = {}
# If psfonts.map specifies no encoding, the indices directly
# map to the font's "native" charmap; so don't use the
# FreeType-synthesized charmap but the native ones (we can't
# directly identify it but it's typically an Adobe charmap), and
# directly load the dvi glyph indices using FT_Load_Char/load_char.
for charmap_name, charmap_code in [
("ADOBE_CUSTOM", 1094992451),
("ADOBE_STANDARD", 1094995778),
]:
try:
font.select_charmap(charmap_code)
except (ValueError, RuntimeError):
pass
else:
break
else:
charmap_name = ""
_log.warning("No supported encoding in font (%s).",
font_bunch.filename)
enc = None

return font, enc

Expand Down