demo + utils venv
This commit is contained in:
563
utils/venv/lib/python3.6/site-packages/matplotlib/textpath.py
Normal file
563
utils/venv/lib/python3.6/site-packages/matplotlib/textpath.py
Normal file
@@ -0,0 +1,563 @@
|
||||
from collections import OrderedDict
|
||||
import functools
|
||||
import urllib.parse
|
||||
import warnings
|
||||
|
||||
import numpy as np
|
||||
|
||||
from matplotlib import cbook, dviread, font_manager, rcParams
|
||||
from matplotlib.font_manager import FontProperties, get_font
|
||||
from matplotlib.ft2font import (
|
||||
KERNING_DEFAULT, LOAD_NO_HINTING, LOAD_TARGET_LIGHT)
|
||||
from matplotlib.mathtext import MathTextParser
|
||||
from matplotlib.path import Path
|
||||
from matplotlib.transforms import Affine2D
|
||||
|
||||
|
||||
@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 convert a given text to a path using ttf fonts.
|
||||
"""
|
||||
|
||||
FONT_SCALE = 100.
|
||||
DPI = 72
|
||||
|
||||
def __init__(self):
|
||||
self.mathtext_parser = MathTextParser('path')
|
||||
self._texmanager = None
|
||||
|
||||
@property
|
||||
@cbook.deprecated("3.0")
|
||||
def tex_font_map(self):
|
||||
return dviread.PsfontsMap(dviread.find_tex_file('pdftex.map'))
|
||||
|
||||
def _get_font(self, prop):
|
||||
"""
|
||||
find a ttf font.
|
||||
"""
|
||||
fname = font_manager.findfont(prop)
|
||||
font = get_font(fname)
|
||||
font.set_size(self.FONT_SCALE, self.DPI)
|
||||
|
||||
return font
|
||||
|
||||
def _get_hinting_flag(self):
|
||||
return LOAD_NO_HINTING
|
||||
|
||||
def _get_char_id(self, font, ccode):
|
||||
"""
|
||||
Return a unique id for the given font and character-code set.
|
||||
"""
|
||||
return urllib.parse.quote('{}-{}'.format(font.postscript_name, ccode))
|
||||
|
||||
def _get_char_id_ps(self, font, ccode):
|
||||
"""
|
||||
Return a unique id for the given font and character-code set (for tex).
|
||||
"""
|
||||
ps_name = font.get_ps_font_info()[2]
|
||||
char_id = urllib.parse.quote('%s-%d' % (ps_name, ccode))
|
||||
return char_id
|
||||
|
||||
def glyph_to_path(self, font, currx=0.):
|
||||
"""
|
||||
convert the ft2font glyph to vertices and codes.
|
||||
"""
|
||||
verts, codes = font.get_path()
|
||||
if currx != 0.0:
|
||||
verts[:, 0] += currx
|
||||
return verts, codes
|
||||
|
||||
def get_text_width_height_descent(self, s, prop, ismath):
|
||||
if rcParams['text.usetex']:
|
||||
texmanager = self.get_texmanager()
|
||||
fontsize = prop.get_size_in_points()
|
||||
w, h, d = texmanager.get_text_width_height_descent(s, fontsize,
|
||||
renderer=None)
|
||||
return w, h, d
|
||||
|
||||
fontsize = prop.get_size_in_points()
|
||||
scale = fontsize / self.FONT_SCALE
|
||||
|
||||
if ismath:
|
||||
prop = prop.copy()
|
||||
prop.set_size(self.FONT_SCALE)
|
||||
|
||||
width, height, descent, trash, used_characters = \
|
||||
self.mathtext_parser.parse(s, 72, prop)
|
||||
return width * scale, height * scale, descent * scale
|
||||
|
||||
font = self._get_font(prop)
|
||||
font.set_text(s, 0.0, flags=LOAD_NO_HINTING)
|
||||
w, h = font.get_width_height()
|
||||
w /= 64.0 # convert from subpixels
|
||||
h /= 64.0
|
||||
d = font.get_descent()
|
||||
d /= 64.0
|
||||
return w * scale, h * scale, d * scale
|
||||
|
||||
def get_text_path(self, prop, s, ismath=False, usetex=False):
|
||||
"""
|
||||
Convert text *s* to path (a tuple of vertices and codes for
|
||||
matplotlib.path.Path).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
prop : `matplotlib.font_manager.FontProperties` instance
|
||||
The font properties for the text.
|
||||
|
||||
s : str
|
||||
The text to be converted.
|
||||
|
||||
usetex : bool, optional
|
||||
Whether to use tex rendering. Defaults to ``False``.
|
||||
|
||||
ismath : bool, optional
|
||||
If True, use mathtext parser. Effective only if
|
||||
``usetex == False``.
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
||||
verts, codes : tuple of lists
|
||||
*verts* is a list of numpy arrays containing the x and y
|
||||
coordinates of the vertices. *codes* is a list of path codes.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
Create a list of vertices and codes from a text, and create a `Path`
|
||||
from those::
|
||||
|
||||
from matplotlib.path import Path
|
||||
from matplotlib.textpath import TextToPath
|
||||
from matplotlib.font_manager import FontProperties
|
||||
|
||||
fp = FontProperties(family="Humor Sans", style="italic")
|
||||
verts, codes = TextToPath().get_text_path(fp, "ABC")
|
||||
path = Path(verts, codes, closed=False)
|
||||
|
||||
Also see `TextPath` for a more direct way to create a path from a text.
|
||||
"""
|
||||
if not usetex:
|
||||
if not ismath:
|
||||
font = self._get_font(prop)
|
||||
glyph_info, glyph_map, rects = self.get_glyphs_with_font(
|
||||
font, s)
|
||||
else:
|
||||
glyph_info, glyph_map, rects = self.get_glyphs_mathtext(
|
||||
prop, s)
|
||||
else:
|
||||
glyph_info, glyph_map, rects = self.get_glyphs_tex(prop, s)
|
||||
|
||||
verts, codes = [], []
|
||||
|
||||
for glyph_id, xposition, yposition, scale in glyph_info:
|
||||
verts1, codes1 = glyph_map[glyph_id]
|
||||
if len(verts1):
|
||||
verts1 = np.array(verts1) * scale + [xposition, yposition]
|
||||
verts.extend(verts1)
|
||||
codes.extend(codes1)
|
||||
|
||||
for verts1, codes1 in rects:
|
||||
verts.extend(verts1)
|
||||
codes.extend(codes1)
|
||||
|
||||
return verts, codes
|
||||
|
||||
def get_glyphs_with_font(self, font, s, glyph_map=None,
|
||||
return_new_glyphs_only=False):
|
||||
"""
|
||||
Convert string *s* to vertices and codes using the provided ttf font.
|
||||
"""
|
||||
|
||||
# Mostly copied from backend_svg.py.
|
||||
|
||||
lastgind = None
|
||||
|
||||
currx = 0
|
||||
xpositions = []
|
||||
glyph_ids = []
|
||||
|
||||
if glyph_map is None:
|
||||
glyph_map = OrderedDict()
|
||||
|
||||
if return_new_glyphs_only:
|
||||
glyph_map_new = OrderedDict()
|
||||
else:
|
||||
glyph_map_new = glyph_map
|
||||
|
||||
# I'm not sure if I get kernings right. Needs to be verified. -JJL
|
||||
|
||||
for c in s:
|
||||
ccode = ord(c)
|
||||
gind = font.get_char_index(ccode)
|
||||
if gind is None:
|
||||
ccode = ord('?')
|
||||
gind = 0
|
||||
|
||||
if lastgind is not None:
|
||||
kern = font.get_kerning(lastgind, gind, KERNING_DEFAULT)
|
||||
else:
|
||||
kern = 0
|
||||
|
||||
glyph = font.load_char(ccode, flags=LOAD_NO_HINTING)
|
||||
horiz_advance = glyph.linearHoriAdvance / 65536
|
||||
|
||||
char_id = self._get_char_id(font, ccode)
|
||||
if char_id not in glyph_map:
|
||||
glyph_map_new[char_id] = self.glyph_to_path(font)
|
||||
|
||||
currx += kern / 64
|
||||
|
||||
xpositions.append(currx)
|
||||
glyph_ids.append(char_id)
|
||||
|
||||
currx += horiz_advance
|
||||
|
||||
lastgind = gind
|
||||
|
||||
ypositions = [0] * len(xpositions)
|
||||
sizes = [1.] * len(xpositions)
|
||||
|
||||
rects = []
|
||||
|
||||
return (list(zip(glyph_ids, xpositions, ypositions, sizes)),
|
||||
glyph_map_new, rects)
|
||||
|
||||
def get_glyphs_mathtext(self, prop, s, glyph_map=None,
|
||||
return_new_glyphs_only=False):
|
||||
"""
|
||||
convert the string *s* to vertices and codes by parsing it with
|
||||
mathtext.
|
||||
"""
|
||||
|
||||
prop = prop.copy()
|
||||
prop.set_size(self.FONT_SCALE)
|
||||
|
||||
width, height, descent, glyphs, rects = self.mathtext_parser.parse(
|
||||
s, self.DPI, prop)
|
||||
|
||||
if not glyph_map:
|
||||
glyph_map = OrderedDict()
|
||||
|
||||
if return_new_glyphs_only:
|
||||
glyph_map_new = OrderedDict()
|
||||
else:
|
||||
glyph_map_new = glyph_map
|
||||
|
||||
xpositions = []
|
||||
ypositions = []
|
||||
glyph_ids = []
|
||||
sizes = []
|
||||
|
||||
currx, curry = 0, 0
|
||||
for font, fontsize, ccode, ox, oy in glyphs:
|
||||
char_id = self._get_char_id(font, ccode)
|
||||
if char_id not in glyph_map:
|
||||
font.clear()
|
||||
font.set_size(self.FONT_SCALE, self.DPI)
|
||||
glyph = font.load_char(ccode, flags=LOAD_NO_HINTING)
|
||||
glyph_map_new[char_id] = self.glyph_to_path(font)
|
||||
|
||||
xpositions.append(ox)
|
||||
ypositions.append(oy)
|
||||
glyph_ids.append(char_id)
|
||||
size = fontsize / self.FONT_SCALE
|
||||
sizes.append(size)
|
||||
|
||||
myrects = []
|
||||
for ox, oy, w, h in rects:
|
||||
vert1 = [(ox, oy), (ox, oy + h), (ox + w, oy + h),
|
||||
(ox + w, oy), (ox, oy), (0, 0)]
|
||||
code1 = [Path.MOVETO,
|
||||
Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO,
|
||||
Path.CLOSEPOLY]
|
||||
myrects.append((vert1, code1))
|
||||
|
||||
return (list(zip(glyph_ids, xpositions, ypositions, sizes)),
|
||||
glyph_map_new, myrects)
|
||||
|
||||
def get_texmanager(self):
|
||||
"""
|
||||
return the :class:`matplotlib.texmanager.TexManager` instance
|
||||
"""
|
||||
if self._texmanager is None:
|
||||
from matplotlib.texmanager import TexManager
|
||||
self._texmanager = TexManager()
|
||||
return self._texmanager
|
||||
|
||||
def get_glyphs_tex(self, prop, s, glyph_map=None,
|
||||
return_new_glyphs_only=False):
|
||||
"""
|
||||
convert the string *s* to vertices and codes using matplotlib's usetex
|
||||
mode.
|
||||
"""
|
||||
|
||||
# codes are modstly borrowed from pdf backend.
|
||||
|
||||
texmanager = self.get_texmanager()
|
||||
|
||||
fontsize = prop.get_size_in_points()
|
||||
if hasattr(texmanager, "get_dvi"):
|
||||
dvifilelike = texmanager.get_dvi(s, self.FONT_SCALE)
|
||||
dvi = dviread.DviFromFileLike(dvifilelike, self.DPI)
|
||||
else:
|
||||
dvifile = texmanager.make_dvi(s, self.FONT_SCALE)
|
||||
dvi = dviread.Dvi(dvifile, self.DPI)
|
||||
with dvi:
|
||||
page = next(iter(dvi))
|
||||
|
||||
if glyph_map is None:
|
||||
glyph_map = OrderedDict()
|
||||
|
||||
if return_new_glyphs_only:
|
||||
glyph_map_new = OrderedDict()
|
||||
else:
|
||||
glyph_map_new = glyph_map
|
||||
|
||||
glyph_ids, xpositions, ypositions, sizes = [], [], [], []
|
||||
|
||||
# Gather font information and do some setup for combining
|
||||
# characters into strings.
|
||||
# oldfont, seq = None, []
|
||||
for x1, y1, dvifont, glyph, width in page.text:
|
||||
font, enc = self._get_ps_font_and_encoding(dvifont.texname)
|
||||
char_id = self._get_char_id_ps(font, glyph)
|
||||
|
||||
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)
|
||||
else:
|
||||
warnings.warn("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)
|
||||
|
||||
glyph_map_new[char_id] = self.glyph_to_path(font)
|
||||
|
||||
glyph_ids.append(char_id)
|
||||
xpositions.append(x1)
|
||||
ypositions.append(y1)
|
||||
sizes.append(dvifont.size / self.FONT_SCALE)
|
||||
|
||||
myrects = []
|
||||
|
||||
for ox, oy, h, w in page.boxes:
|
||||
vert1 = [(ox, oy), (ox + w, oy), (ox + w, oy + h),
|
||||
(ox, oy + h), (ox, oy), (0, 0)]
|
||||
code1 = [Path.MOVETO,
|
||||
Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO,
|
||||
Path.CLOSEPOLY]
|
||||
myrects.append((vert1, code1))
|
||||
|
||||
return (list(zip(glyph_ids, xpositions, ypositions, sizes)),
|
||||
glyph_map_new, myrects)
|
||||
|
||||
@staticmethod
|
||||
@functools.lru_cache(50)
|
||||
def _get_ps_font_and_encoding(texname):
|
||||
tex_font_map = dviread.PsfontsMap(dviread.find_tex_file('pdftex.map'))
|
||||
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))
|
||||
|
||||
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
|
||||
else:
|
||||
charmap_name = ""
|
||||
warnings.warn("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 = {}
|
||||
|
||||
return font, enc
|
||||
|
||||
|
||||
text_to_path = TextToPath()
|
||||
|
||||
|
||||
class TextPath(Path):
|
||||
"""
|
||||
Create a path from the text.
|
||||
"""
|
||||
|
||||
def __init__(self, xy, s, size=None, prop=None,
|
||||
_interpolation_steps=1, usetex=False,
|
||||
*kl, **kwargs):
|
||||
r"""
|
||||
Create a path from the text. Note that it simply is a path,
|
||||
not an artist. You need to use the `~.PathPatch` (or other artists)
|
||||
to draw this path onto the canvas.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
xy : tuple or array of two float values
|
||||
Position of the text. For no offset, use ``xy=(0, 0)``.
|
||||
|
||||
s : str
|
||||
The text to convert to a path.
|
||||
|
||||
size : float, optional
|
||||
Font size in points. Defaults to the size specified via the font
|
||||
properties *prop*.
|
||||
|
||||
prop : `matplotlib.font_manager.FontProperties`, optional
|
||||
Font property. If not provided, will use a default
|
||||
``FontProperties`` with parameters from the
|
||||
:ref:`rcParams <matplotlib-rcparams>`.
|
||||
|
||||
_interpolation_steps : integer, optional
|
||||
(Currently ignored)
|
||||
|
||||
usetex : bool, optional
|
||||
Whether to use tex rendering. Defaults to ``False``.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
The following creates a path from the string "ABC" with Helvetica
|
||||
font face; and another path from the latex fraction 1/2::
|
||||
|
||||
from matplotlib.textpath import TextPath
|
||||
from matplotlib.font_manager import FontProperties
|
||||
|
||||
fp = FontProperties(family="Helvetica", style="italic")
|
||||
path1 = TextPath((12,12), "ABC", size=12, prop=fp)
|
||||
path2 = TextPath((0,0), r"$\frac{1}{2}$", size=12, usetex=True)
|
||||
|
||||
Also see :doc:`/gallery/text_labels_and_annotations/demo_text_path`.
|
||||
"""
|
||||
|
||||
if prop is None:
|
||||
prop = FontProperties()
|
||||
|
||||
if size is None:
|
||||
size = prop.get_size_in_points()
|
||||
|
||||
self._xy = xy
|
||||
self.set_size(size)
|
||||
|
||||
self._cached_vertices = None
|
||||
|
||||
self._vertices, self._codes = self.text_get_vertices_codes(
|
||||
prop, s,
|
||||
usetex=usetex)
|
||||
|
||||
self._should_simplify = False
|
||||
self._simplify_threshold = rcParams['path.simplify_threshold']
|
||||
self._has_nonfinite = False
|
||||
self._interpolation_steps = _interpolation_steps
|
||||
|
||||
def set_size(self, size):
|
||||
"""
|
||||
set the size of the text
|
||||
"""
|
||||
self._size = size
|
||||
self._invalid = True
|
||||
|
||||
def get_size(self):
|
||||
"""
|
||||
get the size of the text
|
||||
"""
|
||||
return self._size
|
||||
|
||||
def _get_vertices(self):
|
||||
"""
|
||||
Return the cached path after updating it if necessary.
|
||||
"""
|
||||
self._revalidate_path()
|
||||
return self._cached_vertices
|
||||
|
||||
def _get_codes(self):
|
||||
"""
|
||||
Return the codes
|
||||
"""
|
||||
return self._codes
|
||||
|
||||
vertices = property(_get_vertices)
|
||||
codes = property(_get_codes)
|
||||
|
||||
def _revalidate_path(self):
|
||||
"""
|
||||
update the path if necessary.
|
||||
|
||||
The path for the text is initially create with the font size
|
||||
of FONT_SCALE, and this path is rescaled to other size when
|
||||
necessary.
|
||||
|
||||
"""
|
||||
if self._invalid or self._cached_vertices is None:
|
||||
tr = Affine2D().scale(
|
||||
self._size / text_to_path.FONT_SCALE,
|
||||
self._size / text_to_path.FONT_SCALE).translate(*self._xy)
|
||||
self._cached_vertices = tr.transform(self._vertices)
|
||||
self._invalid = False
|
||||
|
||||
def is_math_text(self, s):
|
||||
"""
|
||||
Returns True if the given string *s* contains any mathtext.
|
||||
"""
|
||||
# copied from Text.is_math_text -JJL
|
||||
|
||||
# Did we find an even number of non-escaped dollar signs?
|
||||
# If so, treat is as math text.
|
||||
dollar_count = s.count(r'$') - s.count(r'\$')
|
||||
even_dollars = (dollar_count > 0 and dollar_count % 2 == 0)
|
||||
|
||||
if rcParams['text.usetex']:
|
||||
return s, 'TeX'
|
||||
|
||||
if even_dollars:
|
||||
return s, True
|
||||
else:
|
||||
return s.replace(r'\$', '$'), False
|
||||
|
||||
def text_get_vertices_codes(self, prop, s, usetex):
|
||||
"""
|
||||
convert the string *s* to vertices and codes using the
|
||||
provided font property *prop*. Mostly copied from
|
||||
backend_svg.py.
|
||||
"""
|
||||
|
||||
if usetex:
|
||||
verts, codes = text_to_path.get_text_path(prop, s, usetex=True)
|
||||
else:
|
||||
clean_line, ismath = self.is_math_text(s)
|
||||
verts, codes = text_to_path.get_text_path(prop, clean_line,
|
||||
ismath=ismath)
|
||||
|
||||
return verts, codes
|
||||
Reference in New Issue
Block a user