Files
cshenv/bin/__build_sgr_code

587 lines
15 KiB
Bash
Executable File

#!/bin/sh
# 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
debug_print()
{
if [ ${debug} -ne 0 ]
then
echo ${*} >&2
fi
}
# This is the named color map. Just add new entries and they'll be supported...
COLOR_TABLE=""
map()
{
if [ $# -eq 2 ]
then
COLOR_TABLE="${COLOR_TABLE}:${1}=${2}"
else
___right="${COLOR_TABLE##*:${1}=}"
echo ${___right%%:*}
fi
}
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
dump_colors()
{
all_maps=$(echo ${COLOR_TABLE} | sed -e 's/:/ /g' -e 's/=[0-9a-fA-F]\{6\}//g')
debug_print "Going through all these colors ${all_maps} (${COLOR_TABLE})"
for key in ${all_maps}
do
debug_print "Processing key ${key}"
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 [ -z "${CSHENV_TERMINAL_COLORS+x}" ] || [ ${CSHENV_TERMINAL_COLORS} -ge 256 ]
then
printf "`env CSHENV_TERMINAL_COLORS=256 $0 reset reverse bg:${text_color} fg:${key}`8-bit sample`$0 reset`|"
fi
if [ -z "${CSHENV_TERMINAL_COLORS+x}" ] || [ ${CSHENV_TERMINAL_COLORS} -ge 16 ]
then
printf "`env CSHENV_TERMINAL_COLORS=16 $0 reset reverse bg:${text_color} fg:${key}`4-bit sample`$0 reset`|"
fi
if [ -z "${CSHENV_TERMINAL_COLORS+x}" ] || [ ${CSHENV_TERMINAL_COLORS} -ge 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
}
cube_row()
{
r_base=${1}
g=0
while [ ${g} -lt 6 ]
do
u=0
while [ ${u} -lt 3 ]
do
r=$(( r_base + u ))
b=0
while [ ${b} -lt 6 ]
do
i=$(( 16 + ( r * 36 ) + ( g * 6 ) + ( b ) ))
txt="" && [ ${g} -lt 3 ] && txt=';37;1'
printf "[30${txt}m[48;5;${i}m%3i " ${i}
b=$(( b + 1 ))
done
printf ' '
u=$(( u + 1 ))
done
printf '\n'
g=$(( g + 1 ))
done
printf '\n'
}
greyscale_chart()
{
[ ${i} -ge 244 ] && txt=";30"
local k=$(( i + 12 ))
while [ ${i} -lt ${k} ]
do
printf "[48;5;${i};37;1${txt}m%3i " ${i}
i=$(( i + 1 ))
done
printf '\n'
}
color_chart()
{
i=0
# Base 16
while [ ${i} -lt 16 ]
do
[ ${i} -gt 0 ] && txt=";30"
printf "[37;1${txt};48;5;${i}m%3i " ${i}
i=$(( i + 1 ))
done
printf '\n\n'
r=0;g=0;b=0
cube_row 0
cube_row 3
i=$(( 6 * 6 * 6 + 16 ))
greyscale_chart
greyscale_chart
}
color_block()
{
printf "\e[37;1;48;5;${1}m%3i\e[m " ${1}
}
color_swatches()
{
while [ "$1" != "" ]
do
color_block $1
shift 1
done
printf "\n"
}
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 [ "${CSHENV_TERMINAL_COLORS+x}" ]
then
if [ ${CSHENV_TERMINAL_COLORS} -eq 256 ]
then
use_8_bit=1
elif [ ${CSHENV_TERMINAL_COLORS} -eq 16 ]
then
use_4_bit=1
elif [ ${CSHENV_TERMINAL_COLORS} -eq 8 ]
then
use_3_bit=1
else
echo "ERROR!" 1>&2 ; exit 255
fi
fi
}
render_color()
{
command_color=30
if [ ${use_3_bit} -ne 0 ]
then
# Underline colours need 8-bit or more
if [ ${background} -gt 1 ] ; then return; fi
basecolor=$(( ${command_color} + ${background}*10))
next="$(( ${basecolor} + ${legacy_3_bit} ))"
elif [ ${use_4_bit} -ne 0 ]
then
# Underline colours need 8-bit or more
if [ ${background} -gt 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} -ne 0 ]
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
}
ansi_color()
{
if [ ${1} -gt 15 ]
then
echo "ERROR!" 1>&2 ; exit 255
elif [ ${1} -ge 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
}
ext_grey()
{
use_8_bit=1
grey_val=${1}
if [ ${grey_val} -le 0 - ${grey_val} -gt 23 ]
then
echo "ERROR!" 1>&2 ; exit 255
fi
ext_8_bit=$(( 232 + ${grey_val} ))
}
ext_rgb()
{
use_8_bit=1
red_ext_val="${1%??*}"
tmp="${1#?}"
green_ext_val=${tmp%?*}
blue_ext_val=${tmp#?*}
ext_8_bit=$(( 16 + 36 * ${red_ext_val} + 6 * ${green_ext_val} + ${blue_ext_val} ))
}
ext_color()
{
use_8_bit=1
if [ ${1} -gt 255 ]
then
echo "ERROR!" 1>&2 ; exit 255
else
ext_8_bit=${1}
fi
}
rgb_color()
{
color=$1
case $1 in
[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])
tmp="${color}"
color="${tmp%??*}${tmp%??*}"
tmp="${tmp#?}"
color="${color}${tmp%?*}${tmp%?*}${tmp#?}${tmp#?}"
;;
esac
# First split off the red, green, and blue components...
red_hex="0x${color%????}"
color="${color#??}"
green_hex="0x${color%??}"
blue_hex="0x${color#??}"
# Convert to decimal...
red_dec=$((${red_hex}))
green_dec=$((${green_hex}))
blue_dec=$((${blue_hex}))
if [ ${debug} -ne 0 ]
then
debug_print "Red: $red_dec"
debug_print "Green: $green_dec"
debug_print "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} -ge 192 -o ${green_dec} -ge 192 -o ${blue_dec} -ge 192 ]
then
intensity_1_bit=1
fi
if [ ${debug} -ne 0 ]
then
debug_print "Red bit: " $red_1_bit
debug_print "Green bit: " $green_1_bit
debug_print "Blue bit: " $blue_1_bit
debug_print "Intensity bit: " $intensity_1_bit
fi
# Because grey is tricky, I'm going to special case it:
if [ ${red_dec} -eq ${green_dec} -a ${green_dec} -eq ${blue_dec} -a ${red_dec} -lt 128 -a ${red_dec} -ge 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} -gt 128 -a ${green_dec} -gt 80 -a ${blue_dec} -lt 50 ]
then
red_1_bit=1
green_1_bit=1
blue_1_bit=0
fi
if [ ${debug} -ne 0 ]
then
debug_print "Red bit: " $red_1_bit
debug_print "Green bit: " $green_1_bit
debug_print "Blue bit: " $blue_1_bit
debug_print "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} -ne 0 ]
then
debug_print "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} -ne 0 ]
then
debug_print "Red ext: " ${red_ext_val}
debug_print "Green ext: " ${green_ext_val}
debug_print "Blue ext: " ${blue_ext_val}
fi
ext_8_bit=$(( 16 + 36 * ${red_ext_val} + 6 * ${green_ext_val} + ${blue_ext_val} ))
if [ ${debug} -ne 0 ]
then
debug_print "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} -eq ${green_dec} -a ${green_dec} -eq ${blue_dec} ]
then
ext_8_bit=$(( 232 + ${red_dec} * 24 / 256 ))
fi
if [ ${debug} -ne 0 ]
then
debug_print "Computed 256 color choice, after applying greyscale scan: " ${ext_8_bit}
fi
if [ ${enable_greyscale_cheat} -ne 0 ]
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} -eq ${green_dec} -a ${green_dec} -eq ${blue_dec} ]
then
if [ ${red_dec} -eq 0 ]
then
ext_8_bit=0
elif [ ${red_dec} -eq 255 ]
then
ext_8_bit=15
elif [ ${red_dec} -eq 128 ]
then
ext_8_bit=8
elif [ ${red_dec} -eq 192 ]
then
ext_8_bit=7
fi
fi
fi
if [ ${debug} -ne 0 ]
then
debug_print "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.
}
make_color()
{
setup_color_depth
background=$1 # $1 is the background bit
$2 $3 # $2 is the color function
render_color
}
named_color()
{
name=$( echo ${1} | awk '{print tolower($1)}' )
mapped=$(map ${name})
debug_print "${name} -> ${mapped}"
if [ -z "${mapped}" ]
then
echo "ERROR!" 1>&2 ; exit 255
fi
rgb_color ${mapped}
}
build_sgr_code()
{
case $1 in
dump-colors) dump_colors ;;
color-chart) color_chart ;;
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#?????} ;;
ext:grey*) make_color 0 ext_grey ${1#????????} ;;
ext:rgb*) make_color 0 ext_rgb ${1#???????} ;;
ext:*) make_color 0 ext_color ${1#????} ;;
# 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#????????} ;;
fg:ext:grey*) make_color 0 ext_grey ${1#???????????} ;;
fg:ext:rgb*) make_color 0 ext_rgb ${1#??????????} ;;
fg:ext:*) make_color 0 ext_color ${1#???????} ;;
# 12-bit color also supported
fg:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 0 rgb_color ${1#???} ;;
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#???} ;;
bg:ansi:*) make_color 1 ansi_color ${1#????????} ;;
bg:ext:grey*) make_color 1 ext_grey ${1#???????????} ;;
bg:ext:rgb*) make_color 1 ext_rgb ${1#??????????} ;;
bg:ext:*) make_color 1 ext_color ${1#???????} ;;
# 12-bit color also supported
bg:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 1 rgb_color ${1#???} ;;
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#???} ;;
ul:ext:grey*) make_color 2 ext_grey ${1#???????????} ;;
ul:ext:rgb*) make_color 2 ext_rgb ${1#??????????} ;;
ul:ext:*) make_color 2 ext_color ${1#???????} ;;
# 12-bit color also supported
ul:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 2 rgb_color ${1#???} ;;
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#???} ;;
fg:*) make_color 0 named_color ${1#???} ;;
bg:*) make_color 1 named_color ${1#???} ;;
ul:*) make_color 2 named_color ${1#???} ;;
*) make_color 0 named_color $1 ;;
esac
if [ ! -z ${output} ]
then
output="${output};"
fi
output="${output}${next}"
}
output=""
if [ "$1" = "color-swatches" ]
then
shift 1
color_swatches $*
else
while [ ! -z "$1" ]
do
build_sgr_code $1
shift 1
done
fi
if [ ! -z "${output}" ] && [ ${nocsi} -ne 1 ]
then
#echo "I AM PRINTING WITH CSI! (${nocsi} is nocsi, \`${output}\` is output)">&2
output="[${output}m"
fi
if [ ! -z "${output}" ]
then
printf ${output}
fi
exit 0