rtf.py 4.9 KB

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