| """ |
| pygments.formatters.svg |
| ~~~~~~~~~~~~~~~~~~~~~~~ |
| |
| Formatter for SVG output. |
| |
| :copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS. |
| :license: BSD, see LICENSE for details. |
| """ |
| |
| from pip._vendor.pygments.formatter import Formatter |
| from pip._vendor.pygments.token import Comment |
| from pip._vendor.pygments.util import get_bool_opt, get_int_opt |
| |
| __all__ = ['SvgFormatter'] |
| |
| |
| def escape_html(text): |
| """Escape &, <, > as well as single and double quotes for HTML.""" |
| return text.replace('&', '&'). \ |
| replace('<', '<'). \ |
| replace('>', '>'). \ |
| replace('"', '"'). \ |
| replace("'", ''') |
| |
| |
| class2style = {} |
| |
| class SvgFormatter(Formatter): |
| """ |
| Format tokens as an SVG graphics file. This formatter is still experimental. |
| Each line of code is a ``<text>`` element with explicit ``x`` and ``y`` |
| coordinates containing ``<tspan>`` elements with the individual token styles. |
| |
| By default, this formatter outputs a full SVG document including doctype |
| declaration and the ``<svg>`` root element. |
| |
| .. versionadded:: 0.9 |
| |
| Additional options accepted: |
| |
| `nowrap` |
| Don't wrap the SVG ``<text>`` elements in ``<svg><g>`` elements and |
| don't add a XML declaration and a doctype. If true, the `fontfamily` |
| and `fontsize` options are ignored. Defaults to ``False``. |
| |
| `fontfamily` |
| The value to give the wrapping ``<g>`` element's ``font-family`` |
| attribute, defaults to ``"monospace"``. |
| |
| `fontsize` |
| The value to give the wrapping ``<g>`` element's ``font-size`` |
| attribute, defaults to ``"14px"``. |
| |
| `linenos` |
| If ``True``, add line numbers (default: ``False``). |
| |
| `linenostart` |
| The line number for the first line (default: ``1``). |
| |
| `linenostep` |
| If set to a number n > 1, only every nth line number is printed. |
| |
| `linenowidth` |
| Maximum width devoted to line numbers (default: ``3*ystep``, sufficient |
| for up to 4-digit line numbers. Increase width for longer code blocks). |
| |
| `xoffset` |
| Starting offset in X direction, defaults to ``0``. |
| |
| `yoffset` |
| Starting offset in Y direction, defaults to the font size if it is given |
| in pixels, or ``20`` else. (This is necessary since text coordinates |
| refer to the text baseline, not the top edge.) |
| |
| `ystep` |
| Offset to add to the Y coordinate for each subsequent line. This should |
| roughly be the text size plus 5. It defaults to that value if the text |
| size is given in pixels, or ``25`` else. |
| |
| `spacehack` |
| Convert spaces in the source to `` ``, which are non-breaking |
| spaces. SVG provides the ``xml:space`` attribute to control how |
| whitespace inside tags is handled, in theory, the ``preserve`` value |
| could be used to keep all whitespace as-is. However, many current SVG |
| viewers don't obey that rule, so this option is provided as a workaround |
| and defaults to ``True``. |
| """ |
| name = 'SVG' |
| aliases = ['svg'] |
| filenames = ['*.svg'] |
| |
| def __init__(self, **options): |
| Formatter.__init__(self, **options) |
| self.nowrap = get_bool_opt(options, 'nowrap', False) |
| self.fontfamily = options.get('fontfamily', 'monospace') |
| self.fontsize = options.get('fontsize', '14px') |
| self.xoffset = get_int_opt(options, 'xoffset', 0) |
| fs = self.fontsize.strip() |
| if fs.endswith('px'): fs = fs[:-2].strip() |
| try: |
| int_fs = int(fs) |
| except: |
| int_fs = 20 |
| self.yoffset = get_int_opt(options, 'yoffset', int_fs) |
| self.ystep = get_int_opt(options, 'ystep', int_fs + 5) |
| self.spacehack = get_bool_opt(options, 'spacehack', True) |
| self.linenos = get_bool_opt(options,'linenos',False) |
| self.linenostart = get_int_opt(options,'linenostart',1) |
| self.linenostep = get_int_opt(options,'linenostep',1) |
| self.linenowidth = get_int_opt(options,'linenowidth', 3*self.ystep) |
| self._stylecache = {} |
| |
| def format_unencoded(self, tokensource, outfile): |
| """ |
| Format ``tokensource``, an iterable of ``(tokentype, tokenstring)`` |
| tuples and write it into ``outfile``. |
| |
| For our implementation we put all lines in their own 'line group'. |
| """ |
| x = self.xoffset |
| y = self.yoffset |
| if not self.nowrap: |
| if self.encoding: |
| outfile.write('<?xml version="1.0" encoding="%s"?>\n' % |
| self.encoding) |
| else: |
| outfile.write('<?xml version="1.0"?>\n') |
| outfile.write('<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" ' |
| '"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/' |
| 'svg10.dtd">\n') |
| outfile.write('<svg xmlns="http://www.w3.org/2000/svg">\n') |
| outfile.write('<g font-family="%s" font-size="%s">\n' % |
| (self.fontfamily, self.fontsize)) |
| |
| counter = self.linenostart |
| counter_step = self.linenostep |
| counter_style = self._get_style(Comment) |
| line_x = x |
| |
| if self.linenos: |
| if counter % counter_step == 0: |
| outfile.write('<text x="%s" y="%s" %s text-anchor="end">%s</text>' % |
| (x+self.linenowidth,y,counter_style,counter)) |
| line_x += self.linenowidth + self.ystep |
| counter += 1 |
| |
| outfile.write('<text x="%s" y="%s" xml:space="preserve">' % (line_x, y)) |
| for ttype, value in tokensource: |
| style = self._get_style(ttype) |
| tspan = style and '<tspan' + style + '>' or '' |
| tspanend = tspan and '</tspan>' or '' |
| value = escape_html(value) |
| if self.spacehack: |
| value = value.expandtabs().replace(' ', ' ') |
| parts = value.split('\n') |
| for part in parts[:-1]: |
| outfile.write(tspan + part + tspanend) |
| y += self.ystep |
| outfile.write('</text>\n') |
| if self.linenos and counter % counter_step == 0: |
| outfile.write('<text x="%s" y="%s" text-anchor="end" %s>%s</text>' % |
| (x+self.linenowidth,y,counter_style,counter)) |
| |
| counter += 1 |
| outfile.write('<text x="%s" y="%s" ' 'xml:space="preserve">' % (line_x,y)) |
| outfile.write(tspan + parts[-1] + tspanend) |
| outfile.write('</text>') |
| |
| if not self.nowrap: |
| outfile.write('</g></svg>\n') |
| |
| def _get_style(self, tokentype): |
| if tokentype in self._stylecache: |
| return self._stylecache[tokentype] |
| otokentype = tokentype |
| while not self.style.styles_token(tokentype): |
| tokentype = tokentype.parent |
| value = self.style.style_for_token(tokentype) |
| result = '' |
| if value['color']: |
| result = ' fill="#' + value['color'] + '"' |
| if value['bold']: |
| result += ' font-weight="bold"' |
| if value['italic']: |
| result += ' font-style="italic"' |
| self._stylecache[otokentype] = result |
| return result |