Server : Apache System : Linux indy02.toastserver.com 3.10.0-962.3.2.lve1.5.85.el7.x86_64 #1 SMP Thu Apr 18 15:18:36 UTC 2024 x86_64 User : palandch ( 1163) PHP Version : 7.1.33 Disable Function : NONE Directory : /opt/alt/python27/lib64/python2.7/site-packages/matplotlib/ |
""" This module supports embedded TeX expressions in matplotlib via dvipng and dvips for the raster and postscript backends. The tex and dvipng/dvips information is cached in ~/.matplotlib/tex.cache for reuse between sessions Requirements: * latex * \*Agg backends: dvipng * PS backend: latex w/ psfrag, dvips, and Ghostscript 8.51 (older versions do not work properly) Backends: * \*Agg * PS * PDF For raster output, you can get RGBA numpy arrays from TeX expressions as follows:: texmanager = TexManager() 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 text.usetex in your matplotlibrc file (http://matplotlib.sf.net/matplotlibrc) or include these two lines in your script:: from matplotlib import rc rc('text', usetex=True) """ import copy, glob, os, shutil, sys, warnings from subprocess import Popen, PIPE, STDOUT try: from hashlib import md5 except ImportError: from md5 import md5 #Deprecated in 2.5 import distutils.version import numpy as np import matplotlib as mpl from matplotlib import rcParams from matplotlib._png import read_png import matplotlib.dviread as dviread import re DEBUG = False 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')) stdin, stdout = p.stdin, p.stdout for line in stdout: if line.startswith('dvipng '): version = line.split()[-1] mpl.verbose.report('Found dvipng version %s'% version, 'helpful') version = distutils.version.LooseVersion(version) return version < distutils.version.LooseVersion('1.6') mpl.verbose.report('No dvipng was found', 'helpful') return False 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() oldcache = os.path.join(oldpath, '.tex.cache') configdir = mpl.get_configdir() texcache = os.path.join(configdir, 'tex.cache') if os.path.exists(oldcache): print >> sys.stderr, """\ WARNING: found a TeX cache dir in the deprecated location "%s". Moving it to the new default location "%s"."""%(oldcache, texcache) shutil.move(oldcache, texcache) if not os.path.exists(texcache): os.mkdir(texcache) _dvipng_hack_alpha = None #_dvipng_hack_alpha = dvipng_hack_alpha() # mappable cache of rgba_arrayd = {} grey_arrayd = {} postscriptd = {} pscnt = 0 serif = ('cmr', '') sans_serif = ('cmss', '') monospace = ('cmtt', '') cursive = ('pzc', r'\usepackage{chancery}') font_family = 'serif' font_families = ('serif', 'sans-serif', 'cursive', 'monospace') 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', '')} _rc_cache = None _rc_cache_keys = ('text.latex.preamble', )\ + tuple(['font.'+n for n in ('family', ) + font_families]) def __init__(self): if not os.path.isdir(self.texcache): os.mkdir(self.texcache) ff = rcParams['font.family'].lower() 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') 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]: 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()]) break else: 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') setattr(self, font_family_attr, self.font_info[font_family]) fontconfig.append(getattr(self, font_family_attr)[0]) self._fontconfig = ''.join(fontconfig) # 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]) while r'\usepackage{type1cm}' in cmd: cmd.remove(r'\usepackage{type1cm}') cmd = '\n'.join(cmd) self._font_preamble = '\n'.join([r'\usepackage{type1cm}', cmd, r'\usepackage{textcomp}']) 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, self.get_custom_preamble(), str(dpi or '')]) # make sure hash is consistent for all strings, regardless of encoding: bytes = unicode(s).encode('utf-8') return os.path.join(self.texcache, md5(bytes).hexdigest()) 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]] if 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]) # 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 self.__init__() if DEBUG: print 'DEBUG fontconfig:', self._fontconfig return self._fontconfig def get_font_preamble(self): """ returns a string containing font configuration for the tex preamble """ return self._font_preamble def get_custom_preamble(self): """returns a string containing user additions to the tex preamble""" return '\n'.join(rcParams['text.latex.preamble']) def _get_shell_cmd(self, *args): """ On windows, changing directories can be complicated by the presence of multiple drives. get_shell_cmd deals with this issue. """ if sys.platform == 'win32': command = ['%s'% os.path.splitdrive(self.texcache)[0]] else: command = [] command.extend(args) return ' && '.join(command) def make_tex(self, tex, fontsize): """ Generate a tex file to render the tex string at a specific font size returns the file name """ basefile = self.get_basefile(tex, fontsize) texfile = '%s.tex'%basefile fh = file(texfile, 'w') custom_preamble = self.get_custom_preamble() 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']: unicode_preamble = """\usepackage{ucs} \usepackage[utf8x]{inputenc}""" else: unicode_preamble = '' s = r"""\documentclass{article} %s %s %s \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) if rcParams['text.latex.unicode']: fh.write(s.encode('utf8')) else: try: fh.write(s) except UnicodeEncodeError, err: mpl.verbose.report("You are using unicode and latex, but have " "not enabled the matplotlib 'text.latex.unicode' " "rcParam.", 'helpful') raise fh.close() return texfile _re_vbox = re.compile(r"MatplotlibBox:\(([\d.]+)pt\+([\d.]+)pt\)x([\d.]+)pt") def make_tex_preview(self, tex, fontsize): """ Generate a tex file to render the tex string at a specific font size. It uses the preview.sty to determin the dimension (width, height, descent) of the output. returns the file name """ basefile = self.get_basefile(tex, fontsize) texfile = '%s.tex'%basefile fh = file(texfile, 'w') custom_preamble = self.get_custom_preamble() 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']: unicode_preamble = """\usepackage{ucs} \usepackage[utf8x]{inputenc}""" 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} %% we override the default showbox as it is treated as an error and makes %% the exit status not zero \def\showbox#1{\immediate\write16{MatplotlibBox:(\the\ht#1+\the\dp#1)x\the\wd#1}} \begin{document} \begin{preview} {\fontsize{%f}{%f}%s} \end{preview} \end{document} """ % (self._font_preamble, unicode_preamble, custom_preamble, fontsize, fontsize*1.25, tex) if rcParams['text.latex.unicode']: fh.write(s.encode('utf8')) else: try: fh.write(s) except UnicodeEncodeError, err: mpl.verbose.report("You are using unicode and latex, but have " "not enabled the matplotlib 'text.latex.unicode' " "rcParam.", 'helpful') raise fh.close() return texfile def make_dvi(self, tex, fontsize): """ generates a dvi file containing latex's layout of tex string 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 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)) mpl.verbose.report(command, 'debug') exit_status = os.system(command) try: fh = file(outfile) report = fh.read() fh.close() except IOError: report = 'No latex error report available.' try: os.stat(dvifile) exists = True 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 else: 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 string. It calls make_tex_preview() method and store the size information (width, height, descent) in a separte file. returns the file name """ basefile = self.get_basefile(tex, fontsize) dvifile = '%s.dvi'% basefile baselinefile = '%s.baseline'% basefile 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)) mpl.verbose.report(command, 'debug') exit_status = os.system(command) try: fh = file(outfile) report = fh.read() fh.close() 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') # find the box extent information in the latex output # file and store them in ".baseline" file m = TexManager._re_vbox.search(report) open(basefile+'.baseline',"w").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 else: try: os.remove(fname) except OSError: pass return dvifile def make_png(self, tex, fontsize, dpi): """ generates a png file containing latex's rendering of tex string returns the filename """ basefile = self.get_basefile(tex, fontsize, dpi) 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)) mpl.verbose.report(command, 'debug') exit_status = os.system(command) try: fh = file(outfile) report = fh.read() fh.close() 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 return pngfile def make_ps(self, tex, fontsize): """ generates a postscript file containing latex's rendering of tex string returns the file name """ basefile = self.get_basefile(tex, fontsize) 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)) mpl.verbose.report(command, 'debug') exit_status = os.system(command) fh = file(outfile) 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()) else: mpl.verbose.report(fh.read(), 'debug') fh.close() os.remove(outfile) return psfile def get_ps_bbox(self, tex, fontsize): """ returns a list containing the postscript bounding box for latex's rendering of the tex string """ psfile = self.make_ps(tex, fontsize) ps = file(psfile) for line in ps: if line.startswith('%%BoundingBox:'): return [int(val) for val in line.split()[1:]] raise RuntimeError('Could not parse %s'%psfile) def get_grey(self, tex, fontsize=None, dpi=None): """returns the alpha channel""" key = tex, self.get_font_config(), fontsize, dpi alpha = self.grey_arrayd.get(key) if alpha is None: pngfile = self.make_png(tex, fontsize, dpi) X = read_png(os.path.join(self.texcache, pngfile)) if rcParams['text.dvipnghack'] is not None: hack = rcParams['text.dvipnghack'] else: if TexManager._dvipng_hack_alpha is 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 # overlay these rasters with antialiasing over arbitrary # backgrounds that may have other figure elements under them. # When you set dvipng -bg Transparent, it actually makes the # alpha channel 1 and does the background compositing and # antialiasing itself and puts the blended data in the rgb # channels. So what we do is extract the alpha information # from the red channel, which is a blend of the default dvipng # background (white) and foreground (black). So the amount of # red (or green or blue for that matter since white and black # blend to a grayscale) is the alpha intensity. Once we # extract the correct alpha information, we assign it to the # alpha channel properly and let the users pick their rgb. In # this way, we can overlay tex strings on arbitrary # backgrounds with antialiasing # # red = alpha*red_foreground + (1-alpha)*red_background # # 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] else: alpha = X[:,:,-1] self.grey_arrayd[key] = alpha return alpha 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 key = tex, self.get_font_config(), fontsize, dpi, tuple(rgb) Z = self.rgba_arrayd.get(key) if Z is None: alpha = self.get_grey(tex, fontsize, dpi) Z = np.zeros((alpha.shape[0], alpha.shape[1], 4), np.float) 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. """ if tex.strip() == '': return 0, 0, 0 if renderer: dpi_fraction = renderer.points_to_pixels(1.) else: dpi_fraction = 1. if rcParams['text.latex.preview']: # use preview.sty basefile = self.get_basefile(tex, fontsize) baselinefile = '%s.baseline'% basefile if DEBUG or not os.path.exists(baselinefile): dvifile = self.make_dvi_preview(tex, fontsize) l = open(baselinefile).read().split() 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) page = iter(dvi).next() dvi.close() # A total height (including the descent) needs to be returned. return page.width, page.height+page.descent, page.descent