Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pygments/formatters/svg.py: 89%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

87 statements  

1""" 

2 pygments.formatters.svg 

3 ~~~~~~~~~~~~~~~~~~~~~~~ 

4 

5 Formatter for SVG output. 

6 

7 :copyright: Copyright 2006-present by the Pygments team, see AUTHORS. 

8 :license: BSD, see LICENSE for details. 

9""" 

10 

11from pygments.formatter import Formatter 

12from pygments.token import Comment 

13from pygments.util import get_bool_opt, get_int_opt, html_escape 

14 

15__all__ = ['SvgFormatter'] 

16 

17 

18class2style = {} 

19 

20class SvgFormatter(Formatter): 

21 """ 

22 Format tokens as an SVG graphics file. This formatter is still experimental. 

23 Each line of code is a ``<text>`` element with explicit ``x`` and ``y`` 

24 coordinates containing ``<tspan>`` elements with the individual token styles. 

25 

26 By default, this formatter outputs a full SVG document including doctype 

27 declaration and the ``<svg>`` root element. 

28 

29 .. versionadded:: 0.9 

30 

31 Additional options accepted: 

32 

33 `nowrap` 

34 Don't wrap the SVG ``<text>`` elements in ``<svg><g>`` elements and 

35 don't add a XML declaration and a doctype. If true, the `fontfamily` 

36 and `fontsize` options are ignored. Defaults to ``False``. 

37 

38 `fontfamily` 

39 The value to give the wrapping ``<g>`` element's ``font-family`` 

40 attribute, defaults to ``"monospace"``. 

41 

42 `fontsize` 

43 The value to give the wrapping ``<g>`` element's ``font-size`` 

44 attribute, defaults to ``"14px"``. 

45 

46 `linenos` 

47 If ``True``, add line numbers (default: ``False``). 

48 

49 `linenostart` 

50 The line number for the first line (default: ``1``). 

51 

52 `linenostep` 

53 If set to a number n > 1, only every nth line number is printed. 

54 

55 `linenowidth` 

56 Maximum width devoted to line numbers (default: ``3*ystep``, sufficient 

57 for up to 4-digit line numbers. Increase width for longer code blocks). 

58 

59 `xoffset` 

60 Starting offset in X direction, defaults to ``0``. 

61 

62 `yoffset` 

63 Starting offset in Y direction, defaults to the font size if it is given 

64 in pixels, or ``20`` else. (This is necessary since text coordinates 

65 refer to the text baseline, not the top edge.) 

66 

67 `ystep` 

68 Offset to add to the Y coordinate for each subsequent line. This should 

69 roughly be the text size plus 5. It defaults to that value if the text 

70 size is given in pixels, or ``25`` else. 

71 

72 `spacehack` 

73 Convert spaces in the source to ``&#160;``, which are non-breaking 

74 spaces. SVG provides the ``xml:space`` attribute to control how 

75 whitespace inside tags is handled, in theory, the ``preserve`` value 

76 could be used to keep all whitespace as-is. However, many current SVG 

77 viewers don't obey that rule, so this option is provided as a workaround 

78 and defaults to ``True``. 

79 """ 

80 name = 'SVG' 

81 aliases = ['svg'] 

82 filenames = ['*.svg'] 

83 

84 def __init__(self, **options): 

85 Formatter.__init__(self, **options) 

86 self.nowrap = get_bool_opt(options, 'nowrap', False) 

87 self.fontfamily = options.get('fontfamily', 'monospace') 

88 self.fontsize = options.get('fontsize', '14px') 

89 self.xoffset = get_int_opt(options, 'xoffset', 0) 

90 fs = self.fontsize.strip() 

91 if fs.endswith('px'): 

92 fs = fs[:-2].strip() 

93 try: 

94 int_fs = int(fs) 

95 except ValueError: 

96 int_fs = 20 

97 self.yoffset = get_int_opt(options, 'yoffset', int_fs) 

98 self.ystep = get_int_opt(options, 'ystep', int_fs + 5) 

99 self.spacehack = get_bool_opt(options, 'spacehack', True) 

100 self.linenos = get_bool_opt(options,'linenos',False) 

101 self.linenostart = get_int_opt(options,'linenostart',1) 

102 self.linenostep = get_int_opt(options,'linenostep',1) 

103 self.linenowidth = get_int_opt(options,'linenowidth', 3*self.ystep) 

104 self._stylecache = {} 

105 

106 def format_unencoded(self, tokensource, outfile): 

107 """ 

108 Format ``tokensource``, an iterable of ``(tokentype, tokenstring)`` 

109 tuples and write it into ``outfile``. 

110 

111 For our implementation we put all lines in their own 'line group'. 

112 """ 

113 x = self.xoffset 

114 y = self.yoffset 

115 if not self.nowrap: 

116 if self.encoding: 

117 outfile.write(f'<?xml version="1.0" encoding="{self.encoding}"?>\n') 

118 else: 

119 outfile.write('<?xml version="1.0"?>\n') 

120 outfile.write('<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" ' 

121 '"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/' 

122 'svg10.dtd">\n') 

123 outfile.write('<svg xmlns="http://www.w3.org/2000/svg">\n') 

124 outfile.write(f'<g font-family="{self.fontfamily}" font-size="{self.fontsize}">\n') 

125 

126 counter = self.linenostart 

127 counter_step = self.linenostep 

128 counter_style = self._get_style(Comment) 

129 line_x = x 

130 

131 if self.linenos: 

132 if counter % counter_step == 0: 

133 outfile.write(f'<text x="{x+self.linenowidth}" y="{y}" {counter_style} text-anchor="end">{counter}</text>') 

134 line_x += self.linenowidth + self.ystep 

135 counter += 1 

136 

137 outfile.write(f'<text x="{line_x}" y="{y}" xml:space="preserve">') 

138 for ttype, value in tokensource: 

139 style = self._get_style(ttype) 

140 tspan = style and '<tspan' + style + '>' or '' 

141 tspanend = tspan and '</tspan>' or '' 

142 value = html_escape(value) 

143 if self.spacehack: 

144 value = value.expandtabs().replace(' ', '&#160;') 

145 parts = value.split('\n') 

146 for part in parts[:-1]: 

147 outfile.write(tspan + part + tspanend) 

148 y += self.ystep 

149 outfile.write('</text>\n') 

150 if self.linenos and counter % counter_step == 0: 

151 outfile.write(f'<text x="{x+self.linenowidth}" y="{y}" text-anchor="end" {counter_style}>{counter}</text>') 

152 

153 counter += 1 

154 outfile.write(f'<text x="{line_x}" y="{y}" ' 'xml:space="preserve">') 

155 outfile.write(tspan + parts[-1] + tspanend) 

156 outfile.write('</text>') 

157 

158 if not self.nowrap: 

159 outfile.write('</g></svg>\n') 

160 

161 def _get_style(self, tokentype): 

162 if tokentype in self._stylecache: 

163 return self._stylecache[tokentype] 

164 otokentype = tokentype 

165 while not self.style.styles_token(tokentype): 

166 tokentype = tokentype.parent 

167 value = self.style.style_for_token(tokentype) 

168 result = '' 

169 if value['color']: 

170 result = ' fill="#' + value['color'] + '"' 

171 if value['bold']: 

172 result += ' font-weight="bold"' 

173 if value['italic']: 

174 result += ' font-style="italic"' 

175 self._stylecache[otokentype] = result 

176 return result