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

87 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-03-26 07:45 +0000

1""" 

2 pygments.formatters.svg 

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

4 

5 Formatter for SVG output. 

6 

7 :copyright: Copyright 2006-2022 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 

14 

15__all__ = ['SvgFormatter'] 

16 

17 

18def escape_html(text): 

19 """Escape &, <, > as well as single and double quotes for HTML.""" 

20 return text.replace('&', '&amp;'). \ 

21 replace('<', '&lt;'). \ 

22 replace('>', '&gt;'). \ 

23 replace('"', '&quot;'). \ 

24 replace("'", '&#39;') 

25 

26 

27class2style = {} 

28 

29class 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 .. versionadded:: 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 `linenos` 

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

57 

58 `linenostart` 

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

60 

61 `linenostep` 

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

63  

64 `linenowidth` 

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

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

67  

68 `xoffset` 

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

70 

71 `yoffset` 

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

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

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

75 

76 `ystep` 

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

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

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

80 

81 `spacehack` 

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

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

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

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

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

87 and defaults to ``True``. 

88 """ 

89 name = 'SVG' 

90 aliases = ['svg'] 

91 filenames = ['*.svg'] 

92 

93 def __init__(self, **options): 

94 Formatter.__init__(self, **options) 

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

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

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

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

99 fs = self.fontsize.strip() 

100 if fs.endswith('px'): fs = fs[:-2].strip() 

101 try: 

102 int_fs = int(fs) 

103 except: 

104 int_fs = 20 

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

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

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

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

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

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

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

112 self._stylecache = {} 

113 

114 def format_unencoded(self, tokensource, outfile): 

115 """ 

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

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

118 

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

120 """ 

121 x = self.xoffset 

122 y = self.yoffset 

123 if not self.nowrap: 

124 if self.encoding: 

125 outfile.write('<?xml version="1.0" encoding="%s"?>\n' % 

126 self.encoding) 

127 else: 

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

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

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

131 'svg10.dtd">\n') 

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

133 outfile.write('<g font-family="%s" font-size="%s">\n' % 

134 (self.fontfamily, self.fontsize)) 

135 

136 counter = self.linenostart 

137 counter_step = self.linenostep 

138 counter_style = self._get_style(Comment) 

139 line_x = x 

140 

141 if self.linenos: 

142 if counter % counter_step == 0: 

143 outfile.write('<text x="%s" y="%s" %s text-anchor="end">%s</text>' % 

144 (x+self.linenowidth,y,counter_style,counter)) 

145 line_x += self.linenowidth + self.ystep 

146 counter += 1 

147 

148 outfile.write('<text x="%s" y="%s" xml:space="preserve">' % (line_x, y)) 

149 for ttype, value in tokensource: 

150 style = self._get_style(ttype) 

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

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

153 value = escape_html(value) 

154 if self.spacehack: 

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

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

157 for part in parts[:-1]: 

158 outfile.write(tspan + part + tspanend) 

159 y += self.ystep 

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

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

162 outfile.write('<text x="%s" y="%s" text-anchor="end" %s>%s</text>' % 

163 (x+self.linenowidth,y,counter_style,counter)) 

164 

165 counter += 1 

166 outfile.write('<text x="%s" y="%s" ' 'xml:space="preserve">' % (line_x,y)) 

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

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

169 

170 if not self.nowrap: 

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

172 

173 def _get_style(self, tokentype): 

174 if tokentype in self._stylecache: 

175 return self._stylecache[tokentype] 

176 otokentype = tokentype 

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

178 tokentype = tokentype.parent 

179 value = self.style.style_for_token(tokentype) 

180 result = '' 

181 if value['color']: 

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

183 if value['bold']: 

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

185 if value['italic']: 

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

187 self._stylecache[otokentype] = result 

188 return result