buildframework/helium/external/python/lib/2.5/Sphinx-0.5.1-py2.5.egg/sphinx/ext/pngmath.py
changeset 179 d8ac696cc51f
parent 1 be27ed110b50
child 180 e02a83d4c571
child 592 3215c239276a
equal deleted inserted replaced
1:be27ed110b50 179:d8ac696cc51f
     1 # -*- coding: utf-8 -*-
       
     2 """
       
     3     sphinx.ext.pngmath
       
     4     ~~~~~~~~~~~~~~~~~~
       
     5 
       
     6     Render math in HTML via dvipng.
       
     7 
       
     8     :copyright: 2008 by Georg Brandl.
       
     9     :license: BSD.
       
    10 """
       
    11 
       
    12 import re
       
    13 import shutil
       
    14 import tempfile
       
    15 import posixpath
       
    16 from os import path, getcwd, chdir
       
    17 from subprocess import Popen, PIPE
       
    18 try:
       
    19     from hashlib import sha1 as sha
       
    20 except ImportError:
       
    21     from sha import sha
       
    22 
       
    23 from docutils import nodes
       
    24 
       
    25 from sphinx.util import ensuredir
       
    26 from sphinx.util.png import read_png_depth, write_png_depth
       
    27 from sphinx.application import SphinxError
       
    28 from sphinx.ext.mathbase import setup as mathbase_setup, wrap_displaymath
       
    29 
       
    30 class MathExtError(SphinxError):
       
    31     category = 'Math extension error'
       
    32 
       
    33 
       
    34 DOC_HEAD = r'''
       
    35 \documentclass[12pt]{article}
       
    36 \usepackage[utf8]{inputenc}
       
    37 \usepackage{amsmath}
       
    38 \usepackage{amsthm}
       
    39 \usepackage{amssymb}
       
    40 \usepackage{amsfonts}
       
    41 \usepackage{bm}
       
    42 \pagestyle{empty}
       
    43 '''
       
    44 
       
    45 DOC_BODY = r'''
       
    46 \begin{document}
       
    47 %s
       
    48 \end{document}
       
    49 '''
       
    50 
       
    51 DOC_BODY_PREVIEW = r'''
       
    52 \usepackage[active]{preview}
       
    53 \begin{document}
       
    54 \begin{preview}
       
    55 %s
       
    56 \end{preview}
       
    57 \end{document}
       
    58 '''
       
    59 
       
    60 depth_re = re.compile(r'\[\d+ depth=(-?\d+)\]')
       
    61 
       
    62 def render_math(self, math):
       
    63     """
       
    64     Render the LaTeX math expression *math* using latex and dvipng.
       
    65 
       
    66     Return the filename relative to the built document and the "depth",
       
    67     that is, the distance of image bottom and baseline in pixels, if the
       
    68     option to use preview_latex is switched on.
       
    69 
       
    70     Error handling may seem strange, but follows a pattern: if LaTeX or
       
    71     dvipng aren't available, only a warning is generated (since that enables
       
    72     people on machines without these programs to at least build the rest
       
    73     of the docs successfully).  If the programs are there, however, they
       
    74     may not fail since that indicates a problem in the math source.
       
    75     """
       
    76     use_preview = self.builder.config.pngmath_use_preview
       
    77 
       
    78     shasum = "%s.png" % sha(math.encode('utf-8')).hexdigest()
       
    79     relfn = posixpath.join(self.builder.imgpath, 'math', shasum)
       
    80     outfn = path.join(self.builder.outdir, '_images', 'math', shasum)
       
    81     if path.isfile(outfn):
       
    82         depth = read_png_depth(outfn)
       
    83         return relfn, depth
       
    84 
       
    85     latex = DOC_HEAD + self.builder.config.pngmath_latex_preamble
       
    86     latex += (use_preview and DOC_BODY_PREVIEW or DOC_BODY) % math
       
    87     if isinstance(latex, unicode):
       
    88         latex = latex.encode('utf-8')
       
    89 
       
    90     # use only one tempdir per build -- the use of a directory is cleaner
       
    91     # than using temporary files, since we can clean up everything at once
       
    92     # just removing the whole directory (see cleanup_tempdir)
       
    93     if not hasattr(self.builder, '_mathpng_tempdir'):
       
    94         tempdir = self.builder._mathpng_tempdir = tempfile.mkdtemp()
       
    95     else:
       
    96         tempdir = self.builder._mathpng_tempdir
       
    97 
       
    98     tf = open(path.join(tempdir, 'math.tex'), 'w')
       
    99     tf.write(latex)
       
   100     tf.close()
       
   101 
       
   102     # build latex command; old versions of latex don't have the
       
   103     # --output-directory option, so we have to manually chdir to the
       
   104     # temp dir to run it.
       
   105     ltx_args = [self.builder.config.pngmath_latex, '--interaction=nonstopmode']
       
   106     # add custom args from the config file
       
   107     ltx_args.extend(self.builder.config.pngmath_latex_args)
       
   108     ltx_args.append('math.tex')
       
   109 
       
   110     curdir = getcwd()
       
   111     chdir(tempdir)
       
   112 
       
   113     try:
       
   114         try:
       
   115             p = Popen(ltx_args, stdout=PIPE, stderr=PIPE)
       
   116         except OSError, err:
       
   117             if err.errno != 2:   # No such file or directory
       
   118                 raise
       
   119             if not hasattr(self.builder, '_mathpng_warned_latex'):
       
   120                 self.builder.warn('LaTeX command %r cannot be run (needed for math '
       
   121                                   'display), check the pngmath_latex setting' %
       
   122                                   self.builder.config.pngmath_latex)
       
   123                 self.builder._mathpng_warned_latex = True
       
   124             return relfn, None
       
   125     finally:
       
   126         chdir(curdir)
       
   127 
       
   128     stdout, stderr = p.communicate()
       
   129     if p.returncode != 0:
       
   130         raise MathExtError('latex exited with error:\n[stderr]\n%s\n[stdout]\n%s'
       
   131                            % (stderr, stdout))
       
   132 
       
   133     ensuredir(path.dirname(outfn))
       
   134     # use some standard dvipng arguments
       
   135     dvipng_args = [self.builder.config.pngmath_dvipng]
       
   136     dvipng_args += ['-o', outfn, '-T', 'tight', '-z9']
       
   137     # add custom ones from config value
       
   138     dvipng_args.extend(self.builder.config.pngmath_dvipng_args)
       
   139     if use_preview:
       
   140         dvipng_args.append('--depth')
       
   141     # last, the input file name
       
   142     dvipng_args.append(path.join(tempdir, 'math.dvi'))
       
   143     try:
       
   144         p = Popen(dvipng_args, stdout=PIPE, stderr=PIPE)
       
   145     except OSError, err:
       
   146         if err.errno != 2:   # No such file or directory
       
   147             raise
       
   148         if not hasattr(self.builder, '_mathpng_warned_dvipng'):
       
   149             self.builder.warn('dvipng command %r cannot be run (needed for math '
       
   150                               'display), check the pngmath_dvipng setting' %
       
   151                               self.builder.config.pngmath_dvipng)
       
   152             self.builder._mathpng_warned_dvipng = True
       
   153         return relfn, None
       
   154     stdout, stderr = p.communicate()
       
   155     if p.returncode != 0:
       
   156         raise MathExtError('dvipng exited with error:\n[stderr]\n%s\n[stdout]\n%s'
       
   157                            % (stderr, stdout))
       
   158     depth = None
       
   159     if use_preview:
       
   160         for line in stdout.splitlines():
       
   161             m = depth_re.match(line)
       
   162             if m:
       
   163                 depth = int(m.group(1))
       
   164                 write_png_depth(outfn, depth)
       
   165                 break
       
   166 
       
   167     return relfn, depth
       
   168 
       
   169 def cleanup_tempdir(app, exc):
       
   170     if exc:
       
   171         return
       
   172     if not hasattr(app.builder, '_mathpng_tempdir'):
       
   173         return
       
   174     try:
       
   175         shutil.rmtree(app.builder._mathpng_tempdir)
       
   176     except Exception:
       
   177         pass
       
   178 
       
   179 def html_visit_math(self, node):
       
   180     try:
       
   181         fname, depth = render_math(self, '$'+node['latex']+'$')
       
   182     except MathExtError, exc:
       
   183         sm = nodes.system_message(str(exc), type='WARNING', level=2,
       
   184                                   backrefs=[], source=node['latex'])
       
   185         sm.walkabout(self)
       
   186         self.builder.warn('display latex %r: ' % node['latex'] + str(exc))
       
   187         raise nodes.SkipNode
       
   188     self.body.append('<img class="math" src="%s" alt="%s" %s/>' %
       
   189                      (fname, self.encode(node['latex']).strip(),
       
   190                       depth and 'style="vertical-align: %dpx" ' % (-depth) or ''))
       
   191     raise nodes.SkipNode
       
   192 
       
   193 def html_visit_displaymath(self, node):
       
   194     if node['nowrap']:
       
   195         latex = node['latex']
       
   196     else:
       
   197         latex = wrap_displaymath(node['latex'], None)
       
   198     try:
       
   199         fname, depth = render_math(self, latex)
       
   200     except MathExtError, exc:
       
   201         sm = nodes.system_message(str(exc), type='WARNING', level=2,
       
   202                                   backrefs=[], source=node['latex'])
       
   203         sm.walkabout(self)
       
   204         self.builder.warn('inline latex %r: ' % node['latex'] + str(exc))
       
   205         raise nodes.SkipNode
       
   206     self.body.append(self.starttag(node, 'div', CLASS='math'))
       
   207     self.body.append('<p>')
       
   208     if node['number']:
       
   209         self.body.append('<span class="eqno">(%s)</span>' % node['number'])
       
   210     self.body.append('<img src="%s" alt="%s" />\n</div>' %
       
   211                      (fname, self.encode(node['latex']).strip()))
       
   212     self.body.append('</p>')
       
   213     raise nodes.SkipNode
       
   214 
       
   215 
       
   216 def setup(app):
       
   217     mathbase_setup(app, (html_visit_math, None), (html_visit_displaymath, None))
       
   218     app.add_config_value('pngmath_dvipng', 'dvipng', False)
       
   219     app.add_config_value('pngmath_latex', 'latex', False)
       
   220     app.add_config_value('pngmath_use_preview', False, False)
       
   221     app.add_config_value('pngmath_dvipng_args', ['-gamma 1.5', '-D 110'], False)
       
   222     app.add_config_value('pngmath_latex_args', [], False)
       
   223     app.add_config_value('pngmath_latex_preamble', '', False)
       
   224     app.connect('build-finished', cleanup_tempdir)