Skip to content

Commit

Permalink
Merge pull request #696 from mgeier/ansi-underline-inverse
Browse files Browse the repository at this point in the history
Enable ANSI underline and inverse
  • Loading branch information
takluyver authored Feb 2, 2018
2 parents ef3218c + dab9596 commit aa20c50
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 20 deletions.
73 changes: 53 additions & 20 deletions nbconvert/filters/ansi.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,30 +74,40 @@ def ansi2latex(text):
return _ansi2anything(text, _latexconverter)


def _htmlconverter(fg, bg, bold):
def _htmlconverter(fg, bg, bold, underline, inverse):
"""
Return start and end tags for given foreground/background/bold.
Return start and end tags for given foreground/background/bold/underline.
"""
if (fg, bg, bold) == (None, None, False):
if (fg, bg, bold, underline, inverse) == (None, None, False, False, False):
return '', ''

classes = []
styles = []

if inverse:
fg, bg = bg, fg

if isinstance(fg, int):
classes.append(_ANSI_COLORS[fg] + '-fg')
elif fg:
styles.append('color: rgb({},{},{})'.format(*fg))
elif inverse:
classes.append('ansi-default-inverse-fg')

if isinstance(bg, int):
classes.append(_ANSI_COLORS[bg] + '-bg')
elif bg:
styles.append('background-color: rgb({},{},{})'.format(*bg))
elif inverse:
classes.append('ansi-default-inverse-bg')

if bold:
classes.append('ansi-bold')

if underline:
classes.append('ansi-underline')

starttag = '<span'
if classes:
starttag += ' class="' + ' '.join(classes) + '"'
Expand All @@ -107,16 +117,19 @@ def _htmlconverter(fg, bg, bold):
return starttag, '</span>'


def _latexconverter(fg, bg, bold):
def _latexconverter(fg, bg, bold, underline, inverse):
"""
Return start and end markup given foreground/background/bold.
Return start and end markup given foreground/background/bold/underline.
"""
if (fg, bg, bold) == (None, None, False):
if (fg, bg, bold, underline, inverse) == (None, None, False, False, False):
return '', ''

starttag, endtag = '', ''

if inverse:
fg, bg = bg, fg

if isinstance(fg, int):
starttag += r'\textcolor{' + _ANSI_COLORS[fg] + '}{'
endtag = '}' + endtag
Expand All @@ -125,21 +138,33 @@ def _latexconverter(fg, bg, bold):
starttag += r'\def\tcRGB{\textcolor[RGB]}\expandafter'
starttag += r'\tcRGB\expandafter{\detokenize{%s,%s,%s}}{' % fg
endtag = '}' + endtag
elif inverse:
starttag += r'\textcolor{ansi-default-inverse-fg}{'
endtag = '}' + endtag

if isinstance(bg, int):
starttag += r'\setlength{\fboxsep}{0pt}\colorbox{'
starttag += _ANSI_COLORS[bg] + '}{'
starttag += r'\setlength{\fboxsep}{0pt}'
starttag += r'\colorbox{' + _ANSI_COLORS[bg] + '}{'
endtag = r'\strut}' + endtag
elif bg:
starttag += r'\setlength{\fboxsep}{0pt}'
# See http://tex.stackexchange.com/a/291102/13684
starttag += r'\def\cbRGB{\colorbox[RGB]}\expandafter'
starttag += r'\cbRGB\expandafter{\detokenize{%s,%s,%s}}{' % bg
endtag = r'\strut}' + endtag
elif inverse:
starttag += r'\setlength{\fboxsep}{0pt}'
starttag += r'\colorbox{ansi-default-inverse-bg}{'
endtag = r'\strut}' + endtag

if bold:
starttag += r'\textbf{'
endtag = '}' + endtag

if underline:
starttag += r'\underline{'
endtag = '}' + endtag

return starttag, endtag


