Skip to content

Commit

Permalink
add support for vt100 box drawing mode and high-intensity ansi codes
Browse files Browse the repository at this point in the history
  • Loading branch information
Paul Sherratt committed Oct 18, 2018
1 parent f591f92 commit bf327d2
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 8 deletions.
54 changes: 51 additions & 3 deletions ansi2html/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,24 @@
ANSI_BACKGROUND_DEFAULT = 49
ANSI_NEGATIVE_ON = 7
ANSI_NEGATIVE_OFF = 27

ANSI_FOREGROUND_HIGH_INTENSITY_MIN = 90
ANSI_FOREGROUND_HIGH_INTENSITY_MAX = 97
ANSI_BACKGROUND_HIGH_INTENSITY_MIN = 100
ANSI_BACKGROUND_HIGH_INTENSITY_MAX = 107

VT100_BOX_CODES = {
'0x71': '─',
'0x74': '├',
'0x75': '┤',
'0x76': '┴',
'0x77': '┬',
'0x78': '│',
'0x6a': '┘',
'0x6b': '┐',
'0x6c': '┌',
'0x6d': '└',
'0x6e': '┼'
}

# http://stackoverflow.com/a/15190498
_latex_template = '''\\documentclass{scrartcl}
Expand Down Expand Up @@ -129,12 +146,16 @@ def adjust(self, ansi_code, parameter=None):
self.visibility = ansi_code
elif ANSI_FOREGROUND_CUSTOM_MIN <= ansi_code <= ANSI_FOREGROUND_CUSTOM_MAX:
self.foreground = (ansi_code, None)
elif ANSI_FOREGROUND_HIGH_INTENSITY_MIN <= ansi_code <= ANSI_FOREGROUND_HIGH_INTENSITY_MAX:
self.foreground = (ansi_code, None)
elif ansi_code == ANSI_FOREGROUND_256:
self.foreground = (ansi_code, parameter)
elif ansi_code == ANSI_FOREGROUND_DEFAULT:
self.foreground = (ansi_code, None)
elif ANSI_BACKGROUND_CUSTOM_MIN <= ansi_code <= ANSI_BACKGROUND_CUSTOM_MAX:
self.background = (ansi_code, None)
elif ANSI_BACKGROUND_HIGH_INTENSITY_MIN <= ansi_code <= ANSI_BACKGROUND_HIGH_INTENSITY_MAX:
self.background = (ansi_code, None)
elif ansi_code == ANSI_BACKGROUND_256:
self.background = (ansi_code, parameter)
elif ansi_code == ANSI_BACKGROUND_DEFAULT:
Expand Down Expand Up @@ -185,6 +206,9 @@ def linkify(line, latex_mode):
else:
return url_matcher.sub(r'<a href="\1">\1</a>', line)

def map_vt100_box_code(char):
char_hex = hex(ord(char))
return VT100_BOX_CODES[char_hex] if char_hex in VT100_BOX_CODES else char

def _needs_extra_newline(text):
if not text or text.endswith('\n'):
Expand All @@ -209,6 +233,7 @@ def __init__(self,
latex=False,
inline=False,
dark_bg=True,
line_wrap=True,
font_size='normal',
linkify=False,
escaped=True,
Expand All @@ -221,6 +246,7 @@ def __init__(self,
self.latex = latex
self.inline = inline
self.dark_bg = dark_bg
self.line_wrap = line_wrap
self.font_size = font_size
self.linkify = linkify
self.escaped = escaped
Expand All @@ -231,8 +257,9 @@ def __init__(self,
self._attrs = None

if inline:
self.styles = dict([(item.klass.strip('.'), item) for item in get_styles(self.dark_bg, self.scheme)])
self.styles = dict([(item.klass.strip('.'), item) for item in get_styles(self.dark_bg, self.line_wrap, self.scheme)])

self.vt100_box_codes_prog = re.compile('\033\\(([B0])')
self.ansi_codes_prog = re.compile('\033\\[' '([\\d;]*)' '([a-zA-z])')

def apply_regex(self, ansi):
Expand Down Expand Up @@ -268,6 +295,21 @@ def _apply_regex(self, ansi, styles_used):
for pattern, special in specials.items():
ansi = ansi.replace(pattern, special)

def _vt100_box_drawing():
last_end = 0 # the index of the last end of a code we've seen
box_drawing_mode = False
for match in self.vt100_box_codes_prog.finditer(ansi):
trailer = ansi[last_end:match.start()]
if box_drawing_mode:
for char in trailer:
yield map_vt100_box_code(char)
else:
yield trailer
last_end = match.end()
box_drawing_mode = (match.groups()[0] == "0")
yield ansi[last_end:]
ansi = "".join(_vt100_box_drawing())

state = _State()
inside_span = False
last_end = 0 # the index of the last end of a code we've seen
Expand Down Expand Up @@ -403,6 +445,7 @@ def prepare(self, ansi='', ensure_trailing_newline=False):

self._attrs = {
'dark_bg': self.dark_bg,
'line_wrap': self.line_wrap,
'font_size': self.font_size,
'body': body,
'styles': styles,
Expand All @@ -425,7 +468,7 @@ def convert(self, ansi, full=True, ensure_trailing_newline=False):
_template = _latex_template
else:
_template = _html_template
all_styles = get_styles(self.dark_bg, self.scheme)
all_styles = get_styles(self.dark_bg, self.line_wrap, self.scheme)
backgrounds = all_styles[:6]
used_styles = filter(lambda e: e.klass.lstrip(".") in attrs["styles"], all_styles)

Expand Down Expand Up @@ -479,6 +522,10 @@ def main():
"-l", '--light-background', dest='light_background',
default=False, action="store_true",
help="Set output to 'light background' mode.")
parser.add_option(
"-W", '--no-line-wrap', dest='no_line_wrap',
default=False, action="store_true",
help="Disable line wrapping.")
parser.add_option(
"-a", '--linkify', dest='linkify',
default=False, action="store_true",
Expand Down Expand Up @@ -515,6 +562,7 @@ def main():
latex=opts.latex,
inline=opts.inline,
dark_bg=not opts.light_background,
line_wrap=not opts.no_line_wrap,
font_size=opts.font_size,
linkify=opts.linkify,
escaped=opts.escaped,
Expand Down
9 changes: 4 additions & 5 deletions ansi2html/style.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,9 @@ def index2(grey):
}


def get_styles(dark_bg=True, scheme='ansi2html'):

def get_styles(dark_bg=True, line_wrap=True, scheme='ansi2html'):
css = [
Rule('.ansi2html-content', white_space='pre-wrap', word_wrap='break-word', display='inline'),
Rule('.ansi2html-content', white_space=('pre', 'pre-wrap')[line_wrap], word_wrap='break-word', display='inline'),
Rule('.body_foreground', color=('#000000', '#AAAAAA')[dark_bg]),
Rule('.body_background', background_color=('#AAAAAA', '#000000')[dark_bg]),
Rule('.body_foreground > .bold,.bold > .body_foreground, body.body_foreground > pre > .bold',
Expand All @@ -105,16 +104,16 @@ def get_styles(dark_bg=True, scheme='ansi2html'):
for _index in range(8):
css.append(Rule('.ansi3%s' % _index, color=pal[_index]))
css.append(Rule('.inv3%s' % _index, background_color=pal[_index]))
for _index in range(8):
css.append(Rule('.ansi4%s' % _index, background_color=pal[_index]))
css.append(Rule('.inv4%s' % _index, color=pal[_index]))
css.append(Rule('.ansi9%s' % _index, color=pal[_index]))
css.append(Rule('.ansi10%s' % _index, background_color=pal[_index]))

# set palette colors in 256 color encoding
pal = SCHEME[scheme]
for _index in range(len(pal)):
css.append(Rule('.ansi38-%s' % _index, color=pal[_index]))
css.append(Rule('.inv38-%s' % _index, background_color=pal[_index]))
for _index in range(len(pal)):
css.append(Rule('.ansi48-%s' % _index, background_color=pal[_index]))
css.append(Rule('.inv48-%s' % _index, color=pal[_index]))

Expand Down

0 comments on commit bf327d2

Please sign in to comment.