1""" 2 pygments.formatters.rtf 3 ~~~~~~~~~~~~~~~~~~~~~~~ 4 5 A formatter that generates RTF files. 6 7 :copyright: Copyright 2006-2021 by the Pygments team, see AUTHORS. 8 :license: BSD, see LICENSE for details. 9""" 10 11from pygments.formatter import Formatter 12from pygments.util import get_int_opt, surrogatepair 13 14 15__all__ = ['RtfFormatter'] 16 17 18class RtfFormatter(Formatter): 19 """ 20 Format tokens as RTF markup. This formatter automatically outputs full RTF 21 documents with color information and other useful stuff. Perfect for Copy and 22 Paste into Microsoft(R) Word(R) documents. 23 24 Please note that ``encoding`` and ``outencoding`` options are ignored. 25 The RTF format is ASCII natively, but handles unicode characters correctly 26 thanks to escape sequences. 27 28 .. versionadded:: 0.6 29 30 Additional options accepted: 31 32 `style` 33 The style to use, can be a string or a Style subclass (default: 34 ``'default'``). 35 36 `fontface` 37 The used font family, for example ``Bitstream Vera Sans``. Defaults to 38 some generic font which is supposed to have fixed width. 39 40 `fontsize` 41 Size of the font used. Size is specified in half points. The 42 default is 24 half-points, giving a size 12 font. 43 44 .. versionadded:: 2.0 45 """ 46 name = 'RTF' 47 aliases = ['rtf'] 48 filenames = ['*.rtf'] 49 50 def __init__(self, **options): 51 r""" 52 Additional options accepted: 53 54 ``fontface`` 55 Name of the font used. Could for example be ``'Courier New'`` 56 to further specify the default which is ``'\fmodern'``. The RTF 57 specification claims that ``\fmodern`` are "Fixed-pitch serif 58 and sans serif fonts". Hope every RTF implementation thinks 59 the same about modern... 60 61 """ 62 Formatter.__init__(self, **options) 63 self.fontface = options.get('fontface') or '' 64 self.fontsize = get_int_opt(options, 'fontsize', 0) 65 66 def _escape(self, text): 67 return text.replace('\\', '\\\\') \ 68 .replace('{', '\\{') \ 69 .replace('}', '\\}') 70 71 def _escape_text(self, text): 72 # empty strings, should give a small performance improvement 73 if not text: 74 return '' 75 76 # escape text 77 text = self._escape(text) 78 79 buf = [] 80 for c in text: 81 cn = ord(c) 82 if cn < (2**7): 83 # ASCII character 84 buf.append(str(c)) 85 elif (2**7) <= cn < (2**16): 86 # single unicode escape sequence 87 buf.append('{\\u%d}' % cn) 88 elif (2**16) <= cn: 89 # RTF limits unicode to 16 bits. 90 # Force surrogate pairs 91 buf.append('{\\u%d}{\\u%d}' % surrogatepair(cn)) 92 93 return ''.join(buf).replace('\n', '\\par\n') 94 95 def format_unencoded(self, tokensource, outfile): 96 # rtf 1.8 header 97 outfile.write('{\\rtf1\\ansi\\uc0\\deff0' 98 '{\\fonttbl{\\f0\\fmodern\\fprq1\\fcharset0%s;}}' 99 '{\\colortbl;' % (self.fontface and 100 ' ' + self._escape(self.fontface) or 101 '')) 102 103 # convert colors and save them in a mapping to access them later. 104 color_mapping = {} 105 offset = 1 106 for _, style in self.style: 107 for color in style['color'], style['bgcolor'], style['border']: 108 if color and color not in color_mapping: 109 color_mapping[color] = offset 110 outfile.write('\\red%d\\green%d\\blue%d;' % ( 111 int(color[0:2], 16), 112 int(color[2:4], 16), 113 int(color[4:6], 16) 114 )) 115 offset += 1 116 outfile.write('}\\f0 ') 117 if self.fontsize: 118 outfile.write('\\fs%d' % self.fontsize) 119 120 # highlight stream 121 for ttype, value in tokensource: 122 while not self.style.styles_token(ttype) and ttype.parent: 123 ttype = ttype.parent 124 style = self.style.style_for_token(ttype) 125 buf = [] 126 if style['bgcolor']: 127 buf.append('\\cb%d' % color_mapping[style['bgcolor']]) 128 if style['color']: 129 buf.append('\\cf%d' % color_mapping[style['color']]) 130 if style['bold']: 131 buf.append('\\b') 132 if style['italic']: 133 buf.append('\\i') 134 if style['underline']: 135 buf.append('\\ul') 136 if style['border']: 137 buf.append('\\chbrdr\\chcfpat%d' % 138 color_mapping[style['border']]) 139 start = ''.join(buf) 140 if start: 141 outfile.write('{%s ' % start) 142 outfile.write(self._escape_text(value)) 143 if start: 144 outfile.write('}') 145 146 outfile.write('}') 147