Skip to content

Commit

Permalink
Change color scheme for 216 colors (#8)
Browse files Browse the repository at this point in the history
  • Loading branch information
kimikage committed Oct 28, 2020
1 parent 48a97d8 commit f040d21
Show file tree
Hide file tree
Showing 5 changed files with 247 additions and 44 deletions.
54 changes: 33 additions & 21 deletions src/AnsiColoredPrinters.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ import Base: show, showable

export HTMLPrinter

struct SGRColor
class::String
hex::String
SGRColor(class::AbstractString="", hex::AbstractString="") = new(class, hex)
end

mutable struct SGRContext
fg::String
fghex::String
bg::String
bghex::String
fg::SGRColor
bg::SGRColor
flags::BitVector
SGRContext() = new("", "", "", "", falses(128))
SGRContext() = new(SGRColor(), SGRColor(), falses(128))
end

abstract type AbstractPrinter end
Expand All @@ -19,8 +23,8 @@ include("colors.jl")
include("html.jl")

function reset_color(ctx::SGRContext)
ctx.fg, ctx.fghex = "", ""
ctx.bg, ctx.bghex = "", ""
ctx.fg = SGRColor()
ctx.bg = SGRColor()
end

function reset(ctx::SGRContext)
Expand All @@ -37,16 +41,26 @@ function reset(printer::AbstractPrinter)
reset(printer.prevctx)
end

function copy!(dest::SGRContext, src::SGRContext)
dest.fg = src.fg
dest.bg = src.bg
dest.flags .= src.flags
end

escape_char(printer::AbstractPrinter, c::UInt8) = nothing

function show_body(io::IO, printer::AbstractPrinter)
reset(printer)
buf = printer.buf

ctx_changed = false
while !eof(buf)
c = read(buf, UInt8)
if c !== UInt8('\e')
apply_changes(io, printer)
if ctx_changed
apply_changes(io, printer)
copy!(printer.prevctx, printer.ctx)
ctx_changed = false
end
ec = escape_char(printer, c)
write(io, ec === nothing ? c : ec)
continue
Expand All @@ -70,6 +84,7 @@ function show_body(io::IO, printer::AbstractPrinter)
isempty(astr) && break
end
end
ctx_changed = printer.prevctx != printer.ctx
end

while !isempty(printer.stack) # force closing
Expand All @@ -89,9 +104,9 @@ function parse_sgrcodes(ctx::SGRContext, astr::AbstractString)
ctx.flags[di] = false
ctx.flags[di + (di === 5)] = false
elseif (m = match(r"^39;?", astr)) !== nothing
ctx.fg = ""
ctx.fg = SGRColor()
elseif (m = match(r"^49;?", astr)) !== nothing
ctx.bg = ""
ctx.bg = SGRColor()
elseif (m = match(r"^([349][0-7]|10[0-7]);?", astr)) !== nothing
set_16colors!(ctx, m.captures[1])
elseif (m = match(r"^([345]8);5;(\d{0,3});?", astr)) !== nothing
Expand Down Expand Up @@ -128,21 +143,18 @@ function apply_changes(io::IO, printer::HTMLPrinter)

for di = 1:9
if prevctx.flags[di] != ctx.flags[di]
class = "sgr$di"
class = string(di)
marks .|= map(c -> c == class, stack)
ctx.flags[di] && push!(nstack, class)
end
end
prevctx.flags .= ctx.flags
if prevctx.fg != ctx.fg || prevctx.fghex != ctx.fghex || invert
prevctx.fg, prevctx.fghex = ctx.fg, ctx.fghex
marks .|= map(c -> occursin(r"^sgr(?:3[0-7]|9[0-7]|38_[25])$", c), stack)
isempty(ctx.fg) || push!(nstack, ctx.fg)
if prevctx.fg != ctx.fg || invert
marks .|= map(c -> occursin(r"^(?:3[0-7]|9[0-7]|38_[25])$", c), stack)
isnormal(ctx.fg) || push!(nstack, ctx.fg.class)
end
if prevctx.bg != ctx.bg || prevctx.bghex != ctx.bghex || invert
prevctx.bg, prevctx.bghex = ctx.bg, ctx.bghex
marks .|= map(c -> occursin(r"^sgr(?:4[0-7]|10[0-7]|48_[25])$", c), stack)
isempty(ctx.bg) || push!(nstack, ctx.bg)
if prevctx.bg != ctx.bg || invert
marks .|= map(c -> occursin(r"^(?:4[0-7]|10[0-7]|48_[25])$", c), stack)
isnormal(ctx.bg) || push!(nstack, ctx.bg.class)
end
poplevel = findfirst(marks)
if poplevel !== nothing
Expand Down
79 changes: 61 additions & 18 deletions src/colors.jl
Original file line number Diff line number Diff line change
@@ -1,46 +1,89 @@

isnormal(c::SGRColor) = isempty(c.class)

is216color(c::SGRColor) = is216color(c.hex)

function is216color(hex::AbstractString)
hex == "000" || hex == "fff" || occursin(r"(?:00|5f|87|af|d7|ff){3}$", hex)
end

function codes(c::SGRColor)
m = match(r"^([345]8)_([25])$", c.class)
m === nothing && return (parse(Int, c.class),)
code, sub = m.captures
codei = parse(Int, code)
if sub == "5"
if is216color(c)
h = parse(UInt32, c.hex, base=16)
h === 0x00000fff && return (codei, 5, 231)
r = (h >> 0x10) % UInt8 ÷ 0x30
g = (h >> 0x08) % UInt8 ÷ 0x30
b = (h >> 0x00) % UInt8 ÷ 0x30
return (codei, 5, (r * 0x24 + g * 0x6 + b) + 16)
else
h = parse(UInt8, c.hex[1:2], base=16)
g = (h - 0x8) ÷ 0xa
return (codei, 5, g + 232)
end
else
if length(c.hex) == 3
h = parse(UInt16, c.hex, base=16)
r = (h >> 0x8)
g = (h >> 0x4) & 0xf
b = h & 0xf
return (codei, 2, r * 17, g * 17, b * 17)
else
h = parse(UInt32, c.hex, base=16)
r = (h >> 0x10)
g = (h >> 0x8) & 0xff
b = h & 0xff
return (codei, 2, Int(r), Int(g), Int(b))
end
end
end

function short_hex(r::UInt8, g::UInt8, b::UInt8)
rgb6 = UInt32(r) << 0x10 + UInt32(g) << 0x8 + b
rgb6 === (rgb6 & 0x0f0f0f) * 0x11 || return string(rgb6, pad=6, base=16)
string(UInt16(r >> 0x4) << 0x8 + UInt16(g >> 0x4) << 0x4 + b >> 0x4, pad=3, base=16)
end

function set_16colors!(ctx::SGRContext, d::AbstractString)
class = "sgr" * d
if d[1] === '3' || d[1] === '9'
ctx.fg, ctx.fghex = class, ""
ctx.fg = SGRColor(d)
else
ctx.bg, ctx.bghex = class, ""
ctx.bg = SGRColor(d)
end
end

const SCALE_216 = UInt8[0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]

function set_256colors!(ctx::SGRContext, d::AbstractString, color::AbstractString)
fore = d[1] === '3'
class = "sgr"
hex = ""
colorid = isempty(color) ? 0x0 : parse(UInt8, color)
if colorid < 0x8
class *= d[1] * string(colorid)
class = d[1] * string(colorid)
elseif colorid < 0x10
class *= (fore ? "9" : "10") * string(colorid - 0x8)
class = (fore ? "9" : "10") * string(colorid - 0x8)
else
if colorid < 0xe8
c = colorid - 0x10
r = c ÷ 0x24
g = c ÷ 0x6 % 0x6
b = c % 0x6
hex = string(r * 0x300 + g * 0x30 + b * 0x3, pad=3, base=16)
r = SCALE_216[c ÷ 0x24 + 1]
g = SCALE_216[c ÷ 0x6 % 0x6 + 1]
b = SCALE_216[c % 0x6 + 1]
hex = short_hex(r, g, b)
else
g = (colorid - 0xe8) * 0xa + 0x8
hex = short_hex(g, g, g)
end
class *= d * "_5"
class = d * "_5"
end

if fore
ctx.fg, ctx.fghex = class, hex
ctx.fg = SGRColor(class, hex)
else
ctx.bg, ctx.bghex = class, hex
ctx.bg = SGRColor(class, hex)
end
end

Expand All @@ -50,14 +93,14 @@ function set_24bitcolors!(ctx::SGRContext, d::AbstractString,
g8 = isempty(g) ? 0x0 : parse(UInt8, g)
b8 = isempty(b) ? 0x0 : parse(UInt8, b)
hex = short_hex(r8, g8, b8)
if occursin(r"^[0369cf]{3}$", hex) || (r8 === g8 === b8 && (r8 - 8) % 10 == 0)
class = "sgr" * d * "_5"
if is216color(hex) || (r8 === g8 === b8 && (r8 - 8) % 10 == 0)
class = d * "_5"
else
class = "sgr" * d * "_2"
class = d * "_2"
end
if d == "38"
ctx.fg, ctx.fghex = class, hex
ctx.fg = SGRColor(class, hex)
else
ctx.bg, ctx.bghex = class, hex
ctx.bg = SGRColor(class, hex)
end
end
10 changes: 5 additions & 5 deletions src/html.jl
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ end
function start_new_state(io::IO, printer::HTMLPrinter)
class = printer.stack[end]
ctx = printer.ctx
if occursin(r"^sgr38_[25]$", class)
write(io, "<span class=\"", class, "\" style=\"color:#", ctx.fghex, "\">")
elseif occursin(r"^sgr48_[25]$", class)
write(io, "<span class=\"", class, "\" style=\"background:#", ctx.bghex, "\">")
if occursin(r"^38_[25]$", class)
write(io, "<span class=\"sgr", class, "\" style=\"color:#", ctx.fg.hex, "\">")
elseif occursin(r"^48_[25]$", class)
write(io, "<span class=\"sgr", class, "\" style=\"background:#", ctx.bg.hex, "\">")
else
write(io, "<span class=\"", class, "\">")
write(io, "<span class=\"sgr", class, "\">")
end
end

Expand Down
145 changes: 145 additions & 0 deletions test/colors.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
using Test, AnsiColoredPrinters

@testset "isnormal" begin
n = AnsiColoredPrinters.SGRColor()
@test AnsiColoredPrinters.isnormal(n)

c16 = AnsiColoredPrinters.SGRColor("30")
@test !AnsiColoredPrinters.isnormal(c16)

c256 = AnsiColoredPrinters.SGRColor("38_5", "000")
@test !AnsiColoredPrinters.isnormal(c256)
end

@testset "is216color" begin
c000 = AnsiColoredPrinters.SGRColor("38_5", "000")
@test AnsiColoredPrinters.is216color(c000)

c080808 = AnsiColoredPrinters.SGRColor("48_5", "080808")
@test !AnsiColoredPrinters.is216color(c080808)

cabc = AnsiColoredPrinters.SGRColor("38_2", "abc")
@test !AnsiColoredPrinters.is216color(cabc)
end

@testset "set_16colors!" begin
ctx = AnsiColoredPrinters.SGRContext()

AnsiColoredPrinters.set_16colors!(ctx, "30")
@test ctx.fg.class == "30"
@test isempty(ctx.fg.hex)
@test AnsiColoredPrinters.codes(ctx.fg) === (30,)

AnsiColoredPrinters.set_16colors!(ctx, "47")
@test ctx.bg.class == "47"
@test isempty(ctx.bg.hex)
@test AnsiColoredPrinters.codes(ctx.bg) === (47,)

AnsiColoredPrinters.set_16colors!(ctx, "97")
@test ctx.fg.class == "97"
@test isempty(ctx.fg.hex)
@test AnsiColoredPrinters.codes(ctx.fg) === (97,)

AnsiColoredPrinters.set_16colors!(ctx, "100")
@test ctx.bg.class == "100"
@test isempty(ctx.bg.hex)
@test AnsiColoredPrinters.codes(ctx.bg) === (100,)
end

@testset "set_256colors!" begin
ctx = AnsiColoredPrinters.SGRContext()

# 16 colors
AnsiColoredPrinters.set_256colors!(ctx, "38", "1")
@test ctx.fg.class == "31"
@test isempty(ctx.fg.hex)
@test AnsiColoredPrinters.codes(ctx.fg) === (31,)

AnsiColoredPrinters.set_256colors!(ctx, "48", "6")
@test ctx.bg.class == "46"
@test isempty(ctx.bg.hex)
@test AnsiColoredPrinters.codes(ctx.bg) === (46,)

AnsiColoredPrinters.set_256colors!(ctx, "38", "15")
@test ctx.fg.class == "97"
@test isempty(ctx.fg.hex)
@test AnsiColoredPrinters.codes(ctx.fg) === (97,)

AnsiColoredPrinters.set_256colors!(ctx, "48", "8")
@test ctx.bg.class == "100"
@test isempty(ctx.bg.hex)
@test AnsiColoredPrinters.codes(ctx.bg) === (100,)

# 216 colors (6 * 6 * 6)
AnsiColoredPrinters.set_256colors!(ctx, "38", "16")
@test ctx.fg.class == "38_5"
@test ctx.fg.hex == "000"
@test AnsiColoredPrinters.codes(ctx.fg) === (38, 5, 16)

AnsiColoredPrinters.set_256colors!(ctx, "48", "17")
@test ctx.bg.class == "48_5"
@test ctx.bg.hex == "00005f"
@test AnsiColoredPrinters.codes(ctx.bg) === (48, 5, 17)

AnsiColoredPrinters.set_256colors!(ctx, "38", "110")
@test ctx.fg.class == "38_5"
@test ctx.fg.hex == "87afd7"
@test AnsiColoredPrinters.codes(ctx.fg) === (38, 5, 110)

AnsiColoredPrinters.set_256colors!(ctx, "38", "230")
@test ctx.fg.class == "38_5"
@test ctx.fg.hex == "ffffd7"
@test AnsiColoredPrinters.codes(ctx.fg) === (38, 5, 230)

AnsiColoredPrinters.set_256colors!(ctx, "48", "231")
@test ctx.bg.class == "48_5"
@test ctx.bg.hex == "fff"
@test AnsiColoredPrinters.codes(ctx.bg) === (48, 5, 231)

# grays
AnsiColoredPrinters.set_256colors!(ctx, "38", "232")
@test ctx.fg.class == "38_5"
@test ctx.fg.hex == "080808"
@test AnsiColoredPrinters.codes(ctx.fg) === (38, 5, 232)

AnsiColoredPrinters.set_256colors!(ctx, "48", "255")
@test ctx.bg.class == "48_5"
@test ctx.bg.hex == "eee"
@test AnsiColoredPrinters.codes(ctx.bg) === (48, 5, 255)
end

@testset "set_24bitcolors" begin
ctx = AnsiColoredPrinters.SGRContext()

AnsiColoredPrinters.set_24bitcolors!(ctx, "38", "0", "128", "255")
@test ctx.fg.class == "38_2"
@test ctx.fg.hex == "0080ff"
@test AnsiColoredPrinters.codes(ctx.fg) === (38, 2, 0, 128, 255)

AnsiColoredPrinters.set_24bitcolors!(ctx, "48", "170", "187", "204")
@test ctx.bg.class == "48_2"
@test ctx.bg.hex == "abc"
@test AnsiColoredPrinters.codes(ctx.bg) === (48, 2, 170, 187, 204)

# 216 colors
AnsiColoredPrinters.set_24bitcolors!(ctx, "38", "0", "0", "0")
@test ctx.fg.class == "38_5"
@test ctx.fg.hex == "000"
@test AnsiColoredPrinters.codes(ctx.fg) === (38, 5, 16)

AnsiColoredPrinters.set_24bitcolors!(ctx, "48", "0", "0", "95")
@test ctx.bg.class == "48_5"
@test ctx.bg.hex == "00005f"
@test AnsiColoredPrinters.codes(ctx.bg) === (48, 5, 17)

# grays
AnsiColoredPrinters.set_24bitcolors!(ctx, "38", "8", "8", "8")
@test ctx.fg.class == "38_5"
@test ctx.fg.hex == "080808"
@test AnsiColoredPrinters.codes(ctx.fg) === (38, 5, 232)

AnsiColoredPrinters.set_24bitcolors!(ctx, "48", "238", "238", "238")
@test ctx.bg.class == "48_5"
@test ctx.bg.hex == "eee"
@test AnsiColoredPrinters.codes(ctx.bg) === (48, 5, 255)
end

0 comments on commit f040d21

Please sign in to comment.