|
1 # -*- coding: utf-8 -*- |
|
2 """ |
|
3 pygments.formatters.svg |
|
4 ~~~~~~~~~~~~~~~~~~~~~~~ |
|
5 |
|
6 Formatter for SVG output. |
|
7 |
|
8 :copyright: Copyright 2006-2009 by the Pygments team, see AUTHORS. |
|
9 :license: BSD, see LICENSE for details. |
|
10 """ |
|
11 |
|
12 from pygments.formatter import Formatter |
|
13 from pygments.util import get_bool_opt, get_int_opt |
|
14 |
|
15 __all__ = ['SvgFormatter'] |
|
16 |
|
17 |
|
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('"', '"'). \ |
|
24 replace("'", ''') |
|
25 |
|
26 |
|
27 class2style = {} |
|
28 |
|
29 class SvgFormatter(Formatter): |
|
30 """ |
|
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. |
|
34 |
|
35 By default, this formatter outputs a full SVG document including doctype |
|
36 declaration and the ``<svg>`` root element. |
|
37 |
|
38 *New in Pygments 0.9.* |
|
39 |
|
40 Additional options accepted: |
|
41 |
|
42 `nowrap` |
|
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``. |
|
46 |
|
47 `fontfamily` |
|
48 The value to give the wrapping ``<g>`` element's ``font-family`` |
|
49 attribute, defaults to ``"monospace"``. |
|
50 |
|
51 `fontsize` |
|
52 The value to give the wrapping ``<g>`` element's ``font-size`` |
|
53 attribute, defaults to ``"14px"``. |
|
54 |
|
55 `xoffset` |
|
56 Starting offset in X direction, defaults to ``0``. |
|
57 |
|
58 `yoffset` |
|
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.) |
|
62 |
|
63 `ystep` |
|
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. |
|
67 |
|
68 `spacehack` |
|
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``. |
|
75 """ |
|
76 name = 'SVG' |
|
77 aliases = ['svg'] |
|
78 filenames = ['*.svg'] |
|
79 |
|
80 def __init__(self, **options): |
|
81 # XXX outencoding |
|
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() |
|
89 try: |
|
90 int_fs = int(fs) |
|
91 except: |
|
92 int_fs = 20 |
|
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) |
|
96 self._stylecache = {} |
|
97 |
|
98 def format_unencoded(self, tokensource, outfile): |
|
99 """ |
|
100 Format ``tokensource``, an iterable of ``(tokentype, tokenstring)`` |
|
101 tuples and write it into ``outfile``. |
|
102 |
|
103 For our implementation we put all lines in their own 'line group'. |
|
104 """ |
|
105 x = self.xoffset |
|
106 y = self.yoffset |
|
107 if not self.nowrap: |
|
108 if self.encoding: |
|
109 outfile.write('<?xml version="1.0" encoding="%s"?>\n' % |
|
110 self.encoding) |
|
111 else: |
|
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/' |
|
115 'svg10.dtd">\n') |
|
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) |
|
125 if self.spacehack: |
|
126 value = value.expandtabs().replace(' ', ' ') |
|
127 parts = value.split('\n') |
|
128 for part in parts[:-1]: |
|
129 outfile.write(tspan + part + tspanend) |
|
130 y += self.ystep |
|
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>') |
|
135 |
|
136 if not self.nowrap: |
|
137 outfile.write('</g></svg>\n') |
|
138 |
|
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) |
|
146 result = '' |
|
147 if value['color']: |
|
148 result = ' fill="#' + value['color'] + '"' |
|
149 if value['bold']: |
|
150 result += ' font-weight="bold"' |
|
151 if value['italic']: |
|
152 result += ' font-style="italic"' |
|
153 self._stylecache[otokentype] = result |
|
154 return result |