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