codekingpro/portable-devtools
114k
1"""2 pygments.formatters.rtf3 ~~~~~~~~~~~~~~~~~~~~~~~4 5 A formatter that generates RTF files.6 7 :copyright: Copyright 2006-present by the Pygments team, see AUTHORS.8 :license: BSD, see LICENSE for details.9"""10 11from collections import OrderedDict12from pygments.formatter import Formatter13from pygments.style import _ansimap14from pygments.util import get_bool_opt, get_int_opt, get_list_opt, surrogatepair15 16 17__all__ = ['RtfFormatter']18 19 20class RtfFormatter(Formatter):21 """22 Format tokens as RTF markup. This formatter automatically outputs full RTF23 documents with color information and other useful stuff. Perfect for Copy and24 Paste into Microsoft(R) Word(R) documents.25 26 Please note that ``encoding`` and ``outencoding`` options are ignored.27 The RTF format is ASCII natively, but handles unicode characters correctly28 thanks to escape sequences.29 30 .. versionadded:: 0.631 32 Additional options accepted:33 34 `style`35 The style to use, can be a string or a Style subclass (default:36 ``'default'``).37 38 `fontface`39 The used font family, for example ``Bitstream Vera Sans``. Defaults to40 some generic font which is supposed to have fixed width.41 42 `fontsize`43 Size of the font used. Size is specified in half points. The44 default is 24 half-points, giving a size 12 font.45 46 .. versionadded:: 2.047 48 `linenos`49 Turn on line numbering (default: ``False``).50 51 .. versionadded:: 2.1852 53 `lineno_fontsize`54 Font size for line numbers. Size is specified in half points55 (default: `fontsize`). 56 57 .. versionadded:: 2.1858 59 `lineno_padding`60 Number of spaces between the (inline) line numbers and the61 source code (default: ``2``).62 63 .. versionadded:: 2.1864 65 `linenostart`66 The line number for the first line (default: ``1``).67 68 .. versionadded:: 2.1869 70 `linenostep`71 If set to a number n > 1, only every nth line number is printed.72 73 .. versionadded:: 2.1874 75 `lineno_color`76 Color for line numbers specified as a hex triplet, e.g. ``'5e5e5e'``. 77 Defaults to the style's line number color if it is a hex triplet, 78 otherwise ansi bright black.79 80 .. versionadded:: 2.1881 82 `hl_lines`83 Specify a list of lines to be highlighted, as line numbers separated by84 spaces, e.g. ``'3 7 8'``. The line numbers are relative to the input 85 (i.e. the first line is line 1) unless `hl_linenostart` is set.86 87 .. versionadded:: 2.1888 89 `hl_color`90 Color for highlighting the lines specified in `hl_lines`, specified as 91 a hex triplet (default: style's `highlight_color`).92 93 .. versionadded:: 2.1894 95 `hl_linenostart`96 If set to ``True`` line numbers in `hl_lines` are specified97 relative to `linenostart` (default ``False``).98 99 .. versionadded:: 2.18100 """101 name = 'RTF'102 aliases = ['rtf']103 filenames = ['*.rtf']104 105 def __init__(self, **options):106 r"""107 Additional options accepted:108 109 ``fontface``110 Name of the font used. Could for example be ``'Courier New'``111 to further specify the default which is ``'\fmodern'``. The RTF112 specification claims that ``\fmodern`` are "Fixed-pitch serif113 and sans serif fonts". Hope every RTF implementation thinks114 the same about modern...115 116 """117 Formatter.__init__(self, **options)118 self.fontface = options.get('fontface') or ''119 self.fontsize = get_int_opt(options, 'fontsize', 0)120 self.linenos = get_bool_opt(options, 'linenos', False)121 self.lineno_fontsize = get_int_opt(options, 'lineno_fontsize',122 self.fontsize)123 self.lineno_padding = get_int_opt(options, 'lineno_padding', 2)124 self.linenostart = abs(get_int_opt(options, 'linenostart', 1))125 self.linenostep = abs(get_int_opt(options, 'linenostep', 1))126 self.hl_linenostart = get_bool_opt(options, 'hl_linenostart', False)127 128 self.hl_color = options.get('hl_color', '')129 if not self.hl_color:130 self.hl_color = self.style.highlight_color131 132 self.hl_lines = []133 for lineno in get_list_opt(options, 'hl_lines', []):134 try:135 lineno = int(lineno)136 if self.hl_linenostart:137 lineno = lineno - self.linenostart + 1138 self.hl_lines.append(lineno)139 except ValueError:140 pass141 142 self.lineno_color = options.get('lineno_color', '')143 if not self.lineno_color:144 if self.style.line_number_color == 'inherit':145 # style color is the css value 'inherit'146 # default to ansi bright-black147 self.lineno_color = _ansimap['ansibrightblack']148 else:149 # style color is assumed to be a hex triplet as other150 # colors in pygments/style.py151 self.lineno_color = self.style.line_number_color152 153 self.color_mapping = self._create_color_mapping()154 155 def _escape(self, text):156 return text.replace('\\', '\\\\') \157 .replace('{', '\\{') \158 .replace('}', '\\}')159 160 def _escape_text(self, text):161 # empty strings, should give a small performance improvement162 if not text:163 return ''164 165 # escape text166 text = self._escape(text)167 168 buf = []169 for c in text:170 cn = ord(c)171 if cn < (2**7):172 # ASCII character173 buf.append(str(c))174 elif (2**7) <= cn < (2**16):175 # single unicode escape sequence176 buf.append('{\\u%d}' % cn)177 elif (2**16) <= cn:178 # RTF limits unicode to 16 bits.179 # Force surrogate pairs180 buf.append('{\\u%d}{\\u%d}' % surrogatepair(cn))181 182 return ''.join(buf).replace('\n', '\\par')183 184 @staticmethod185 def hex_to_rtf_color(hex_color):186 if hex_color[0] == "#":187 hex_color = hex_color[1:]188 189 return '\\red%d\\green%d\\blue%d;' % (190 int(hex_color[0:2], 16),191 int(hex_color[2:4], 16),192 int(hex_color[4:6], 16)193 )194 195 def _split_tokens_on_newlines(self, tokensource):196 """197 Split tokens containing newline characters into multiple token198 each representing a line of the input file. Needed for numbering199 lines of e.g. multiline comments.200 """201 for ttype, value in tokensource:202 if value == '\n':203 yield (ttype, value)204 elif "\n" in value:205 lines = value.split("\n")206 for line in lines[:-1]:207 yield (ttype, line+"\n")208 if lines[-1]:209 yield (ttype, lines[-1])210 else:211 yield (ttype, value)212 213 def _create_color_mapping(self):214 """215 Create a mapping of style hex colors to index/offset in216 the RTF color table.217 """218 color_mapping = OrderedDict()219 offset = 1220 221 if self.linenos:222 color_mapping[self.lineno_color] = offset223 offset += 1224 225 if self.hl_lines:226 color_mapping[self.hl_color] = offset227 offset += 1228 229 for _, style in self.style:230 for color in style['color'], style['bgcolor'], style['border']:231 if color and color not in color_mapping:232 color_mapping[color] = offset233 offset += 1234 235 return color_mapping236 237 @property238 def _lineno_template(self):239 if self.lineno_fontsize != self.fontsize:240 return '{{\\fs{} \\cf{} %s{}}}'.format(self.lineno_fontsize,241 self.color_mapping[self.lineno_color],242 " " * self.lineno_padding)243 244 return '{{\\cf{} %s{}}}'.format(self.color_mapping[self.lineno_color],245 " " * self.lineno_padding)246 247 @property248 def _hl_open_str(self):249 return rf'{{\highlight{self.color_mapping[self.hl_color]} '250 251 @property252 def _rtf_header(self):253 lines = []254 # rtf 1.8 header255 lines.append('{\\rtf1\\ansi\\uc0\\deff0'256 '{\\fonttbl{\\f0\\fmodern\\fprq1\\fcharset0%s;}}'257 % (self.fontface and ' '258 + self._escape(self.fontface) or ''))259 260 # color table261 lines.append('{\\colortbl;')262 for color, _ in self.color_mapping.items():263 lines.append(self.hex_to_rtf_color(color))264 lines.append('}')265 266 # font and fontsize267 lines.append('\\f0\\sa0')268 if self.fontsize:269 lines.append('\\fs%d' % self.fontsize)270 271 # ensure Libre Office Writer imports and renders consecutive272 # space characters the same width, needed for line numbering.273 # https://bugs.documentfoundation.org/show_bug.cgi?id=144050274 lines.append('\\dntblnsbdb')275 276 return lines277 278 def format_unencoded(self, tokensource, outfile):279 for line in self._rtf_header:280 outfile.write(line + "\n")281 282 tokensource = self._split_tokens_on_newlines(tokensource)283 284 # first pass of tokens to count lines, needed for line numbering285 if self.linenos:286 line_count = 0287 tokens = [] # for copying the token source generator288 for ttype, value in tokensource:289 tokens.append((ttype, value))290 if value.endswith("\n"):291 line_count += 1292 293 # width of line number strings (for padding with spaces)294 linenos_width = len(str(line_count+self.linenostart-1))295 296 tokensource = tokens297 298 # highlight stream299 lineno = 1300 start_new_line = True301 for ttype, value in tokensource:302 if start_new_line and lineno in self.hl_lines:303 outfile.write(self._hl_open_str)304 305 if start_new_line and self.linenos:306 if (lineno-self.linenostart+1)%self.linenostep == 0:307 current_lineno = lineno + self.linenostart - 1308 lineno_str = str(current_lineno).rjust(linenos_width)309 else:310 lineno_str = "".rjust(linenos_width)311 outfile.write(self._lineno_template % lineno_str)312 313 while not self.style.styles_token(ttype) and ttype.parent:314 ttype = ttype.parent315 style = self.style.style_for_token(ttype)316 buf = []317 if style['bgcolor']:318 buf.append('\\cb%d' % self.color_mapping[style['bgcolor']])319 if style['color']:320 buf.append('\\cf%d' % self.color_mapping[style['color']])321 if style['bold']:322 buf.append('\\b')323 if style['italic']:324 buf.append('\\i')325 if style['underline']:326 buf.append('\\ul')327 if style['border']:328 buf.append('\\chbrdr\\chcfpat%d' %329 self.color_mapping[style['border']])330 start = ''.join(buf)331 if start:332 outfile.write(f'{{{start} ')333 outfile.write(self._escape_text(value))334 if start:335 outfile.write('}')336 start_new_line = False337 338 # complete line of input339 if value.endswith("\n"):340 # close line highlighting341 if lineno in self.hl_lines:342 outfile.write('}')343 # newline in RTF file after closing }344 outfile.write("\n")345 346 start_new_line = True347 lineno += 1348 349 outfile.write('}\n')350 