Expand All @@ -150,13 +175,6 @@ def _ansi2anything(text, converter):
See https://en.wikipedia.org/wiki/ANSI_escape_code
Accepts codes like '\x1b[32m' (red) and '\x1b[1;32m' (bold, red).
The codes 1 (bold) and 5 (blinking) are selecting a bold font, code
0 and an empty code ('\x1b[m') reset colors and bold-ness.
Unlike in most terminals, "bold" doesn't change the color.
The codes 21 and 22 deselect "bold", the codes 39 and 49 deselect
the foreground and background color, respectively.
The codes 38 and 48 select the "extended" set of foreground and
background colors, respectively.
Non-color escape sequences (not ending with 'm') are filtered out.
Expand All @@ -166,6 +184,8 @@ def _ansi2anything(text, converter):
"""
fg, bg = None, None
bold = False
underline = False
inverse = False
numbers = []
out = []

Expand All @@ -174,6 +194,7 @@ def _ansi2anything(text, converter):
if m:
if m.group(2) == 'm':
try:
# Empty code is same as code 0
numbers = [int(n) if n else 0
for n in m.group(1).split(';')]
except ValueError:
Expand All @@ -185,22 +206,34 @@ def _ansi2anything(text, converter):
chunk, text = text, ''

if chunk:
if bold and fg in range(8):
fg += 8
starttag, endtag = converter(fg, bg, bold)
starttag, endtag = converter(
fg + 8 if bold and fg in range(8) else fg,
bg, bold, underline, inverse)
out.append(starttag)
out.append(chunk)
out.append(endtag)

while numbers:
n = numbers.pop(0)
if n == 0:
# Code 0 (same as empty code): reset everything
fg = bg = None
bold = False
elif n in (1, 5):
bold = underline = inverse = False
elif n == 1:
bold = True
elif n == 4:
underline = True
elif n == 5:
# Code 5: blinking
bold = True
elif n == 7:
inverse = True
elif n in (21, 22):
bold = False
elif n == 24:
underline = False
elif n == 27:
inverse = False
elif 30 <= n <= 37:
fg = n - 30
elif n == 38:
Expand Down
6 changes: 6 additions & 0 deletions nbconvert/filters/tests/test_ansi.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ def test_ansi2html(self):
'hel\x1b[0;32mlo': 'hel<span class="ansi-green-fg">lo</span>',
'hellø': 'hellø',
'\x1b[1mhello\x1b[33mworld\x1b[0m': '<span class="ansi-bold">hello</span><span class="ansi-yellow-intense-fg ansi-bold">world</span>',
'he\x1b[4mll\x1b[24mo': 'he<span class="ansi-underline">ll</span>o',
'\x1b[35mhe\x1b[7mll\x1b[27mo': '<span class="ansi-magenta-fg">he</span><span class="ansi-default-inverse-fg ansi-magenta-bg">ll</span><span class="ansi-magenta-fg">o</span>',
'\x1b[44mhe\x1b[7mll\x1b[27mo': '<span class="ansi-blue-bg">he</span><span class="ansi-blue-fg ansi-default-inverse-bg">ll</span><span class="ansi-blue-bg">o</span>',
}

for inval, outval in correct_outputs.items():
Expand All @@ -61,6 +64,9 @@ def test_ansi2latex(self):
'hello\x1b[01;34mthere': r'hello\textcolor{ansi-blue-intense}{\textbf{there}}',
'hello\x1b[001;34mthere': r'hello\textcolor{ansi-blue-intense}{\textbf{there}}',
'\x1b[1mhello\x1b[33mworld\x1b[0m': r'\textbf{hello}\textcolor{ansi-yellow-intense}{\textbf{world}}',
'he\x1b[4mll\x1b[24mo': 'he\\underline{ll}o',
'\x1b[35mhe\x1b[7mll\x1b[27mo': r'\textcolor{ansi-magenta}{he}\textcolor{ansi-default-inverse-fg}{\setlength{\fboxsep}{0pt}\colorbox{ansi-magenta}{ll\strut}}\textcolor{ansi-magenta}{o}',
'\x1b[44mhe\x1b[7mll\x1b[27mo': r'\setlength{\fboxsep}{0pt}\colorbox{ansi-blue}{he\strut}\textcolor{ansi-blue}{\setlength{\fboxsep}{0pt}\colorbox{ansi-default-inverse-bg}{ll\strut}}\setlength{\fboxsep}{0pt}\colorbox{ansi-blue}{o\strut}',
}

for inval, outval in correct_outputs.items():
Expand Down
2 changes: 2 additions & 0 deletions nbconvert/templates/latex/base.tplx
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ This template does not define a docclass, the inheriting class must define this.
\definecolor{ansi-cyan-intense}{HTML}{258F8F}
\definecolor{ansi-white}{HTML}{C5C1B4}
\definecolor{ansi-white-intense}{HTML}{A1A6B2}
\definecolor{ansi-default-inverse-fg}{HTML}{FFFFFF}
\definecolor{ansi-default-inverse-bg}{HTML}{000000}

% commands and environments needed by pandoc snippets
% extracted from the output of `pandoc -s`
Expand Down

0 comments on commit aa20c50

Please sign in to comment.