1
# -*- coding: utf-8 -*-
3
pygments.formatters.svg
4
~~~~~~~~~~~~~~~~~~~~~~~
6
Formatter for SVG output.
8
:copyright: Copyright 2006-2010 by the Pygments team, see AUTHORS.
9
:license: BSD, see LICENSE for details.
12
from pygments.formatter import Formatter
13
from pygments.util import get_bool_opt, get_int_opt
15
__all__ = ['SvgFormatter']
18
def escape_html(text):
19
"""Escape &, <, > as well as single and double quotes for HTML."""
20
return text.replace('&', '&'). \
21
replace('<', '<'). \
22
replace('>', '>'). \
23
replace('"', '"'). \
29
class SvgFormatter(Formatter):
31
Format tokens as an SVG graphics file. This formatter is still experimental.
32
Each line of code is a ``<text>`` element with explicit ``x`` and ``y``
33
coordinates containing ``<tspan>`` elements with the individual token styles.
35
By default, this formatter outputs a full SVG document including doctype
36
declaration and the ``<svg>`` root element.
38
*New in Pygments 0.9.*
40
Additional options accepted:
43
Don't wrap the SVG ``<text>`` elements in ``<svg><g>`` elements and
44
don't add a XML declaration and a doctype. If true, the `fontfamily`
45
and `fontsize` options are ignored. Defaults to ``False``.
48
The value to give the wrapping ``<g>`` element's ``font-family``
49
attribute, defaults to ``"monospace"``.
52
The value to give the wrapping ``<g>`` element's ``font-size``
53
attribute, defaults to ``"14px"``.
56
Starting offset in X direction, defaults to ``0``.
59
Starting offset in Y direction, defaults to the font size if it is given
60
in pixels, or ``20`` else. (This is necessary since text coordinates
61
refer to the text baseline, not the top edge.)
64
Offset to add to the Y coordinate for each subsequent line. This should
65
roughly be the text size plus 5. It defaults to that value if the text
66
size is given in pixels, or ``25`` else.
69
Convert spaces in the source to `` ``, which are non-breaking
70
spaces. SVG provides the ``xml:space`` attribute to control how
71
whitespace inside tags is handled, in theory, the ``preserve`` value
72
could be used to keep all whitespace as-is. However, many current SVG
73
viewers don't obey that rule, so this option is provided as a workaround
74
and defaults to ``True``.
80
def __init__(self, **options):
82
Formatter.__init__(self, **options)
83
self.nowrap = get_bool_opt(options, 'nowrap', False)
84
self.fontfamily = options.get('fontfamily', 'monospace')
85
self.fontsize = options.get('fontsize', '14px')
86
self.xoffset = get_int_opt(options, 'xoffset', 0)
87
fs = self.fontsize.strip()
88
if fs.endswith('px'): fs = fs[:-2].strip()
93
self.yoffset = get_int_opt(options, 'yoffset', int_fs)
94
self.ystep = get_int_opt(options, 'ystep', int_fs + 5)
95
self.spacehack = get_bool_opt(options, 'spacehack', True)
98
def format_unencoded(self, tokensource, outfile):
100
Format ``tokensource``, an iterable of ``(tokentype, tokenstring)``
101
tuples and write it into ``outfile``.
103
For our implementation we put all lines in their own 'line group'.
109
outfile.write('<?xml version="1.0" encoding="%s"?>\n' %
112
outfile.write('<?xml version="1.0"?>\n')
113
outfile.write('<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" '
114
'"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/'
116
outfile.write('<svg xmlns="http://www.w3.org/2000/svg">\n')
117
outfile.write('<g font-family="%s" font-size="%s">\n' %
118
(self.fontfamily, self.fontsize))
119
outfile.write('<text x="%s" y="%s" xml:space="preserve">' % (x, y))
120
for ttype, value in tokensource:
121
style = self._get_style(ttype)
122
tspan = style and '<tspan' + style + '>' or ''
123
tspanend = tspan and '</tspan>' or ''
124
value = escape_html(value)
126
value = value.expandtabs().replace(' ', ' ')
127
parts = value.split('\n')
128
for part in parts[:-1]:
129
outfile.write(tspan + part + tspanend)
131
outfile.write('</text>\n<text x="%s" y="%s" '
132
'xml:space="preserve">' % (x, y))
133
outfile.write(tspan + parts[-1] + tspanend)
134
outfile.write('</text>')
137
outfile.write('</g></svg>\n')
139
def _get_style(self, tokentype):
140
if tokentype in self._stylecache:
141
return self._stylecache[tokentype]
142
otokentype = tokentype
143
while not self.style.styles_token(tokentype):
144
tokentype = tokentype.parent
145
value = self.style.style_for_token(tokentype)
148
result = ' fill="#' + value['color'] + '"'
150
result += ' font-weight="bold"'
152
result += ' font-style="italic"'
153
self._stylecache[otokentype] = result