Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Crayons -> StyledStrings #398

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ version = "3.7.1"
ColorSchemes = "35d6a980-a343-548e-a6ea-1d62b119f2f4"
ColorTypes = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
Contour = "d38c429a-6771-53c6-b99e-75d170b6e991"
Crayons = "a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f"
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
MarchingCubes = "299715c1-40a9-479a-aaf9-4a633d36f717"
Expand All @@ -19,6 +18,7 @@ Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
StyledStrings = "f489334b-da3d-4c2e-b8f0-e476e12c162b"

[weakdeps]
FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
Expand All @@ -39,7 +39,6 @@ UnitfulExt = "Unitful"
ColorSchemes = "^3.19"
ColorTypes = "0.11 - 0.12"
Contour = "0.5 - 0.6"
Crayons = "^4.1"
Dates = "1"
FileIO = "1"
FreeType = "4"
Expand All @@ -53,6 +52,7 @@ Printf = "1"
SparseArrays = "1"
StaticArrays = "1"
StatsBase = "0.33 - 0.34"
StyledStrings = "1"
Term = "2"
Unitful = "1"
julia = "1.10"
Expand Down
2 changes: 1 addition & 1 deletion src/UnicodePlots.jl
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
module UnicodePlots

using PrecompileTools
using StyledStrings
using LinearAlgebra
using StaticArrays
using ColorTypes
using Crayons
using Printf
using Dates

