second commit
This commit is contained in:
349
env/lib/python3.11/site-packages/pygments/formatters/rtf.py
vendored
Normal file
349
env/lib/python3.11/site-packages/pygments/formatters/rtf.py
vendored
Normal file
@ -0,0 +1,349 @@
|
||||
"""
|
||||
pygments.formatters.rtf
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A formatter that generates RTF files.
|
||||
|
||||
:copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
from collections import OrderedDict
|
||||
from pygments.formatter import Formatter
|
||||
from pygments.style import _ansimap
|
||||
from pygments.util import get_bool_opt, get_int_opt, get_list_opt, surrogatepair
|
||||
|
||||
|
||||
__all__ = ['RtfFormatter']
|
||||
|
||||
|
||||
class RtfFormatter(Formatter):
|
||||
"""
|
||||
Format tokens as RTF markup. This formatter automatically outputs full RTF
|
||||
documents with color information and other useful stuff. Perfect for Copy and
|
||||
Paste into Microsoft(R) Word(R) documents.
|
||||
|
||||
Please note that ``encoding`` and ``outencoding`` options are ignored.
|
||||
The RTF format is ASCII natively, but handles unicode characters correctly
|
||||
thanks to escape sequences.
|
||||
|
||||
.. versionadded:: 0.6
|
||||
|
||||
Additional options accepted:
|
||||
|
||||
`style`
|
||||
The style to use, can be a string or a Style subclass (default:
|
||||
``'default'``).
|
||||
|
||||
`fontface`
|
||||
The used font family, for example ``Bitstream Vera Sans``. Defaults to
|
||||
some generic font which is supposed to have fixed width.
|
||||
|
||||
`fontsize`
|
||||
Size of the font used. Size is specified in half points. The
|
||||
default is 24 half-points, giving a size 12 font.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
`linenos`
|
||||
Turn on line numbering (default: ``False``).
|
||||
|
||||
.. versionadded:: 2.18
|
||||
|
||||
`lineno_fontsize`
|
||||
Font size for line numbers. Size is specified in half points
|
||||
(default: `fontsize`).
|
||||
|
||||
.. versionadded:: 2.18
|
||||
|
||||
`lineno_padding`
|
||||
Number of spaces between the (inline) line numbers and the
|
||||
source code (default: ``2``).
|
||||
|
||||
.. versionadded:: 2.18
|
||||
|
||||
`linenostart`
|
||||
The line number for the first line (default: ``1``).
|
||||
|
||||
.. versionadded:: 2.18
|
||||
|
||||
`linenostep`
|
||||
If set to a number n > 1, only every nth line number is printed.
|
||||
|
||||
.. versionadded:: 2.18
|
||||
|
||||
`lineno_color`
|
||||
Color for line numbers specified as a hex triplet, e.g. ``'5e5e5e'``.
|
||||
Defaults to the style's line number color if it is a hex triplet,
|
||||
otherwise ansi bright black.
|
||||
|
||||
.. versionadded:: 2.18
|
||||
|
||||
`hl_lines`
|
||||
Specify a list of lines to be highlighted, as line numbers separated by
|
||||
spaces, e.g. ``'3 7 8'``. The line numbers are relative to the input
|
||||
(i.e. the first line is line 1) unless `hl_linenostart` is set.
|
||||
|
||||
.. versionadded:: 2.18
|
||||
|
||||
`hl_color`
|
||||
Color for highlighting the lines specified in `hl_lines`, specified as
|
||||
a hex triplet (default: style's `highlight_color`).
|
||||
|
||||
.. versionadded:: 2.18
|
||||
|
||||
`hl_linenostart`
|
||||
If set to ``True`` line numbers in `hl_lines` are specified
|
||||
relative to `linenostart` (default ``False``).
|
||||
|
||||
.. versionadded:: 2.18
|
||||
"""
|
||||
name = 'RTF'
|
||||
aliases = ['rtf']
|
||||
filenames = ['*.rtf']
|
||||
|
||||
def __init__(self, **options):
|
||||
r"""
|
||||
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 ''
|
||||
self.fontsize = get_int_opt(options, 'fontsize', 0)
|
||||
self.linenos = get_bool_opt(options, 'linenos', False)
|
||||
self.lineno_fontsize = get_int_opt(options, 'lineno_fontsize',
|
||||
self.fontsize)
|
||||
self.lineno_padding = get_int_opt(options, 'lineno_padding', 2)
|
||||
self.linenostart = abs(get_int_opt(options, 'linenostart', 1))
|
||||
self.linenostep = abs(get_int_opt(options, 'linenostep', 1))
|
||||
self.hl_linenostart = get_bool_opt(options, 'hl_linenostart', False)
|
||||
|
||||
self.hl_color = options.get('hl_color', '')
|
||||
if not self.hl_color:
|
||||
self.hl_color = self.style.highlight_color
|
||||
|
||||
self.hl_lines = []
|
||||
for lineno in get_list_opt(options, 'hl_lines', []):
|
||||
try:
|
||||
lineno = int(lineno)
|
||||
if self.hl_linenostart:
|
||||
lineno = lineno - self.linenostart + 1
|
||||
self.hl_lines.append(lineno)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
self.lineno_color = options.get('lineno_color', '')
|
||||
if not self.lineno_color:
|
||||
if self.style.line_number_color == 'inherit':
|
||||
# style color is the css value 'inherit'
|
||||
# default to ansi bright-black
|
||||
self.lineno_color = _ansimap['ansibrightblack']
|
||||
else:
|
||||
# style color is assumed to be a hex triplet as other
|
||||
# colors in pygments/style.py
|
||||
self.lineno_color = self.style.line_number_color
|
||||
|
||||
self.color_mapping = self._create_color_mapping()
|
||||
|
||||
def _escape(self, text):
|
||||
return text.replace('\\', '\\\\') \
|
||||
.replace('{', '\\{') \
|
||||
.replace('}', '\\}')
|
||||
|
||||
def _escape_text(self, text):
|
||||
# empty strings, should give a small performance improvement
|
||||
if not text:
|
||||
return ''
|
||||
|
||||
# escape text
|
||||
text = self._escape(text)
|
||||
|
||||
buf = []
|
||||
for c in text:
|
||||
cn = ord(c)
|
||||
if cn < (2**7):
|
||||
# ASCII character
|
||||
buf.append(str(c))
|
||||
elif (2**7) <= cn < (2**16):
|
||||
# single unicode escape sequence
|
||||
buf.append('{\\u%d}' % cn)
|
||||
elif (2**16) <= cn:
|
||||
# RTF limits unicode to 16 bits.
|
||||
# Force surrogate pairs
|
||||
buf.append('{\\u%d}{\\u%d}' % surrogatepair(cn))
|
||||
|
||||
return ''.join(buf).replace('\n', '\\par')
|
||||
|
||||
@staticmethod
|
||||
def hex_to_rtf_color(hex_color):
|
||||
if hex_color[0] == "#":
|
||||
hex_color = hex_color[1:]
|
||||
|
||||
return '\\red%d\\green%d\\blue%d;' % (
|
||||
int(hex_color[0:2], 16),
|
||||
int(hex_color[2:4], 16),
|
||||
int(hex_color[4:6], 16)
|
||||
)
|
||||
|
||||
def _split_tokens_on_newlines(self, tokensource):
|
||||
"""
|
||||
Split tokens containing newline characters into multiple token
|
||||
each representing a line of the input file. Needed for numbering
|
||||
lines of e.g. multiline comments.
|
||||
"""
|
||||
for ttype, value in tokensource:
|
||||
if value == '\n':
|
||||
yield (ttype, value)
|
||||
elif "\n" in value:
|
||||
lines = value.split("\n")
|
||||
for line in lines[:-1]:
|
||||
yield (ttype, line+"\n")
|
||||
if lines[-1]:
|
||||
yield (ttype, lines[-1])
|
||||
else:
|
||||
yield (ttype, value)
|
||||
|
||||
def _create_color_mapping(self):
|
||||
"""
|
||||
Create a mapping of style hex colors to index/offset in
|
||||
the RTF color table.
|
||||
"""
|
||||
color_mapping = OrderedDict()
|
||||
offset = 1
|
||||
|
||||
if self.linenos:
|
||||
color_mapping[self.lineno_color] = offset
|
||||
offset += 1
|
||||
|
||||
if self.hl_lines:
|
||||
color_mapping[self.hl_color] = offset
|
||||
offset += 1
|
||||
|
||||
for _, style in self.style:
|
||||
for color in style['color'], style['bgcolor'], style['border']:
|
||||
if color and color not in color_mapping:
|
||||
color_mapping[color] = offset
|
||||
offset += 1
|
||||
|
||||
return color_mapping
|
||||
|
||||
@property
|
||||
def _lineno_template(self):
|
||||
if self.lineno_fontsize != self.fontsize:
|
||||
return '{{\\fs{} \\cf{} %s{}}}'.format(self.lineno_fontsize,
|
||||
self.color_mapping[self.lineno_color],
|
||||
" " * self.lineno_padding)
|
||||
|
||||
return '{{\\cf{} %s{}}}'.format(self.color_mapping[self.lineno_color],
|
||||
" " * self.lineno_padding)
|
||||
|
||||
@property
|
||||
def _hl_open_str(self):
|
||||
return rf'{{\highlight{self.color_mapping[self.hl_color]} '
|
||||
|
||||
@property
|
||||
def _rtf_header(self):
|
||||
lines = []
|
||||
# rtf 1.8 header
|
||||
lines.append('{\\rtf1\\ansi\\uc0\\deff0'
|
||||
'{\\fonttbl{\\f0\\fmodern\\fprq1\\fcharset0%s;}}'
|
||||
% (self.fontface and ' '
|
||||
+ self._escape(self.fontface) or ''))
|
||||
|
||||
# color table
|
||||
lines.append('{\\colortbl;')
|
||||
for color, _ in self.color_mapping.items():
|
||||
lines.append(self.hex_to_rtf_color(color))
|
||||
lines.append('}')
|
||||
|
||||
# font and fontsize
|
||||
lines.append('\\f0\\sa0')
|
||||
if self.fontsize:
|
||||
lines.append('\\fs%d' % self.fontsize)
|
||||
|
||||
# ensure Libre Office Writer imports and renders consecutive
|
||||
# space characters the same width, needed for line numbering.
|
||||
# https://bugs.documentfoundation.org/show_bug.cgi?id=144050
|
||||
lines.append('\\dntblnsbdb')
|
||||
|
||||
return lines
|
||||
|
||||
def format_unencoded(self, tokensource, outfile):
|
||||
for line in self._rtf_header:
|
||||
outfile.write(line + "\n")
|
||||
|
||||
tokensource = self._split_tokens_on_newlines(tokensource)
|
||||
|
||||
# first pass of tokens to count lines, needed for line numbering
|
||||
if self.linenos:
|
||||
line_count = 0
|
||||
tokens = [] # for copying the token source generator
|
||||
for ttype, value in tokensource:
|
||||
tokens.append((ttype, value))
|
||||
if value.endswith("\n"):
|
||||
line_count += 1
|
||||
|
||||
# width of line number strings (for padding with spaces)
|
||||
linenos_width = len(str(line_count+self.linenostart-1))
|
||||
|
||||
tokensource = tokens
|
||||
|
||||
# highlight stream
|
||||
lineno = 1
|
||||
start_new_line = True
|
||||
for ttype, value in tokensource:
|
||||
if start_new_line and lineno in self.hl_lines:
|
||||
outfile.write(self._hl_open_str)
|
||||
|
||||
if start_new_line and self.linenos:
|
||||
if (lineno-self.linenostart+1)%self.linenostep == 0:
|
||||
current_lineno = lineno + self.linenostart - 1
|
||||
lineno_str = str(current_lineno).rjust(linenos_width)
|
||||
else:
|
||||
lineno_str = "".rjust(linenos_width)
|
||||
outfile.write(self._lineno_template % lineno_str)
|
||||
|
||||
while not self.style.styles_token(ttype) and ttype.parent:
|
||||
ttype = ttype.parent
|
||||
style = self.style.style_for_token(ttype)
|
||||
buf = []
|
||||
if style['bgcolor']:
|
||||
buf.append('\\cb%d' % self.color_mapping[style['bgcolor']])
|
||||
if style['color']:
|
||||
buf.append('\\cf%d' % self.color_mapping[style['color']])
|
||||
if style['bold']:
|
||||
buf.append('\\b')
|
||||
if style['italic']:
|
||||
buf.append('\\i')
|
||||
if style['underline']:
|
||||
buf.append('\\ul')
|
||||
if style['border']:
|
||||
buf.append('\\chbrdr\\chcfpat%d' %
|
||||
self.color_mapping[style['border']])
|
||||
start = ''.join(buf)
|
||||
if start:
|
||||
outfile.write(f'{{{start} ')
|
||||
outfile.write(self._escape_text(value))
|
||||
if start:
|
||||
outfile.write('}')
|
||||
start_new_line = False
|
||||
|
||||
# complete line of input
|
||||
if value.endswith("\n"):
|
||||
# close line highlighting
|
||||
if lineno in self.hl_lines:
|
||||
outfile.write('}')
|
||||
# newline in RTF file after closing }
|
||||
outfile.write("\n")
|
||||
|
||||
start_new_line = True
|
||||
lineno += 1
|
||||
|
||||
outfile.write('}\n')
|
Reference in New Issue
Block a user