JuliaKurs23/_extensions/MHellmund/julia-color/ansi2htmltex.lua

354 lines
11 KiB
Lua

-- based on
-- https://github.com/jupyter/nbconvert/blob/main/nbconvert/filters/ansi.py
local ANSI_COLORS = {
"ansi-black",
"ansi-red",
"ansi-green",
"ansi-yellow",
"ansi-blue",
"ansi-magenta",
"ansi-cyan",
"ansi-white",
"ansi-black-intense",
"ansi-red-intense",
"ansi-green-intense",
"ansi-yellow-intense",
"ansi-blue-intense",
"ansi-magenta-intense",
"ansi-cyan-intense",
"ansi-white-intense"
}
local function get_extended_color(numbers)
local n = table.remove(numbers, 1)
local r,g,b,idx
if n == 2 and #numbers >=3 then
-- 24bit RGB
r = table.remove(numbers, 1)
g = table.remove(numbers, 1)
b = table.remove(numbers, 1)
elseif n == 5 and #numbers >= 1 then
-- 256 colors
idx = table.remove(numbers, 1)
if idx < 16 then
-- 16 default terminal colors
return idx
elseif idx < 232 then
-- 6x6x6 color cube, see http://stackoverflow.com/a/27165165/500098
r = (idx - 16) // 36
r = 55 + r * 40
if r < 0 then r = 0 end
g = ((idx - 16) % 36) // 6
g = 55 + g * 40
if g < 0 then g = 0 end
b = (idx - 16) % 6
b = 55 + b * 40
if b < 0 then b = 0 end
elseif idx < 256 then
-- grayscale, see http://stackoverflow.com/a/27165165/500098
r = (idx - 232) * 10 + 8
g = r
b = r
end
end
return {r, g, b}
end
local function LaTeXconverter(fg, bg, bold, light, italic, underline, inverse)
if not (fg or bg or bold or light or italic or underline or inverse) then
return "",""
end
local starttag = ""
local endtag = ""
if inverse then
fg, bg = bg, fg
end
if type(fg) == "number" then
starttag = starttag .. [[\textcolor{]] .. ANSI_COLORS[fg+1] .. "}{"
endtag = "}" .. endtag
elseif type(fg) == "table" then
-- See http://tex.stackexchange.com/a/291102/13684
starttag = starttag .. [[\def\tcRGB{\textcolor[RGB]}\expandafter]]
starttag = starttag .. string.format([[\tcRGB\expandafter{\detokenize{%d,%d,%d}}{]], fg[1], fg[2], fg[3])
endtag = "}" .. endtag
elseif inverse then
starttag = starttag .. [[\textcolor{ansi-default-inverse-fg}{]]
endtag = "}" .. endtag
end
if type(bg) == "number" then
starttag = starttag .. [[\setlength{\fboxsep}{0pt}]]
starttag = starttag .. [[\colorbox{]] .. ANSI_COLORS[bg+1] .. "}{"
endtag = [[\strut}]] .. endtag
elseif type(bg) == "table" then
-- See http://tex.stackexchange.com/a/291102/13684
starttag = starttag .. [[\setlength{\fboxsep}{0pt}]]
starttag = starttag .. [[\def\cbRGB{\colorbox[RGB]}\expandafter]]
starttag = starttag .. string.format([[\cbRGB\expandafter{\detokenize{%d,%d,%d}}{]], bg[1], bg[2], bg[3])
endtag = [[\strut}]] .. endtag
elseif inverse then
starttag = starttag .. [[\setlength{\fboxsep}{0pt}]]
starttag = starttag .. [[\colorbox{ansi-default-inverse-bg}{]]
endtag = [[\strut}]] .. endtag
end
if bold then
starttag = starttag .. [[\textbf{]]
endtag = "}" .. endtag
end
if light then
starttag = starttag .. [[\textlight{]]
endtag = "}" .. endtag
end
if italic then
starttag = starttag .. [[\textit{]]
endtag = "}" .. endtag
end
if underline then
starttag = starttag .. [[\underline{]]
endtag = "}" .. endtag
end
return starttag, endtag
end
local function HTMLconverter(fg, bg, bold, light, italic, underline, inverse)
if not (fg or bg or bold or light or italic or underline or inverse) then
return "",""
end
local classes = {}
local styles = {}
local type = type -- more efficient?
local next = next
if inverse then
fg, bg = bg, fg
end
if type(fg) == "number" then
table.insert(classes, ANSI_COLORS[fg+1] .. "-fg")
elseif type(fg) == "table" then
table.insert(styles, string.format("color: rgb(%d,%d,%d)", fg[1], fg[2], fg[3]))
elseif inverse then
table.insert(classes, "ansi-default-inverse-fg")
end
if type(bg) == "number" then
table.insert(classes, ANSI_COLORS[bg+1] .. "-bg")
elseif type(bg) == "table" then
table.insert(styles, string.format("background-color: rgb(%d,%d,%d)",
bg[1], bg[2], bg[3]))
elseif inverse then
table.insert(classes, "ansi-default-inverse-bg")
end
if bold then
table.insert(classes, "ansi-bold")
end
if light then
table.insert(classes, "ansi-light")
end
if italic then
table.insert(classes, "ansi-italic")
end
if underline then
table.insert(classes, "ansi-underline")
end
local starttag = "<span"
if next(classes) ~= nil then
starttag = starttag .. ' class="' .. table.concat(classes, " ") .. '"'
end
if next(styles) ~= nil then
starttag = starttag .. ' style="' .. table.concat(styles, " ") .. '"'
end
return starttag..">","</span>"
end
local function codeBlockTrans(e)
local converter, fmt
if quarto.doc.isFormat('latex') then
converter = LaTeXconverter
fmt = 'latex'
elseif quarto.doc.isFormat('html') then
converter = HTMLconverter
fmt = 'html'
else
return
end
-- not for input cells
if e.classes:includes("julia") or e.classes:includes("cell-code") then
return
end
if #e.classes > 0 and not e.classes:includes("julia-stderr") then
return
end
local texenv="OutputCell"
local codeclass=""
-- if string.find(e.text, "\u{a35f}\u{2983}") then
if string.find(e.text, "\x1b%[") then
texenv = "AnsiOutputCell"
codeclass = "ansi"
end
if e.classes:includes("julia-stderr") then
texenv = "StderrOutputCell"
codeclass = codeclass .. " julia-stderr" -- empty leading space doesn't matter
end
local out=""
local text = e.text
-- we remove links (eg in julia ParseErrors. THey link to local files, so they are useless anyway)
text = text:gsub("\x1b%]8;.-\x1b\\", "")
if string.find(text, "\x1b%[") then
-- if string.find(text, "\u{a35f}\u{2983}") then
local bold = false
local light = false
local italic = false
local underline = false
local inverse = false
local chunk = ""
local fg = nil
local bg = nil
local starttag = ""
local endtag = ""
local numbers={}
while text ~= "" do
numbers = {}
-- [@-~]: matches single char between 64 and 126
local s1, e1, c1, d1 = string.find(text, "\x1b%[(.-)([@-~])")
-- local s1, e1, c1, d1 = string.find(text, "\u{a35f}\u{2983}(.-)([@-~])")
if s1 then
if d1 == "m" then
for i in string.gmatch(c1, "([^;]*)") do
table.insert(numbers, tonumber(i))
end
else
quarto.log.warning("Unsupported ANSI sequence ESC["..c1..d1.." ignored\n" )
end
chunk, text = text:sub(1, s1-1), text:sub(e1+1)
else
chunk, text = text, ""
end
if chunk ~= "" then
if bold and type(fg)=="number" and fg<8 then
starttag, endtag = converter(fg+8, bg, bold, light, italic, underline, inverse)
else
starttag, endtag = converter(fg, bg, bold, light, italic, underline, inverse)
end
out = out .. starttag .. chunk .. endtag
end
while next(numbers) ~= nil do
local n = table.remove(numbers, 1)
if n == 0 then
fg = nil
bg = nil
bold = false
inverse = false
underline = false
elseif n == 1 or n == 5 or n == 6 then -- bold and blink
bold = true
elseif n == 2 or n == 8 then -- 'dim' and 'hide'
light = true
elseif n == 3 then
italic = true
elseif n == 4 then
underline = true
elseif n == 7 then
inverse = true
elseif n == 21 or n == 22 or n == 25 then
bold = false
elseif n == 23 then
italic = false
elseif n == 24 then
underline = false
elseif n == 27 then
inverse = false
elseif n == 22 or n == 28 then
light = false
elseif n >= 30 and n <= 37 then
fg = n - 30
elseif n == 38 then
fg = get_extended_color(numbers)
elseif n == 39 then
fg = nil
elseif n >= 40 and n <= 47 then
bg = n - 40
elseif n == 48 then
bg = get_extended_color(numbers)
elseif n == 49 then
bg = nil
elseif n >= 90 and n <= 97 then
fg = n + 8 - 90
elseif n >= 100 and n <= 107 then
bg = n + 8 - 100
else
quarto.log.warning(string.format("ESC sequence with unknown code %d before:\n",n))
quarto.log.warning(chunk.."\n")
end
end
end
else
out = text
end
if fmt == 'html' then
return pandoc.RawBlock(fmt,
'<pre class="' .. codeclass ..'"><code class="' .. codeclass .. ' ansi">'..out..'</code></pre>')
end
if fmt == 'latex' then
return pandoc.RawBlock(fmt, [[\begin{]]..texenv.."}\n"..out.."\n"..[[\end{]].. texenv .. "}")
end
end
-- if div has class 'cell-output-stderr', give CodeBlocks in this div the class 'julia-stderr'
local function divStderr(e)
if e.classes:includes("cell-output-stderr") then
local c = e.content
for i,el in pairs(c) do
if el.t == 'CodeBlock' then
el.classes:insert("julia-stderr")
end
end
return e
end
end
-- repair julia ? output
local function divCodeBlockNoHeader1(e)
if not e.classes:includes("cell-output") then
return
end
local c = e.content
for i, el in pairs(c) do
if el.t == 'Header' then
el.level = 6
end
end
return e
end
return {
{Div = divStderr},
{Div = divCodeBlockNoHeader1},
{CodeBlock = codeBlockTrans},
}