Expand Down
150 changes: 75 additions & 75 deletions src/common.jl
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,8 @@ const SUPERSCRIPT = Dict(
############################################################################################
# define types
const MarkerType = Union{Symbol,AbstractChar,AbstractString}
const CrayonColorType = Union{Integer,Symbol,NTuple{3,Integer},Nothing}
const UserColorType = Union{Crayon,CrayonColorType} # allowed color type
const StyledStringsColorType = Union{Symbol,StyledStrings.RGBTuple,NTuple{3,Integer},UInt32} # from StyledStrings/src/faces.jl
const UserColorType = Union{StyledStrings.Face,StyledStringsColorType,Nothing} # allowed color type
const ColorType = UInt32 # internal UnicodePlots color type (on canvas), 8bit or 24bit

############################################################################################
Expand Down Expand Up @@ -181,20 +181,17 @@ const UnicodeType = UInt32

############################################################################################
# colors
@enum Colormode COLORMODE_24BIT COLORMODE_8BIT
const THRESHOLD = UInt32(256^3) # 8bit - 24bit threshold
const COLORMODE = Ref(Crayons.COLORS_256)
const COLORMODE = Ref(COLORMODE_24BIT)
const INVALID_COLOR = typemax(ColorType)
const USE_LUT = Ref(false)

const CRAYONS_FAST = Ref(true)
const CRAYONS_EMPTY_STYLES = Tuple(Crayons.ANSIStyle() for _ ∈ 1:9)
const CRAYONS_RESET = Crayons.CSI * "0" * Crayons.END_ANSI

const COLOR_CYCLE_FAINT = :green, :blue, :red, :magenta, :yellow, :cyan
const COLOR_CYCLE_BRIGHT = map(s -> Symbol("light_", s), COLOR_CYCLE_FAINT) # COV_EXCL_LINE
const COLOR_CYCLE_BRIGHT = map(s -> Symbol("bright_", s), COLOR_CYCLE_FAINT) # COV_EXCL_LINE
const COLOR_CYCLE = Ref(COLOR_CYCLE_FAINT)

const BORDER_COLOR = Ref(:dark_gray)
const BORDER_COLOR = Ref(:gray)

############################################################################################

Expand All @@ -214,18 +211,33 @@ const ASPECT_RATIO = Ref(4 / 3)
const DEFAULT_HEIGHT = Ref(15)
const DEFAULT_WIDTH = Ref(round(Int, DEFAULT_HEIGHT[] * 2ASPECT_RATIO[]))

brightcolors!() = COLOR_CYCLE[] = COLOR_CYCLE_BRIGHT
faintcolors!() = COLOR_CYCLE[] = COLOR_CYCLE_FAINT

colors256!() = COLORMODE[] = COLORMODE_8BIT
truecolors!() = COLORMODE[] = COLORMODE_24BIT

function init_24bit()
truecolors!()
USE_LUT[] ? brightcolors!() : faintcolors!()
nothing
end

function init_8bit()
colors256!()
faintcolors!()
nothing
end

colormode() =
if (cm = COLORMODE[]) ≡ Crayons.COLORS_256
if (cm = COLORMODE[]) ≡ COLORMODE_8BIT
8
elseif cm ≡ Crayons.COLORS_24BIT
elseif cm ≡ COLORMODE_24BIT
24
else
throw(ArgumentError("color mode $cm is unsupported"))
end

colors256!() = COLORMODE[] = Crayons.COLORS_256
truecolors!() = COLORMODE[] = Crayons.COLORS_24BIT

function colormode!(mode)
if mode ≡ 8
colors256!()
Expand All @@ -237,21 +249,6 @@ function colormode!(mode)
nothing
end

brightcolors!() = COLOR_CYCLE[] = COLOR_CYCLE_BRIGHT
faintcolors!() = COLOR_CYCLE[] = COLOR_CYCLE_FAINT

function init_24bit()
truecolors!()
USE_LUT[] ? brightcolors!() : faintcolors!()
nothing
end

function init_8bit()
colors256!()
faintcolors!()
nothing
end

get_have_truecolor() =
if isdefined(Base, :get_have_truecolor)
Base.get_have_truecolor()
Expand Down Expand Up @@ -461,41 +458,28 @@ function sorted_keys_values(dict::Dict; k2s = true)
first.(keys_vals), last.(keys_vals)
end

crayon_color(::Union{Missing,Nothing}) = Crayons.ANSIColor()
crayon_color(color::ColorType) =
styledstrings_color(::Union{Missing,Nothing}) = nothing
styledstrings_color(color::ColorType) =
if color ≡ INVALID_COLOR
Crayons.ANSIColor()
nothing
elseif color < THRESHOLD # 24bit
Crayons.ANSIColor(red(color), grn(color), blu(color), Crayons.COLORS_24BIT)
StyledStrings.SimpleColor(red(color), grn(color), blu(color))
else # 8bit
Crayons.ANSIColor(color - THRESHOLD, Crayons.COLORS_256)
StyledStrings.Legacy.legacy_color(Int(color - THRESHOLD))
end

function print_crayons(io, c, args...)
if CRAYONS_FAST[]
if Crayons.anyactive(c) # bypass crayons checks (_have_color, _force_color)
print(io, Crayons.CSI)
Crayons._print(io, c)
print(io, Crayons.END_ANSI, args..., CRAYONS_RESET)
else
print(io, args...)
end
else
print(io, c, args..., CRAYONS_RESET)
end
end

print_color(io::IO, color::Crayon, args...) = print_crayons(io, color, args...)
print_color(io::IO, face::StyledStrings.Face, args...) =
print(io, StyledStrings.face!(Base.annotatedstring(args...), face))
print_color(io::IO, color::UserColorType, args...) =
print_color(io, ansi_color(color), args...)

function print_color(io::IO, color::ColorType, args...; bgcol = missing)
if get(io, :color, false)
print_crayons(
io,
Crayon(crayon_color(color), crayon_color(bgcol), CRAYONS_EMPTY_STYLES...),
args...,
face = StyledStrings.Face(
foreground = styledstrings_color(color),
background = styledstrings_color(bgcol),
)
print_color(io, face, args...)
else
print(io, args...)
end
Expand Down Expand Up @@ -544,34 +528,50 @@ c256(c::Integer) = c
# `ColorType` conversion - colormaps
ansi_color(rgb::AbstractRGB) = ansi_color((c256(rgb.r), c256(rgb.g), c256(rgb.b)))
ansi_color(rgb::NTuple{3,AbstractFloat}) = ansi_color(c256.(rgb))
ansi_color(color::NTuple{3,Integer})::ColorType =
r32(color[1]) + g32(color[2]) + b32(color[3])

ansi_color(color::ColorType)::ColorType = color # no-op
ansi_color(crayon::Crayon) = ansi_color(crayon.fg) # ignore bg & styles
ansi_color(::Missing) = INVALID_COLOR # not a CrayonColorType
ansi_color(face::StyledStrings.Face) = ansi_color(face.foreground) # ignore bg & styles
ansi_color(::Union{Nothing,Missing}) = INVALID_COLOR # not a StyledStringsColorType

function ansi_color(color::CrayonColorType)::ColorType
function ansi_color(color::StyledStringsColorType)::ColorType
ignored_color(color) && return INVALID_COLOR
ansi_color(Crayons._parse_color(color))
ansi_color(StyledStrings.SimpleColor(color))
end

ansi_color(c::Crayons.ANSIColor) = if COLORMODE[] ≡ Crayons.COLORS_24BIT
if c.style ≡ Crayons.COLORS_24BIT
r32(c.r) + g32(c.g) + b32(c.b)
elseif c.style ≡ Crayons.COLORS_256
USE_LUT[] ? LUT_8BIT[c.r + 1] : THRESHOLD + c.r
elseif c.style ≡ Crayons.COLORS_16
c8 = ansi_4bit_to_8bit(c.r)
USE_LUT[] ? LUT_8BIT[c8 + 1] : THRESHOLD + c8
end::ColorType
else # 0-255 ansi stored in a UInt32
THRESHOLD + if c.style ≡ Crayons.COLORS_24BIT
Crayons.to_256_colors(c).r
elseif c.style ≡ Crayons.COLORS_256
c.r
elseif c.style ≡ Crayons.COLORS_16
ansi_4bit_to_8bit(c.r)
end::UInt8
end::ColorType
function to_256_colors(color)
r, g, b = rgb = color.r, color.g, color.b
ansi = if r == g == b && r % 10 == 8
232 + min((r - 8) ÷ 10, 23) # gray level
elseif all(map(c -> (c & 0x1) == 0 && (c > 0 ? c == 128 || c == 192 : true), rgb))
(r >> 7) + 2(g >> 7) + 4(b >> 7) # primary color
else
r6, g6, b6 = map(c -> c < 48 ? 0 : (c < 114 ? 1 : trunc(Int, (c - 35) / 40)), rgb)
16 + 36r6 + 6g6 + b6 # cube 6x6x6
end
UInt8(ansi)
end

ansi_color(color::StyledStrings.SimpleColor)::ColorType =
let c = color.value
if COLORMODE[] ≡ COLORMODE_24BIT
if c isa Symbol
c4 = StyledStrings.ANSI_4BIT_COLORS[c]
c8 = ansi_4bit_to_8bit(UInt8(c4))
return USE_LUT[] ? LUT_8BIT[c8 + 1] : THRESHOLD + c8
elseif c isa StyledStrings.RGBTuple
return r32(c.r) + g32(c.g) + b32(c.b)
end::ColorType
else # 0-255 ansi stored in a UInt32
return THRESHOLD + if c isa Symbol
c4 = StyledStrings.ANSI_4BIT_COLORS[c]
ansi_4bit_to_8bit(UInt8(c4))
elseif c isa StyledStrings.RGBTuple
to_256_colors(c)
end::UInt8
end
end

complement(color::UserColorType)::ColorType = complement(ansi_color(color))
complement(color::ColorType)::ColorType = if color ≡ INVALID_COLOR
Expand Down
14 changes: 10 additions & 4 deletions src/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ function print_title(
post_pad = blank^(max(0, p_width - length(pre_pad) - length(title)))
print_nocol(io, post_pad, right_pad)
(
count(string('\n'), title) + 1, # NOTE: string(...) for compat with 1.6
count('\n', title) + 1,
length(strip(left_pad * pre_pad * title * post_pad * right_pad, '\n')),
)
end
Expand Down Expand Up @@ -145,7 +145,10 @@ end
Base.show(io::IO, p::Plot) = _show(io, print, print_color, p)

function _show(end_io::IO, print_nocol, print_color, p::Plot)
buf = PipeBuffer() # buffering, for performance
_have_truecolor = Base.have_truecolor
Base.have_truecolor = colormode() == 24

buf = Base.AnnotatedIOBuffer() # buffering, for performance
io_color = get(end_io, :color, false)
io = IOContext(buf, :color => io_color, :displaysize => displaysize(end_io))

Expand Down Expand Up @@ -225,7 +228,8 @@ function _show(end_io::IO, print_nocol, print_color, p::Plot)
border_right_cbar_pad * '\n',
🗹;
p_width = p_width,
color = io_color ? Crayon(foreground = :white, bold = true) : nothing,
color = io_color ? StyledStrings.Face(foreground = :white, weight = :bold) :
nothing,
)
h_lbl = print_labels(
io,
Expand Down Expand Up @@ -385,7 +389,9 @@ function _show(end_io::IO, print_nocol, print_color, p::Plot)
end

# delayed print (buffering)
print_nocol(end_io, read(buf, String))
print(end_io, read(seekstart(buf), Base.AnnotatedString))

Base.have_truecolor = _have_truecolor

# return the approximate image size
(
Expand Down
1 change: 0 additions & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ using TestImages
using ColorTypes
using StableRNGs
using StatsBase
using Crayons
using Unitful

Pkg.status(; outdated = true, mode = Pkg.PKGMODE_MANIFEST)
Expand Down
19 changes: 1 addition & 18 deletions test/tst_common.jl
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,7 @@ end
@test UnicodePlots.c256(0) == 0
@test UnicodePlots.c256(255) == 255

@test_throws ArgumentError UnicodePlots.colormode!(123456789)

_color_mode = UnicodePlots.colormode()
UnicodePlots.COLORMODE[] = Crayons.COLORS_16 # we only support 8bit or 24bit, not 4bit (terminal dependent)
@test_throws ArgumentError UnicodePlots.colormode()
@test_throws ArgumentError UnicodePlots.colormode!(123_456_789)

UnicodePlots.colors256!()
@test UnicodePlots.ansi_color(0x80) == UnicodePlots.THRESHOLD + 0x80 # ansi 128
Expand Down Expand Up @@ -136,8 +132,6 @@ end
@test UnicodePlots.ansi_color(:light_blue) == UnicodePlots.THRESHOLD + 0x0c
UnicodePlots.USE_LUT[] = _lut

UnicodePlots.colormode!(_color_mode)

if true # physical average
@test UnicodePlots.blend_colors(UInt32(0), UInt32(255)) == UInt32(180)
@test UnicodePlots.blend_colors(0xff0000, 0x00ff00) == 0xb4b400 # red & green -> yellow
Expand All @@ -153,18 +147,7 @@ end
@test UnicodePlots.complement(UnicodePlots.INVALID_COLOR) == UnicodePlots.INVALID_COLOR
@test UnicodePlots.complement(0x003ae1c3) == 0x00c51e3c

io = PipeBuffer()
_cfast = UnicodePlots.CRAYONS_FAST[]
for fast ∈ (false, true)
UnicodePlots.CRAYONS_FAST[] = fast
UnicodePlots.print_crayons(io, Crayon(foreground = :red), 123)
UnicodePlots.print_crayons(io, Crayon(), 123)
end
UnicodePlots.CRAYONS_FAST[] = _cfast

@test UnicodePlots.ignored_color(nothing)
@test UnicodePlots.crayon_color(missing) isa Crayons.ANSIColor
@test UnicodePlots.crayon_color(nothing) isa Crayons.ANSIColor
end

@testset "colormaps" begin
Expand Down
Loading