You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			186 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			186 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			Python
		
	
"""
 | 
						|
    pygments.formatters.svg
 | 
						|
    ~~~~~~~~~~~~~~~~~~~~~~~
 | 
						|
 | 
						|
    Formatter for SVG output.
 | 
						|
 | 
						|
    :copyright: Copyright 2006-2025 by the Pygments team, see AUTHORS.
 | 
						|
    :license: BSD, see LICENSE for details.
 | 
						|
"""
 | 
						|
 | 
						|
from pygments.formatter import Formatter
 | 
						|
from pygments.token import Comment
 | 
						|
from 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 ValueError:
 | 
						|
            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(f'<?xml version="1.0" encoding="{self.encoding}"?>\n')
 | 
						|
            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(f'<g font-family="{self.fontfamily}" font-size="{self.fontsize}">\n')
 | 
						|
 | 
						|
        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(f'<text x="{x+self.linenowidth}" y="{y}" {counter_style} text-anchor="end">{counter}</text>')
 | 
						|
            line_x += self.linenowidth + self.ystep
 | 
						|
            counter += 1
 | 
						|
 | 
						|
        outfile.write(f'<text x="{line_x}" y="{y}" xml:space="preserve">')
 | 
						|
        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(f'<text x="{x+self.linenowidth}" y="{y}" text-anchor="end" {counter_style}>{counter}</text>')
 | 
						|
 | 
						|
                counter += 1
 | 
						|
                outfile.write(f'<text x="{line_x}" y="{y}" ' 'xml:space="preserve">')
 | 
						|
            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
 |