diff --git a/lib/matplotlib/texmanager.py b/lib/matplotlib/texmanager.py index 08d09cd2b3cb..3623e4bdf77c 100644 --- a/lib/matplotlib/texmanager.py +++ b/lib/matplotlib/texmanager.py @@ -21,7 +21,8 @@ as follows:: texmanager = TexManager() - s = '\\TeX\\ is Number $\\displaystyle\\sum_{n=1}^\\infty\\frac{-e^{i\pi}}{2^n}$!' + s = ('\\TeX\\ is Number ' + '$\\displaystyle\\sum_{n=1}^\\infty\\frac{-e^{i\pi}}{2^n}$!') Z = self.texmanager.get_rgba(s, size=12, dpi=80, rgb=(1,0,0)) To enable tex rendering of all text in your matplotlib figure, set @@ -35,13 +36,17 @@ from __future__ import print_function -import copy, glob, os, shutil, sys, warnings +import copy +import glob +import os +import shutil +import sys from subprocess import Popen, PIPE, STDOUT try: from hashlib import md5 except ImportError: - from md5 import md5 #Deprecated in 2.5 + from md5 import md5 # Deprecated in 2.5 import distutils.version import numpy as np @@ -54,18 +59,21 @@ DEBUG = False -if sys.platform.startswith('win'): cmd_split = '&' -else: cmd_split = ';' +if sys.platform.startswith('win'): + cmd_split = '&' +else: + cmd_split = ';' + def dvipng_hack_alpha(): p = Popen('dvipng -version', shell=True, stdin=PIPE, stdout=PIPE, - stderr=STDOUT, close_fds=(sys.platform!='win32')) + stderr=STDOUT, close_fds=(sys.platform != 'win32')) stdin, stdout = p.stdin, p.stdout for line in stdout: if line.startswith(b'dvipng '): version = line.split()[-1] - mpl.verbose.report('Found dvipng version %s'% version, - 'helpful') + mpl.verbose.report('Found dvipng version %s' % version, + 'helpful') version = version.decode('ascii') version = distutils.version.LooseVersion(version) return version < distutils.version.LooseVersion('1.6') @@ -74,23 +82,25 @@ def dvipng_hack_alpha(): class TexManager: - """ Convert strings to dvi files using TeX, caching the results to a working dir """ oldpath = mpl.get_home() - if oldpath is None: oldpath = mpl.get_data_path() + if oldpath is None: + oldpath = mpl.get_data_path() oldcache = os.path.join(oldpath, '.tex.cache') configdir = mpl.get_configdir() texcache = os.path.join(configdir, 'tex.cache') if os.path.exists(oldcache): + # FIXME raise proper warning print("""\ WARNING: found a TeX cache dir in the deprecated location "%s". - Moving it to the new default location "%s"."""%(oldcache, texcache), file=sys.stderr) + Moving it to the new default location "%s".""" % (oldcache, texcache), + file=sys.stderr) shutil.move(oldcache, texcache) mkdirs(texcache) @@ -111,25 +121,26 @@ class TexManager: font_info = {'new century schoolbook': ('pnc', r'\renewcommand{\rmdefault}{pnc}'), - 'bookman': ('pbk', r'\renewcommand{\rmdefault}{pbk}'), - 'times': ('ptm', r'\usepackage{mathptmx}'), - 'palatino': ('ppl', r'\usepackage{mathpazo}'), - 'zapf chancery': ('pzc', r'\usepackage{chancery}'), - 'cursive': ('pzc', r'\usepackage{chancery}'), - 'charter': ('pch', r'\usepackage{charter}'), - 'serif': ('cmr', ''), - 'sans-serif': ('cmss', ''), - 'helvetica': ('phv', r'\usepackage{helvet}'), - 'avant garde': ('pag', r'\usepackage{avant}'), - 'courier': ('pcr', r'\usepackage{courier}'), - 'monospace': ('cmtt', ''), - 'computer modern roman': ('cmr', ''), - 'computer modern sans serif': ('cmss', ''), - 'computer modern typewriter': ('cmtt', '')} + 'bookman': ('pbk', r'\renewcommand{\rmdefault}{pbk}'), + 'times': ('ptm', r'\usepackage{mathptmx}'), + 'palatino': ('ppl', r'\usepackage{mathpazo}'), + 'zapf chancery': ('pzc', r'\usepackage{chancery}'), + 'cursive': ('pzc', r'\usepackage{chancery}'), + 'charter': ('pch', r'\usepackage{charter}'), + 'serif': ('cmr', ''), + 'sans-serif': ('cmss', ''), + 'helvetica': ('phv', r'\usepackage{helvet}'), + 'avant garde': ('pag', r'\usepackage{avant}'), + 'courier': ('pcr', r'\usepackage{courier}'), + 'monospace': ('cmtt', ''), + 'computer modern roman': ('cmr', ''), + 'computer modern sans serif': ('cmss', ''), + 'computer modern typewriter': ('cmtt', '')} _rc_cache = None - _rc_cache_keys = ('text.latex.preamble', )\ - + tuple(['font.'+n for n in ('family', ) + font_families]) + _rc_cache_keys = (('text.latex.preamble', ) + + tuple(['font.' + n for n in ('family', ) + + font_families])) def __init__(self): @@ -138,25 +149,30 @@ def __init__(self): if ff in self.font_families: self.font_family = ff else: - mpl.verbose.report('The %s font family is not compatible with LaTeX. serif will be used by default.' % ff, 'helpful') + mpl.verbose.report('The %s font family is not compatible with ' + 'LaTeX. serif will be used by default.' % ff, + 'helpful') self.font_family = 'serif' fontconfig = [self.font_family] - for font_family, font_family_attr in \ - [(ff, ff.replace('-', '_')) for ff in self.font_families]: - for font in rcParams['font.'+font_family]: + for font_family, font_family_attr in [(ff, ff.replace('-', '_')) + for ff in self.font_families]: + for font in rcParams['font.' + font_family]: if font.lower() in self.font_info: - found_font = self.font_info[font.lower()] setattr(self, font_family_attr, self.font_info[font.lower()]) if DEBUG: - print('family: %s, font: %s, info: %s'%(font_family, - font, self.font_info[font.lower()])) + print('family: %s, font: %s, info: %s' % + (font_family, font, + self.font_info[font.lower()])) break else: - if DEBUG: print('$s font is not compatible with usetex') + if DEBUG: + print('$s font is not compatible with usetex') else: - mpl.verbose.report('No LaTeX-compatible font found for the %s font family in rcParams. Using default.' % ff, 'helpful') + mpl.verbose.report('No LaTeX-compatible font found for the ' + '%s font family in rcParams. Using ' + 'default.' % ff, 'helpful') setattr(self, font_family_attr, self.font_info[font_family]) fontconfig.append(getattr(self, font_family_attr)[0]) self._fontconfig = ''.join(fontconfig) @@ -164,7 +180,8 @@ def __init__(self): # The following packages and commands need to be included in the latex # file's preamble: cmd = [self.serif[1], self.sans_serif[1], self.monospace[1]] - if self.font_family == 'cursive': cmd.append(self.cursive[1]) + if self.font_family == 'cursive': + cmd.append(self.cursive[1]) while r'\usepackage{type1cm}' in cmd: cmd.remove(r'\usepackage{type1cm}') cmd = '\n'.join(cmd) @@ -175,7 +192,7 @@ def get_basefile(self, tex, fontsize, dpi=None): """ returns a filename based on a hash of the string, fontsize, and dpi """ - s = ''.join([tex, self.get_font_config(), '%f'%fontsize, + s = ''.join([tex, self.get_font_config(), '%f' % fontsize, self.get_custom_preamble(), str(dpi or '')]) # make sure hash is consistent for all strings, regardless of encoding: bytes = unicode(s).encode('utf-8') @@ -184,20 +201,23 @@ def get_basefile(self, tex, fontsize, dpi=None): def get_font_config(self): """Reinitializes self if relevant rcParams on have changed.""" if self._rc_cache is None: - self._rc_cache = dict([(k,None) for k in self._rc_cache_keys]) - changed = [par for par in self._rc_cache_keys if rcParams[par] != \ - self._rc_cache[par]] + self._rc_cache = dict([(k, None) for k in self._rc_cache_keys]) + changed = [par for par in self._rc_cache_keys + if rcParams[par] != self._rc_cache[par]] if changed: - if DEBUG: print('DEBUG following keys changed:', changed) + if DEBUG: + print('DEBUG following keys changed:', changed) for k in changed: if DEBUG: - print('DEBUG %-20s: %-10s -> %-10s' % \ - (k, self._rc_cache[k], rcParams[k])) + print('DEBUG %-20s: %-10s -> %-10s' % + (k, self._rc_cache[k], rcParams[k])) # deepcopy may not be necessary, but feels more future-proof self._rc_cache[k] = copy.deepcopy(rcParams[k]) - if DEBUG: print('DEBUG RE-INIT\nold fontconfig:', self._fontconfig) + if DEBUG: + print('DEBUG RE-INIT\nold fontconfig:', self._fontconfig) self.__init__() - if DEBUG: print('DEBUG fontconfig:', self._fontconfig) + if DEBUG: + print('DEBUG fontconfig:', self._fontconfig) return self._fontconfig def get_font_preamble(self): @@ -216,7 +236,7 @@ def _get_shell_cmd(self, *args): multiple drives. get_shell_cmd deals with this issue. """ if sys.platform == 'win32': - command = ['%s'% os.path.splitdrive(self.texcache)[0]] + command = ['%s' % os.path.splitdrive(self.texcache)[0]] else: command = [] command.extend(args) @@ -229,11 +249,11 @@ def make_tex(self, tex, fontsize): returns the file name """ basefile = self.get_basefile(tex, fontsize) - texfile = '%s.tex'%basefile + texfile = '%s.tex' % basefile custom_preamble = self.get_custom_preamble() - fontcmd = {'sans-serif' : r'{\sffamily %s}', - 'monospace' : r'{\ttfamily %s}'}.get(self.font_family, - r'{\rmfamily %s}') + fontcmd = {'sans-serif': r'{\sffamily %s}', + 'monospace': r'{\ttfamily %s}'}.get(self.font_family, + r'{\rmfamily %s}') tex = fontcmd % tex if rcParams['text.latex.unicode']: @@ -246,13 +266,13 @@ def make_tex(self, tex, fontsize): %s %s %s -\usepackage[papersize={72in,72in}, body={70in,70in}, margin={1in,1in}]{geometry} +\usepackage[papersize={72in,72in},body={70in,70in},margin={1in,1in}]{geometry} \pagestyle{empty} \begin{document} \fontsize{%f}{%f}%s \end{document} """ % (self._font_preamble, unicode_preamble, custom_preamble, - fontsize, fontsize*1.25, tex) + fontsize, fontsize * 1.25, tex) with open(texfile, 'wb') as fh: if rcParams['text.latex.unicode']: fh.write(s.encode('utf8')) @@ -260,15 +280,16 @@ def make_tex(self, tex, fontsize): try: fh.write(s.encode('ascii')) except UnicodeEncodeError as err: - mpl.verbose.report("You are using unicode and latex, but have " - "not enabled the matplotlib 'text.latex.unicode' " - "rcParam.", 'helpful') + mpl.verbose.report("You are using unicode and latex, but " + "have not enabled the matplotlib " + "'text.latex.unicode' rcParam.", + 'helpful') raise return texfile - - _re_vbox = re.compile(r"MatplotlibBox:\(([\d.]+)pt\+([\d.]+)pt\)x([\d.]+)pt") + _re_vbox = re.compile( + r"MatplotlibBox:\(([\d.]+)pt\+([\d.]+)pt\)x([\d.]+)pt") def make_tex_preview(self, tex, fontsize): """ @@ -279,11 +300,11 @@ def make_tex_preview(self, tex, fontsize): returns the file name """ basefile = self.get_basefile(tex, fontsize) - texfile = '%s.tex'%basefile + texfile = '%s.tex' % basefile custom_preamble = self.get_custom_preamble() - fontcmd = {'sans-serif' : r'{\sffamily %s}', - 'monospace' : r'{\ttfamily %s}'}.get(self.font_family, - r'{\rmfamily %s}') + fontcmd = {'sans-serif': r'{\sffamily %s}', + 'monospace': r'{\ttfamily %s}'}.get(self.font_family, + r'{\rmfamily %s}') tex = fontcmd % tex if rcParams['text.latex.unicode']: @@ -292,18 +313,15 @@ def make_tex_preview(self, tex, fontsize): else: unicode_preamble = '' - - # newbox, setbox, immediate, etc. are used to find the box # extent of the rendered text. - s = r"""\documentclass{article} %s %s %s \usepackage[active,showbox,tightpage]{preview} -\usepackage[papersize={72in,72in}, body={70in,70in}, margin={1in,1in}]{geometry} +\usepackage[papersize={72in,72in},body={70in,70in},margin={1in,1in}]{geometry} %% we override the default showbox as it is treated as an error and makes %% the exit status not zero @@ -315,7 +333,7 @@ def make_tex_preview(self, tex, fontsize): \end{preview} \end{document} """ % (self._font_preamble, unicode_preamble, custom_preamble, - fontsize, fontsize*1.25, tex) + fontsize, fontsize * 1.25, tex) with open(texfile, 'wb') as fh: if rcParams['text.latex.unicode']: fh.write(s.encode('utf8')) @@ -323,14 +341,14 @@ def make_tex_preview(self, tex, fontsize): try: fh.write(s.encode('ascii')) except UnicodeEncodeError as err: - mpl.verbose.report("You are using unicode and latex, but have " - "not enabled the matplotlib 'text.latex.unicode' " - "rcParam.", 'helpful') + mpl.verbose.report("You are using unicode and latex, but " + "have not enabled the matplotlib " + "'text.latex.unicode' rcParam.", + 'helpful') raise return texfile - def make_dvi(self, tex, fontsize): """ generates a dvi file containing latex's layout of tex string @@ -338,19 +356,19 @@ def make_dvi(self, tex, fontsize): returns the file name """ - if rcParams['text.latex.preview']: return self.make_dvi_preview(tex, fontsize) basefile = self.get_basefile(tex, fontsize) - dvifile = '%s.dvi'% basefile + dvifile = '%s.dvi' % basefile if DEBUG or not os.path.exists(dvifile): texfile = self.make_tex(tex, fontsize) - outfile = basefile+'.output' - command = self._get_shell_cmd('cd "%s"'% self.texcache, - 'latex -interaction=nonstopmode %s > "%s"'\ - %(os.path.split(texfile)[-1], outfile)) + outfile = basefile + '.output' + command = self._get_shell_cmd( + 'cd "%s"' % self.texcache, + 'latex -interaction=nonstopmode %s > "%s"' % + (os.path.split(texfile)[-1], outfile)) mpl.verbose.report(command, 'debug') exit_status = os.system(command) try: @@ -364,19 +382,25 @@ def make_dvi(self, tex, fontsize): except OSError: exists = False if exit_status or not exists: - raise RuntimeError(('LaTeX was not able to process the following \ -string:\n%s\nHere is the full report generated by LaTeX: \n\n'% repr(tex)) + report) - else: mpl.verbose.report(report, 'debug') - for fname in glob.glob(basefile+'*'): - if fname.endswith('dvi'): pass - elif fname.endswith('tex'): pass + raise RuntimeError( + ('LaTeX was not able to process the following ' + 'string:\n%s\nHere is the full report generated by ' + 'LaTeX: \n\n' % repr(tex)) + report) + else: + mpl.verbose.report(report, 'debug') + for fname in glob.glob(basefile + '*'): + if fname.endswith('dvi'): + pass + elif fname.endswith('tex'): + pass else: - try: os.remove(fname) - except OSError: pass + try: + os.remove(fname) + except OSError: + pass return dvifile - def make_dvi_preview(self, tex, fontsize): """ generates a dvi file containing latex's layout of tex @@ -386,16 +410,17 @@ def make_dvi_preview(self, tex, fontsize): returns the file name """ basefile = self.get_basefile(tex, fontsize) - dvifile = '%s.dvi'% basefile - baselinefile = '%s.baseline'% basefile + dvifile = '%s.dvi' % basefile + baselinefile = '%s.baseline' % basefile - if DEBUG or not os.path.exists(dvifile) or \ - not os.path.exists(baselinefile): + if (DEBUG or not os.path.exists(dvifile) or + not os.path.exists(baselinefile)): texfile = self.make_tex_preview(tex, fontsize) - outfile = basefile+'.output' - command = self._get_shell_cmd('cd "%s"'% self.texcache, - 'latex -interaction=nonstopmode %s > "%s"'\ - %(os.path.split(texfile)[-1], outfile)) + outfile = basefile + '.output' + command = self._get_shell_cmd( + 'cd "%s"' % self.texcache, + 'latex -interaction=nonstopmode %s > "%s"' % + (os.path.split(texfile)[-1], outfile)) mpl.verbose.report(command, 'debug') exit_status = os.system(command) try: @@ -405,23 +430,31 @@ def make_dvi_preview(self, tex, fontsize): except IOError: report = 'No latex error report available.' if exit_status: - raise RuntimeError(('LaTeX was not able to process the following \ -string:\n%s\nHere is the full report generated by LaTeX: \n\n'% repr(tex)) + report) - else: mpl.verbose.report(report, 'debug') + raise RuntimeError( + ('LaTeX was not able to process the following ' + 'string:\n%s\nHere is the full report generated by ' + 'LaTeX: \n\n' % repr(tex)) + report) + else: + mpl.verbose.report(report, 'debug') # find the box extent information in the latex output # file and store them in ".baseline" file m = TexManager._re_vbox.search(report) - with open(basefile+'.baseline',"w") as fh: + with open(basefile + '.baseline', "w") as fh: fh.write(" ".join(m.groups())) - for fname in glob.glob(basefile+'*'): - if fname.endswith('dvi'): pass - elif fname.endswith('tex'): pass - elif fname.endswith('baseline'): pass + for fname in glob.glob(basefile + '*'): + if fname.endswith('dvi'): + pass + elif fname.endswith('tex'): + pass + elif fname.endswith('baseline'): + pass else: - try: os.remove(fname) - except OSError: pass + try: + os.remove(fname) + except OSError: + pass return dvifile @@ -432,16 +465,17 @@ def make_png(self, tex, fontsize, dpi): returns the filename """ basefile = self.get_basefile(tex, fontsize, dpi) - pngfile = '%s.png'% basefile + pngfile = '%s.png' % basefile # see get_rgba for a discussion of the background if DEBUG or not os.path.exists(pngfile): dvifile = self.make_dvi(tex, fontsize) - outfile = basefile+'.output' - command = self._get_shell_cmd('cd "%s"' % self.texcache, - 'dvipng -bg Transparent -D %s -T tight -o \ - "%s" "%s" > "%s"'%(dpi, os.path.split(pngfile)[-1], - os.path.split(dvifile)[-1], outfile)) + outfile = basefile + '.output' + command = self._get_shell_cmd( + 'cd "%s"' % self.texcache, + 'dvipng -bg Transparent -D %s -T tight -o "%s" "%s" > "%s"' % + (dpi, os.path.split(pngfile)[-1], + os.path.split(dvifile)[-1], outfile)) mpl.verbose.report(command, 'debug') exit_status = os.system(command) try: @@ -450,12 +484,16 @@ def make_png(self, tex, fontsize, dpi): except IOError: report = 'No dvipng error report available.' if exit_status: - raise RuntimeError('dvipng was not able to \ -process the following file:\n%s\nHere is the full report generated by dvipng: \ -\n\n'% dvifile + report) - else: mpl.verbose.report(report, 'debug') - try: os.remove(outfile) - except OSError: pass + raise RuntimeError( + 'dvipng was not able to process the following ' + 'file:\n%s\nHere is the full report generated by ' + 'dvipng: \n\n' % dvifile + report) + else: + mpl.verbose.report(report, 'debug') + try: + os.remove(outfile) + except OSError: + pass return pngfile @@ -466,22 +504,24 @@ def make_ps(self, tex, fontsize): returns the file name """ basefile = self.get_basefile(tex, fontsize) - psfile = '%s.epsf'% basefile + psfile = '%s.epsf' % basefile if DEBUG or not os.path.exists(psfile): dvifile = self.make_dvi(tex, fontsize) - outfile = basefile+'.output' - command = self._get_shell_cmd('cd "%s"'% self.texcache, - 'dvips -q -E -o "%s" "%s" > "%s"'\ - %(os.path.split(psfile)[-1], - os.path.split(dvifile)[-1], outfile)) + outfile = basefile + '.output' + command = self._get_shell_cmd( + 'cd "%s"' % self.texcache, + 'dvips -q -E -o "%s" "%s" > "%s"' % + (os.path.split(psfile)[-1], + os.path.split(dvifile)[-1], outfile)) mpl.verbose.report(command, 'debug') exit_status = os.system(command) with open(outfile) as fh: if exit_status: - raise RuntimeError('dvipng was not able to \ - process the flowing file:\n%s\nHere is the full report generated by dvipng: \ - \n\n'% dvifile + fh.read()) + raise RuntimeError( + 'dvipng was not able to process the flowing ' + 'file:\n%s\nHere is the full report generated by ' + 'dvipng: \n\n' % dvifile + fh.read()) else: mpl.verbose.report(fh.read(), 'debug') os.remove(outfile) @@ -498,7 +538,7 @@ def get_ps_bbox(self, tex, fontsize): for line in ps: if line.startswith('%%BoundingBox:'): return [int(val) for val in line.split()[1:]] - raise RuntimeError('Could not parse %s'%psfile) + raise RuntimeError('Could not parse %s' % psfile) def get_grey(self, tex, fontsize=None, dpi=None): """returns the alpha channel""" @@ -516,7 +556,6 @@ def get_grey(self, tex, fontsize=None, dpi=None): TexManager._dvipng_hack_alpha = dvipng_hack_alpha() hack = TexManager._dvipng_hack_alpha - if hack: # hack the alpha channel # dvipng assumed a constant background, whereas we want to @@ -540,21 +579,22 @@ def get_grey(self, tex, fontsize=None, dpi=None): # Since the foreground is black (0) and the background is # white (1) this reduces to red = 1-alpha or alpha = 1-red #alpha = npy.sqrt(1-X[:,:,0]) # should this be sqrt here? - alpha = 1-X[:,:,0] + alpha = 1 - X[:, :, 0] else: - alpha = X[:,:,-1] + alpha = X[:, :, -1] self.grey_arrayd[key] = alpha return alpha - - def get_rgba(self, tex, fontsize=None, dpi=None, rgb=(0,0,0)): + def get_rgba(self, tex, fontsize=None, dpi=None, rgb=(0, 0, 0)): """ Returns latex's rendering of the tex string as an rgba array """ - if not fontsize: fontsize = rcParams['font.size'] - if not dpi: dpi = rcParams['savefig.dpi'] - r,g,b = rgb + if not fontsize: + fontsize = rcParams['font.size'] + if not dpi: + dpi = rcParams['savefig.dpi'] + r, g, b = rgb key = tex, self.get_font_config(), fontsize, dpi, tuple(rgb) Z = self.rgba_arrayd.get(key) @@ -563,15 +603,14 @@ def get_rgba(self, tex, fontsize=None, dpi=None, rgb=(0,0,0)): Z = np.zeros((alpha.shape[0], alpha.shape[1], 4), np.float) - Z[:,:,0] = r - Z[:,:,1] = g - Z[:,:,2] = b - Z[:,:,3] = alpha + Z[:, :, 0] = r + Z[:, :, 1] = g + Z[:, :, 2] = b + Z[:, :, 3] = alpha self.rgba_arrayd[key] = Z return Z - def get_text_width_height_descent(self, tex, fontsize, renderer=None): """ return width, heigth and descent of the text. @@ -587,24 +626,23 @@ def get_text_width_height_descent(self, tex, fontsize, renderer=None): if rcParams['text.latex.preview']: # use preview.sty basefile = self.get_basefile(tex, fontsize) - baselinefile = '%s.baseline'% basefile - + baselinefile = '%s.baseline' % basefile if DEBUG or not os.path.exists(baselinefile): dvifile = self.make_dvi_preview(tex, fontsize) with open(baselinefile) as fh: l = fh.read().split() - height, depth, width = [float(l1)*dpi_fraction for l1 in l] - return width, height+depth, depth + height, depth, width = [float(l1) * dpi_fraction for l1 in l] + return width, height + depth, depth else: # use dviread. It sometimes returns a wrong descent. dvifile = self.make_dvi(tex, fontsize) - dvi = dviread.Dvi(dvifile, 72*dpi_fraction) + dvi = dviread.Dvi(dvifile, 72 * dpi_fraction) try: page = next(iter(dvi)) finally: dvi.close() # A total height (including the descent) needs to be returned. - return page.width, page.height+page.descent, page.descent + return page.width, page.height + page.descent, page.descent