summaryrefslogtreecommitdiff
path: root/pygments/formatters/rtf.py
blob: b7030857216bd41c1deab27d883519f50a637387 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# -*- coding: utf-8 -*-
"""
    pygments.formatters.rtf
    ~~~~~~~~~~~~~~~~~~~~~~~

    A formatter that generates RTF files.

    :copyright: 2006 by Armin Ronacher.
    :license: BSD, see LICENSE for more details.
"""
import cStringIO

from pygments.formatter import Formatter
from pygments.token import Token
from pygments.util import get_bool_opt, get_int_opt


__all__ = ['RtfFormatter']


class RtfFormatter(Formatter):

    def __init__(self, **options):
        """
        Additional options accepted:

        ``fontface``
            Name of the font used. Could for example be ``'Courier New'``
            to further specify the default which is ``'\fmodern'``. The RTF
            specification claims that ``\fmodern`` are "Fixed-pitch serif
            and sans serif fonts". Hope every RTF implementation thinks
            the same about modern...
        """
        Formatter.__init__(self, **options)
        self.fontface = options.get('fontface') or ''

    def _escape(self, text):
        return text.replace('\\', '\\\\') \
                   .replace('{', '\\{') \
                   .replace('}', '\\}')

    def _escape_text(self, text):
        # empty strings, should give a small performance improvment
        if not text:
            return ''

        # escape text
        text = self._escape(text)

        # byte strings
        if isinstance(text, str):
            for c in xrange(128, 256):
                text = text.replace(chr(c), '\\\'%x' % c)

        # unicode strings
        elif isinstance(text, unicode):
            buf = []
            for c in text:
                o = ord(c)
                if o > 128:
                    ansic = c.encode('iso-8859-1', 'ignore') or '?'
                    if ord(ansic) > 128:
                        ansic = '\\\'%x' % ord(ansic)
                    buf.append(r'\ud{\u%d%s}' % (o, ansic))
                else:
                    buf.append(str(c))
            text = ''.join(buf)

        return text.replace('\n', '\\par\n')

    def format(self, tokensource, outfile):
        outfile.write(r'{\rtf1\ansi\deff0'
                      r'{\fonttbl{\f0\fmodern\fprq1\fcharset0%s;}}{\colortbl;' %
                      (self.fontface and ' ' + self._escape(self.fontface) or ''))

        # convert colors and save them in a mapping to access them later.
        color_mapping = {}
        offset = 1
        for _, style in self.style:
            for color in style['color'], style['bgcolor'], style['border']:
                if not color or color in color_mapping:
                    continue
                color_mapping[color] = offset
                outfile.write(r'\red%d\green%d\blue%d;' % (
                    int(color[0:2], 16),
                    int(color[2:4], 16),
                    int(color[4:6], 16)
                ))
                offset += 1
        outfile.write(r'}\f0')

        # highlight stream
        for ttype, value in tokensource:
            try:
                style = self.style.style_for_token(ttype)
            except KeyError:
                start = ''
            else:
                buf = []
                if style['bgcolor']:
                    buf.append(r'\cb%d' % color_mapping[style['bgcolor']])
                if style['color']:
                    buf.append(r'\cf%d' % color_mapping[style['color']])
                if style['bold']:
                    buf.append(r'\b')
                if style['italic']:
                    buf.append(r'\i')
                if style['underline']:
                    buf.append(r'\ul')
                if style['border']:
                    buf.append(r'\chbrdr\chcfpat%d' % color_mapping[style['border']])
                start = ''.join(buf)
            if start:
                outfile.write('{%s ' % start)
            outfile.write(self._escape_text(value))
            if start:
                outfile.write('}')

        outfile.write('}')