diff --git a/Project.toml b/Project.toml index f8978e32..7582cfff 100644 --- a/Project.toml +++ b/Project.toml @@ -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" @@ -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" @@ -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" @@ -53,6 +52,7 @@ Printf = "1" SparseArrays = "1" StaticArrays = "1" StatsBase = "0.33 - 0.34" +StyledStrings = "1" Term = "2" Unitful = "1" julia = "1.10" diff --git a/src/UnicodePlots.jl b/src/UnicodePlots.jl index c741e82c..84078d4c 100644 --- a/src/UnicodePlots.jl +++ b/src/UnicodePlots.jl @@ -1,10 +1,10 @@ module UnicodePlots using PrecompileTools +using StyledStrings using LinearAlgebra using StaticArrays using ColorTypes -using Crayons using Printf using Dates diff --git a/src/common.jl b/src/common.jl index a0d52dc4..67b55700 100644 --- a/src/common.jl +++ b/src/common.jl @@ -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 ############################################################################################ @@ -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) ############################################################################################ @@ -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!() @@ -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() @@ -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 @@ -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 diff --git a/src/show.jl b/src/show.jl index 367c1fdb..f7bdc927 100644 --- a/src/show.jl +++ b/src/show.jl @@ -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 @@ -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)) @@ -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, @@ -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 ( diff --git a/test/runtests.jl b/test/runtests.jl index 0da27416..4260c87e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -19,7 +19,6 @@ using TestImages using ColorTypes using StableRNGs using StatsBase -using Crayons using Unitful Pkg.status(; outdated = true, mode = Pkg.PKGMODE_MANIFEST) diff --git a/test/tst_common.jl b/test/tst_common.jl index ec5b461a..8de4ad9a 100644 --- a/test/tst_common.jl +++ b/test/tst_common.jl @@ -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 @@ -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 @@ -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