Files
cshenv/bin/__build_sgr_code

459 lines
13 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# This script will take an HTML compatible hex colour string and turn it into
# a terminal sequence for true colour and other formats which best approximate
# its colour value.
#
# After that, we can map a few other special named symbols. The idea is to
# permit shell variables such as `aaffcc bold italic` to indicate what you
# want a format to look like.
#
# The terminal colour and effect language herein implemented is named
# "SGR Name".
debug=0
# This is the named color map. Just add new entries and they'll be supported...
declare -A map
map[white]="FFFFFF"
map[silver]="C0C0C0"
map[gray]="808080"
map[grey]="808080"
map[black]="000000"
map[red]="FF0000"
map[maroon]="800000"
map[yellow]="FFFF00"
map[olive]="808000"
map[lime]="00FF00"
map[green]="008000"
map[aqua]="00FFFF"
map[teal]="008080"
map[blue]="0000FF"
map[navy]="000080"
map[fuchsia]="FF00FF"
map[purple]="800080"
# Some VGA color names can be promoted:
map[brown]="aa5500"
map[cyan]="00aaaa"
# The VGA names can be prefixed with "vga-" to explicitly avoid the collisions with HTML names, above...
# Based upon wikipedia's article on ANSI. See the table on that page for details.
map[vga-black]="000000"
map[vga-red]="aa0000"
map[vga-green]="00aa00"
map[vga-brown]="aa5500"
map[vga-yellow]="aa5500"
map[vga-blue]="0000aa"
map[vga-magenta]="aa00aa"
map[vga-cyan]="00aaaa"
map[vga-white]="aaaaaa"
map[vga-brightblack]="555555"
map[vga-grey]="555555"
map[vga-gray]="555555"
map[vga-brightred]="ff5555"
map[vga-brightgreen]="55ff55"
map[vga-brightyellow]="ffff55"
map[vga-brightblue]="5555ff"
map[vga-brightmagenta]="ff55ff"
map[vga-brightcyan]="55ffff"
map[vga-brightwhite]="ffffff"
enable_greyscale_cheat=0
nocsi=0
if [[ $1 == "no-csi" ]]
then
nocsi=1
shift 1
fi
function dump_colors()
{
for key in ${!map[@]}
do
case $key in
*bright*) text_color="000000" ;;
*black*) text_color="ffffff" ;;
*) text_color="000000" ;;
esac
printf "%-24s${map[${key}]} |" ${key}
printf "`$0 reset reverse bg:${text_color} fg:${key}`native-sample`$0 reset`|"
if [[ ! -v CSHENV_TERMINAL_COLORS ]] || (( ${CSHENV_TERMINAL_COLORS} >= 256 ))
then
printf "`env CSHENV_TERMINAL_COLORS=256 $0 reset reverse bg:${text_color} fg:${key}`8-bit sample`$0 reset`|"
fi
if [[ ! -v CSHENV_TERMINAL_COLORS ]] || (( ${CSHENV_TERMINAL_COLORS} >= 16 ))
then
printf "`env CSHENV_TERMINAL_COLORS=16 $0 reset reverse bg:${text_color} fg:${key}`4-bit sample`$0 reset`|"
fi
if [[ ! -v CSHENV_TERMINAL_COLORS ]] || (( ${CSHENV_TERMINAL_COLORS} >= 8 ))
then
printf "`env CSHENV_TERMINAL_COLORS=8 $0 reset reverse bg:${text_color} fg:${key}`3-bit sample`$0 reset`|"
fi
printf "\n"
done
exit 0
}
function setup_color_depth()
{
use_8_bit=0
use_4_bit=0
use_3_bit=0
# Must only be set if terminal colors are reduced...
if [[ -v CSHENV_TERMINAL_COLORS ]]
then
if (( ${CSHENV_TERMINAL_COLORS} == 256 ))
then
use_8_bit=1
elif (( ${CSHENV_TERMINAL_COLORS} == 16 ))
then
use_4_bit=1
elif (( ${CSHENV_TERMINAL_COLORS} == 8 ))
then
use_3_bit=1
else
echo "ERROR!" 1>&2 ; exit -1
fi
fi
}
function render_color()
{
command_color=30
if (( ${use_3_bit} ))
then
# Underline colours need 8-bit or more
if (( ${background} > 1 )) ; then return; fi
basecolor=$(( ${command_color} + ${background}*10))
next="$(( ${basecolor} + ${legacy_3_bit} ))"
elif (( ${use_4_bit} ))
then
# Underline colours need 8-bit or more
if (( ${background} > 1 )) ; then return; fi
command_color=$(( ${command_color} + ${intensity_1_bit} * 60 ))
basecolor=$(( ${command_color} + ${background}*10 ))
next="$(( ${basecolor} + ${legacy_3_bit} ))"
elif (( ${use_8_bit} ))
then
basecolor=$(( 8 + ${command_color} + ${background}*10 ))
next="${basecolor};5;${ext_8_bit}"
else
basecolor=$(( 8 + ${command_color} + ${background}*10 ))
next="${basecolor};2;${red_dec};${green_dec};${blue_dec}"
fi
}
function ansi_color()
{
if (( ${1} > 15 ))
then
echo "ERROR!" 1>&2 ; exit -1
elif (( ${1} >= 8 ))
then
use_4_bit=1
intensity_1_bit=1
legacy_3_bit=$(( ${1} - 8 ))
else
use_3_bit=1
intensity_1_bit=0
legacy_3_bit=$(( ${1} ))
fi
}
function ext_grey()
{
use_8_bit=1
grey_val=${1}
if (( ${grey_val} < 0 || ${grey_val} > 23 ))
then
echo "ERROR!" 1>&2 ; exit -1
fi
ext_8_bit=$(( 232 + ${grey_val} ))
}
function ext_rgb()
{
use_8_bit=1
red_ext_val=$((6#${1:0:1}))
green_ext_val=$((6#${1:1:1}))
blue_ext_val=$((6#${1:2:1}))
ext_8_bit=$(( 16 + 36 * ${red_ext_val} + 6 * ${green_ext_val} + ${blue_ext_val} ))
}
function ext_color()
{
use_8_bit=1
if (( ${1} > 255 ))
then
echo "ERROR!" 1>&2 ; exit -1
else
ext_8_bit=${1}
fi
}
function rgb_color()
{
color=$1
case $1 in
[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) color=${1:0:1}${1:0:1}${1:1:1}${1:1:1}${1:2:1}${1:2:1} ;;
esac
# First split off the red, green, and blue components...
red_hex=${color:0:2}
green_hex=${color:2:2}
blue_hex=${color:4:2}
# Convert to decimal...
red_dec=$((16#${red_hex}))
green_dec=$((16#${green_hex}))
blue_dec=$((16#${blue_hex}))
if (( ${debug} != 0 ))
then
echo "Red: $red_dec"
echo "Green: $green_dec"
echo "Blue: $blue_dec"
fi
# Now compute the rest of the stuff...
# We probably don't have to compute all the unused color states. We only have to compute
# the color state we're signed up for by CSHENV_TERMINAL_COLORS... but whatever...
# We round up by 128 points of colour, so that if we're above a certain intensity, we get the top
# bit set.
red_1_bit=$(( ( ( ${red_dec} + 128 ) >> 8 ) & 1 ))
green_1_bit=$(( ( ( ${green_dec} + 128 ) >> 8 ) & 1 ))
blue_1_bit=$(( ( ( ${blue_dec} + 128 ) >> 8 ) & 1 ))
intensity_1_bit=0
# If we can support an intensity bit, we'll turn that on too...
# But we're going to stop using bold to set the colour "intense"
# We'll use the 9x and 10x forms...
if (( ${red_dec} >= 192 || ${green_dec} >= 192 || ${blue_dec} >= 192 ))
then
intensity_1_bit=1
fi
if (( ${debug} != 0 ))
then
echo "Red bit: " $red_1_bit
echo "Green bit: " $green_1_bit
echo "Blue bit: " $blue_1_bit
echo "Intensity bit: " $intensity_1_bit
fi
# Because grey is tricky, I'm going to special case it:
if (( 1 && ${red_dec} == ${green_dec} && ${green_dec} == ${blue_dec} && ${red_dec} < 128 && ${red_dec} >= 80 ))
then
red_1_bit=0
green_1_bit=0
blue_1_bit=0
intensity_1_bit=1
fi
# Because brown is tricky, I'm going to special case it:
if (( ${red_dec} > 128 && ${green_dec} > 80 && ${blue_dec} < 50 ))
then
red_1_bit=1
green_1_bit=1
blue_1_bit=0
fi
if (( ${debug} != 0 ))
then
echo "Red bit: " $red_1_bit
echo "Green bit: " $green_1_bit
echo "Blue bit: " $blue_1_bit
echo "Intensity bit: " $intensity_1_bit
fi
# This lets us combine them for a legacy colour value in the legacy colour space...
legacy_3_bit=$(( ( ${blue_1_bit} << 2 ) + ( ${green_1_bit} << 1 ) + ( ${red_1_bit} ) ))
if (( ${debug} != 0 ))
then
echo "Legacy colour: " ${legacy_3_bit} " Intensity: " ${intensity_1_bit}
fi
# Now compute an extended colour cube placement (216 colours):
red_ext_val=$(( ${red_dec} * 6 / 256 ))
green_ext_val=$(( ${green_dec} * 6 / 256 ))
blue_ext_val=$(( ${blue_dec} * 6 / 256 ))
if (( ${debug} != 0 ))
then
echo "Red ext: " ${red_ext_val}
echo "Green ext: " ${green_ext_val}
echo "Blue ext: " ${blue_ext_val}
fi
ext_8_bit=$(( 16 + 36 * ${red_ext_val} + 6 * ${green_ext_val} + ${blue_ext_val} ))
if (( ${debug} ))
then
echo "Computed 216 color cube: " ${ext_8_bit}
fi
# Check for precise greyscale, which would replace the above:
# (Note that precise greyscale is implied by a user explicitly making ALL of RGB the
# same value, thus demanding greyscale, rather than a subtle tinting...)
if (( ${red_dec} == ${green_dec} && ${green_dec} == ${blue_dec} ))
then
ext_8_bit=$(( 232 + ${red_dec} * 24 / 256 ))
fi
if (( ${debug} ))
then
echo "Computed 256 color choice, after applying greyscale scan: " ${ext_8_bit}
fi
if (( ${enable_greyscale_cheat} ))
then
# Check for SPECIFIC white and black and grey:
# The half-saturation, no saturation, and full saturation values are mapped
# to specific points in the legacy set. (This may be ill-advised as a manually
# built reverse video terminal setting through colour mapping may make it so that
# we wind up in la-la land. Perhaps the better way is to find the head and tail
# points in the 6x6x6 colour cube.)
#
# TODO: Only map the head and tail of the colour cube.
if (( ${red_dec} == ${green_dec} && ${green_dec} == ${blue_dec} ))
then
if (( ${red_dec} == 0 ))
then
ext_8_bit=0
elif (( ${red_dec} == 255 ))
then
ext_8_bit=15
elif (( ${red_dec} == 128 ))
then
ext_8_bit=8
elif (( ${red_dec} == 192 ))
then
ext_8_bit=7
fi
fi
fi
if (( ${debug} ))
then
echo "Computed 256 color choice, after applying white and black scan: " ${ext_8_bit}
fi
# We do NOT check for the base colours precisely. Since those 16 can be custom mapped by your terminal emulator, you just pass `ansi:0` thru
# `ansi:15` to select them by ID. Or use `ext:0` thru `ext:255` to select a specific extended colour.
}
function make_color()
{
setup_color_depth
background=$1 # $1 is the background bit
$2 $3 # $2 is the color function
render_color
}
function named_color()
{
name=${1,,}
if [[ ! -v map[$name] ]]
then
echo "ERROR!" 1>&2 ; exit -1
fi
mapped=${map[$name]}
rgb_color ${mapped}
}
function build_sgr_code()
{
case $1 in
dump-colors) dump_colors ;;
reset) next=0 ;;
bold) next=1 ;;
dim) next=2 ;;
italic) next=3 ;;
underline|under) next=4 ;;
blink) next=5 ;;
fastblink) next=6 ;;
reverse) next=7 ;;
hide|conceal) next=8 ;;
strike|strikethrough|strikethru) next=9 ;;
doubleunder) next=21 ;;
reveal) next=28 ;;
ansi:*) make_color 0 ansi_color ${1:5} ;;
ext:grey*) make_color 0 ext_grey ${1:8} ;;
ext:rgb*) make_color 0 ext_rgb ${1:7} ;;
ext:*) make_color 0 ext_color ${1:4} ;;
# 12-bit color also supported
[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 0 rgb_color $1 ;;
[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 0 rgb_color $1 ;;
fg:ansi:*) make_color 0 ansi_color ${1:8} ;;
fg:ext:grey*) make_color 0 ext_grey ${1:11} ;;
fg:ext:rgb*) make_color 0 ext_rgb ${1:10} ;;
fg:ext:*) make_color 0 ext_color ${1:7} ;;
# 12-bit color also supported
fg:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 0 rgb_color ${1:3} ;;
fg:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 0 rgb_color ${1:3} ;;
bg:ansi:*) make_color 1 ansi_color ${1:8} ;;
bg:ext:grey*) make_color 1 ext_grey ${1:11} ;;
bg:ext:rgb*) make_color 1 ext_rgb ${1:10} ;;
bg:ext:*) make_color 1 ext_color ${1:7} ;;
# 12-bit color also supported
bg:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 1 rgb_color ${1:3} ;;
bg:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 1 rgb_color ${1:3} ;;
ul:ext:*) make_color 2 ext_color ${1:7} ;;
# 12-bit color also supported
ul:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 2 rgb_color ${1:3} ;;
ul:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 2 rgb_color ${1:3} ;;
fg:*) make_color 0 named_color ${1:3} ;;
bg:*) make_color 1 named_color ${1:3} ;;
ul:*) make_color 2 named_color ${1:3} ;;
*) make_color 0 named_color $1 ;;
esac
if [[ ! -z ${output} ]]
then
output="${output};"
fi
output="${output}${next}"
}
output=""
while [[ ! -z $1 ]]
do
build_sgr_code $1
shift 1
done
if [[ ! -z ${output} ]] && (( ! ${nocsi} ))
then
output="[${output}m"
fi
if [[ ! -z ${output} ]]
then
printf ${output}
fi
exit 0