Back to index

moin  1.9.0~rc2
svg.py
Go to the documentation of this file.
00001 # -*- coding: utf-8 -*-
00002 """
00003     pygments.formatters.svg
00004     ~~~~~~~~~~~~~~~~~~~~~~~
00005 
00006     Formatter for SVG output.
00007 
00008     :copyright: Copyright 2006-2009 by the Pygments team, see AUTHORS.
00009     :license: BSD, see LICENSE for details.
00010 """
00011 
00012 from pygments.formatter import Formatter
00013 from pygments.util import get_bool_opt, get_int_opt
00014 
00015 __all__ = ['SvgFormatter']
00016 
00017 
00018 def escape_html(text):
00019     """Escape &, <, > as well as single and double quotes for HTML."""
00020     return text.replace('&', '&amp;').  \
00021                 replace('<', '&lt;').   \
00022                 replace('>', '&gt;').   \
00023                 replace('"', '&quot;'). \
00024                 replace("'", '&#39;')
00025 
00026 
00027 class2style = {}
00028 
00029 class SvgFormatter(Formatter):
00030     """
00031     Format tokens as an SVG graphics file.  This formatter is still experimental.
00032     Each line of code is a ``<text>`` element with explicit ``x`` and ``y``
00033     coordinates containing ``<tspan>`` elements with the individual token styles.
00034 
00035     By default, this formatter outputs a full SVG document including doctype
00036     declaration and the ``<svg>`` root element.
00037 
00038     *New in Pygments 0.9.*
00039 
00040     Additional options accepted:
00041 
00042     `nowrap`
00043         Don't wrap the SVG ``<text>`` elements in ``<svg><g>`` elements and
00044         don't add a XML declaration and a doctype.  If true, the `fontfamily`
00045         and `fontsize` options are ignored.  Defaults to ``False``.
00046 
00047     `fontfamily`
00048         The value to give the wrapping ``<g>`` element's ``font-family``
00049         attribute, defaults to ``"monospace"``.
00050 
00051     `fontsize`
00052         The value to give the wrapping ``<g>`` element's ``font-size``
00053         attribute, defaults to ``"14px"``.
00054 
00055     `xoffset`
00056         Starting offset in X direction, defaults to ``0``.
00057 
00058     `yoffset`
00059         Starting offset in Y direction, defaults to the font size if it is given
00060         in pixels, or ``20`` else.  (This is necessary since text coordinates
00061         refer to the text baseline, not the top edge.)
00062 
00063     `ystep`
00064         Offset to add to the Y coordinate for each subsequent line.  This should
00065         roughly be the text size plus 5.  It defaults to that value if the text
00066         size is given in pixels, or ``25`` else.
00067 
00068     `spacehack`
00069         Convert spaces in the source to ``&#160;``, which are non-breaking
00070         spaces.  SVG provides the ``xml:space`` attribute to control how
00071         whitespace inside tags is handled, in theory, the ``preserve`` value
00072         could be used to keep all whitespace as-is.  However, many current SVG
00073         viewers don't obey that rule, so this option is provided as a workaround
00074         and defaults to ``True``.
00075     """
00076     name = 'SVG'
00077     aliases = ['svg']
00078     filenames = ['*.svg']
00079 
00080     def __init__(self, **options):
00081         # XXX outencoding
00082         Formatter.__init__(self, **options)
00083         self.nowrap = get_bool_opt(options, 'nowrap', False)
00084         self.fontfamily = options.get('fontfamily', 'monospace')
00085         self.fontsize = options.get('fontsize', '14px')
00086         self.xoffset = get_int_opt(options, 'xoffset', 0)
00087         fs = self.fontsize.strip()
00088         if fs.endswith('px'): fs = fs[:-2].strip()
00089         try:
00090             int_fs = int(fs)
00091         except:
00092             int_fs = 20
00093         self.yoffset = get_int_opt(options, 'yoffset', int_fs)
00094         self.ystep = get_int_opt(options, 'ystep', int_fs + 5)
00095         self.spacehack = get_bool_opt(options, 'spacehack', True)
00096         self._stylecache = {}
00097 
00098     def format_unencoded(self, tokensource, outfile):
00099         """
00100         Format ``tokensource``, an iterable of ``(tokentype, tokenstring)``
00101         tuples and write it into ``outfile``.
00102 
00103         For our implementation we put all lines in their own 'line group'.
00104         """
00105         x = self.xoffset
00106         y = self.yoffset
00107         if not self.nowrap:
00108             if self.encoding:
00109                 outfile.write('<?xml version="1.0" encoding="%s"?>\n' %
00110                               self.encoding)
00111             else:
00112                 outfile.write('<?xml version="1.0"?>\n')
00113             outfile.write('<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" '
00114                           '"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/'
00115                           'svg10.dtd">\n')
00116             outfile.write('<svg xmlns="http://www.w3.org/2000/svg">\n')
00117             outfile.write('<g font-family="%s" font-size="%s">\n' %
00118                           (self.fontfamily, self.fontsize))
00119         outfile.write('<text x="%s" y="%s" xml:space="preserve">' % (x, y))
00120         for ttype, value in tokensource:
00121             style = self._get_style(ttype)
00122             tspan = style and '<tspan' + style + '>' or ''
00123             tspanend = tspan and '</tspan>' or ''
00124             value = escape_html(value)
00125             if self.spacehack:
00126                 value = value.expandtabs().replace(' ', '&#160;')
00127             parts = value.split('\n')
00128             for part in parts[:-1]:
00129                 outfile.write(tspan + part + tspanend)
00130                 y += self.ystep
00131                 outfile.write('</text>\n<text x="%s" y="%s" '
00132                               'xml:space="preserve">' % (x, y))
00133             outfile.write(tspan + parts[-1] + tspanend)
00134         outfile.write('</text>')
00135 
00136         if not self.nowrap:
00137             outfile.write('</g></svg>\n')
00138 
00139     def _get_style(self, tokentype):
00140         if tokentype in self._stylecache:
00141             return self._stylecache[tokentype]
00142         otokentype = tokentype
00143         while not self.style.styles_token(tokentype):
00144             tokentype = tokentype.parent
00145         value = self.style.style_for_token(tokentype)
00146         result = ''
00147         if value['color']:
00148             result = ' fill="#' + value['color'] + '"'
00149         if value['bold']:
00150             result += ' font-weight="bold"'
00151         if value['italic']:
00152             result += ' font-style="italic"'
00153         self._stylecache[otokentype] = result
00154         return result