#!/bin/bash
# (c) Ricardas Cepas <rch@pub.osf.lt>. Copying policy: BSD or GNU GPL V2.
# V 0.1
#set -x
set -o errexit -o noclobber
if ! [ $BASH_VERSION \< 2 ]
then :
else
	echo "$0: Needs Bash >=2.0" >&2
	exit 1
fi

function Usage ()
  {
    echo -n \
' Splits .psf console fonts into files and merges several fonts.
 Usage: 

    psfsplit [--range=MIN-MAX] [--shift=X] infont_with_or_without_Unicode_table
 MIN and MAX are glyph positions in font, bash syntax. Defaults to entire
font (0-0xff or 0-0x1ff).
 X is number of rows (-9...9) for shifting rows in each charcell. Negative 
means upwards.

    psfcat [ Options ] $(/bin/ls --reverse --sort=time U+*) >outfont
    psfcat [ Options ] $(/bin/ls --sort=extension      U+*) >outfont
    psfcat [ Options ] U+* >outfont
 Options:  --size=256   --height=<charcell_height>
 In case of several glyphs for the same unicode only first is used.

    psfmerge [ Options ] [psfsplit options] infont1 [psfsplit options] infont2 ... >outfont
 infont, outfont - 256 or 512 characters .psf console font with Unicode table.
 Options:
 --height=<charcell_height>   make <charcell_height> height font (16, 14, ...).
 --pause     pause after splitting all fonts.
 --size=256  output only 256 character font instead of 512.
 --sort-almost-all   sort all glyphs (except 0x00-0x1E from infont1) by Unicode
value. Default is don'\''t sort first 256 glyphs from infont1, *assuming* there
are Unicode values for them. For each infont range and shift can be specified, 
as for psfslit.
' >&2
    exit "${1:-1}"
  }

function Check_output ()
  {
    if test -t \&1
    then
	echo $0: Output is tty ?? >&2
	Usage
    fi
  }

function Psfheader ()
{
	echo font_size=$1 height=$(( 10#$2 )) >&2
	printf '\x36\x04'$(printf '\\x%X\\x%X' $(( $1 / 256 - 1 )) $(( 10#$2 )))
}

function Psfcat ()
  {
    set -o noglob
    Check_output
    unset glyphs symlinks symlinks_targets || :
    declare -i font_size=512 glyphs_number=0 i=0 index=0 max=0 \
						newfont_position=0
    unset height || :
    declare -ir file_size_pos=4 name_pos=8 symlink_sign_pos=9 \
						 symlink_target_pos=10

    echo -e $0: '\033%GSwitching terminal to UTF-8.' >&2
    echo $0: Sorting arguments. Please wait ... >&2
    for option in "$@"
    do
	if [ "${1#--height=}" != "$1" ]
	then
	    height=$((${1#--height=}))
	    shift
	elif [ "${1#--size=}" != "$1" ]
	then
	    font_size="${1#--size=}"
	    shift
	    if (( $font_size != 256 & $font_size != 512 ))
	    then
		    echo $0: Supported font sizes are 256 and 512. >&2
		    Usage
	    fi
	else break
	fi
    done
    command ls -l -p --directory --literal --numeric-uid-gid --sort=none "$@" \
	>$tmp_file.ls
    while read -ra ls_string
      do
	[ "${ls_string[$name_pos]}" != "" ] \
	&& if [ "${ls_string[$name_pos] \
	##U+[0-9a-f][0-9a-f][0-9a-f][0-9a-f].*.0x[0-9a-f][0-9a-f][0-9a-f]}" \
									= "" ]
	then
	  if [ "${ls_string[$symlink_sign_pos]}" = "->" \
		   -a "${ls_string[$symlink_target_pos] \
	##U+[0-9a-f][0-9a-f][0-9a-f][0-9a-f].*.0x[0-9a-f][0-9a-f][0-9a-f]}" \
									= "" ]
	  then
	    symlinks[${#symlinks[@]}]="${ls_string[$name_pos]}"
	    symlinks_targets[${#symlinks_targets[@]}]="${ls_string[ \
							$symlink_target_pos]}"
	  elif (( ${height:=${ls_string[$file_size_pos]}} == \
					${ls_string[$file_size_pos]} ))
	  then
	    glyphs[${#glyphs[@]}]="${ls_string[$name_pos]}"
	  elif  t=($(wc --bytes "${ls_string[$name_pos]}"; ))
		(( ${ls_string[$file_size_pos]} != ${t[0]} ))
	  then :
#	    glyphs[${#glyphs[@]}]="${ls_string[$name_pos]}"
	  elif (( ${t[0]} < 5 || ${t[0]} > 3*${ls_string[$file_size_pos]} ))
	  then
	    echo $0: "Skipping \`${ls_string[$name_pos]}' - " \
			"bad file size (${ls_string[$file_size_pos]})" >&2
	  elif
	    glyphs[${#glyphs[@]}]="${ls_string[$name_pos]}"
	    mv "${ls_string[$name_pos]}"{,~}
	    (( ( i = $height-${ls_string[$file_size_pos]} ) > 0 ))
# t="$(cat ${ls_string[$name_pos]})"; 
	  then
	    dd conv=sync if="${ls_string[$name_pos]}"~ \
		ibs=$((${ls_string[$file_size_pos]} + $i / 2 )) \
		of="${ls_string[$name_pos]}" seek=1 obs=$(( ($i+1) / 2 )) \
								&>/dev/null
	  else
	    dd if="${ls_string[$name_pos]}"~ of="${ls_string[$name_pos]}" \
		ibs=1 skip=$(( (-$i+1) / 2 )) count=$height &>/dev/null
	  fi
	else
	  echo $0: "Skipping \`${ls_string[$name_pos]}' - bad file name" >&2
	fi
      done <$tmp_file.ls
    set +o noglob
    rm $tmp_file.ls --force U+*~
    set -o noglob

    index=-1
    max=${#glyphs[@]}
    echo $0: Testing: >&2
    time while (( ( index += 1 ) < $max ))
    do
#	set -x
	# skip additional glyphs for the same unicodes:
	if [ "${glyphs[$index]}" != "" ]
	then
	   echo -n "${glyphs[$index]} " >&2
	   index2=$index
	   while (( ( index2 += 1 ) < $max ))
	   do
	       if [ "${glyphs[$index2]:2:4}" == "${glyphs[$index]:2:4}" ]
	       then 
		  [ "${glyphs[$index2]}" == "${glyphs[$index]}" ] \
		  || echo -e "\n$0: Skipping ${glyphs[$index2]}" - \
				already have a glyph for this unicode. >&2
		  unset glyphs[$index2]
	       fi
	   done
	fi
    done
#    time for glyph in "${glyphs[@]}"
#    do
#	t="${glyphs[*]##U+${glyph:2:4}*}"
#	[ "$t" == "${glyphs[*]}" ] \
#	|| { unset glyphs; glyphs=($t "$glyph"); }
#	echo "$glyph " >&2
#    done
    echo >&2
free >&2

    if (( ${#glyphs[@]} > $font_size ))
    then
	echo -e "$0: Font size ($font_size) exceeded, skipping: \\n" \
				"${glyphs[@]:$font_size}" >&2
	echo -e "$0: Font size ($font_size) exceeded, skipping: \\n" \
				"${glyphs[@]:$font_size}" >|/tmp/psfcat.log
    else
	>| /tmp/psfcat.log
    fi
    unset t || :
    t=("${glyphs[@]:0:$font_size}")
    unset glyphs || :
    glyphs=("${t[@]}")
#echo -e "${#glyphs[@]} ${glyphs[@]} \\n $t" >&2

    [ ${#glyphs[@]} != 0 ] || { echo $0: No glyphs found >&2; Usage; }

    index=-1
    while (( ( index += 1 ) < ${#symlinks[@]} ))
    do
#	set -x
	# skip symlinks pointing to unexistant unicodes:
	# skip symlinks for unicodes having their own glyph
	if [ "${glyphs[*]##${symlinks_targets[$index]:0:6}*}" == \
							"${glyphs[*]}" ] \
	|| [ "${glyphs[*]##${symlinks[$index]:0:6}*}" != "${glyphs[*]}" ]
	then
		echo $0: Skipping "${symlinks[$index]} -> " \
				"${symlinks_targets[$index]}" >&2
		unset symlinks[$index] symlinks_targets[$index]
	fi
    done

    t="${symlinks[*]}"
    unset symlinks || :
    symlinks=($t)
    t=("${symlinks_targets[*]}")
    unset symlinks_targets || :
    symlinks_targets=($t)

    echo $0: Using ${#glyphs[@]} glyphs for \
			$(( ${#symlinks[@]} + ${#glyphs[@]} )) unicodes. >&2

    newfont_position=-1
    echo Making Unicode table: >&2
    {
      for glyph in "${glyphs[@]}"
      do
#	set -x
	if (( ( newfont_position+=1 ) < ${#glyphs[@]} ))
	then
	  echo -n "$glyph ">&2
	  echo $newfont_position "${glyph:0:6}"
	  index=-1
    	  while (( ( index += 1 ) < ${#symlinks[@]} ))
	  do
#		set -x
	  	if [ "${glyph:0:6}" = "${symlinks_targets[$index]:0:6}" ]
		then
  		  echo $newfont_position "${symlinks[$index]:0:6}"
		  #unset -neveiks !
		fi
	  done
	fi
      done
    } > $tmp_file.uni
    echo >&2
free >&2

    {
      Psfheader $font_size $height
      cat "${glyphs[@]}"
      if (( ${#glyphs[@]} < $font_size ))
      then
	dd count=1 ibs=$(( $height * ( $font_size - ${#glyphs[@]} ) )) \
						if=/dev/zero 2>/dev/null
	echo $0: $(( $font_size - ${#glyphs[@]} )) glyph slots free, filling \
					with zeros. >&2
	echo $0: $(( $font_size - ${#glyphs[@]} )) glyph slots free, filling \
					with zeros. >>/tmp/psfcat.log
      fi
    } | psfaddtable - $tmp_file.uni | tee $tmp_file.psf

    file $tmp_file.psf >&2
    setfont -v $tmp_file.psf >&2
    getunimap | while read -a t; do echo -n "${t[3]}" >&2; done; echo >&2
    rm $tmp_file.{psf,uni}
free >&2
    echo File /tmp/psfcat.log: `cat /tmp/psfcat.log`
  }

function Psfsplit ()
  {
#    set -x
    set -o noglob
    unset t || :
    declare -i height=0 i=0 max=0 max_range=0xFFFF min_range=0 shift=0 \
							size=0 unimap=0
    for option in "$@"
    do
	if [ "$1" != "" -a "${1##--range=*-*}" == ""  ] \
	    && t="${1#--range=}" && min_range=${t%-*} && (($min_range >= 0)) \
	    && t="${t#*-}" && max_range=${t:-0xFFFF} \
	    && (($max_range >= $min_range))
	then shift
	elif [ "$1" != "" ] \
	  && [ "${1##--shift=[0-9]}" == "" -o "${1##--shift=-[0-9]}" == ""  ]
	then
	   shift=${1#--shift=}
	   shift
	else break
	fi
    done

    [ $# == 1 ] || Usage
    if [ "${1#--rename=}" = "$1" ]
    then
	    if [ "`hexdump -e '/2 "%X" ' -n2 $1`" != "436" ]
	    then	echo $0: can\'t read $1 as .psf file >&2
		    exit
	    fi
	    size=`hexdump -e '/1 "%i" ' -n1 -s2 $1 `
	    if (( unimap = $size / 2 ))
	    then
		setfont $1 >&2
	    fi
	    size=$(( ( $size % 2 + 1 ) * 256 ))
	    (( $max_range < $size )) || max_range=$size-1
	    if (($max_range < $min_range))
	    then
		echo "min_range > max_range ($min_range > $max_range)" >&2
		Usage
	    fi
	    height=`hexdump -e '/1 "%i" ' -n1 -s3 $1 `
	    echo "$0: $size chars (using $min_range-$max_range)," \
					height=$height, unimap=$unimap >&2
	    if ! mkdir $1_
	    then
		echo $0: Warning: writing to the existing directory. >&2
		(( unimap == 0 )) || echo $0: Don\'t overwriting. >&2
	    fi
#	overwrite
	    dd bs=4 count=1 if=$1 of=$1_/\$psf_header &>/dev/null
	    i=$min_range-1
	    count=$(($height - ( $shift>0 ? $shift : -$shift) ))
	    while let " ( i+=1 ) <= $max_range "
	    do
		{
		    dd if=$1 bs=1 count=$count \
		     skip=$(( 4 + $i*$height + ( $shift>0 ? 0 : -$shift) )) \
					seek=$(( $shift>0 ? $shift : 0 ))
		    (( $shift>0 )) || dd bs=$((-$shift)) count=1 if=/dev/zero
		} >|$1_/`printf "%.3x" $i` 2>/dev/null
	    done
	    dd bs=1 skip=$(( $i * $height + 4 )) if=$1 of=$1_/map_tables \
								&>/dev/null
    else
	    set - "${1#--rename=}"
	    unimap=1
	    setfont $1 >&2
    fi
    if [ $unimap = 1 ]
    then
       echo -e $0: '\033%GSwitching terminal to UTF-8' >&2
       getunimap | \
	{
	   cd $1_
	   unset glyphs || :
	   while read -r fontindex unicode comment UTF8 etc
	   do
	    if [ ${fontindex%[0-9a-f][0-9a-f][0-9a-f]} = 0x ] \
		&& (( $fontindex >= min_range && $fontindex <= max_range ))
	    then
		#	don't overwrite
	      if mv --interactive ${fontindex#0x} \
		    "$unicode.${UTF8//\//}.$fontindex" 2>/dev/null <<-Here
									No
									Here
	      then
		    glyphs[${#glyphs[@]}]="$unicode.${UTF8//\//}.$fontindex"
	      else
		# file ${fontindex#0x} doesn't exist - it already mv'ed(?)
		    for glyph in "${glyphs[@]}"
		    do
		      [ "0x${glyph:$((-3)):3}" == "$fontindex" ] \
		      && ln --symbolic "$glyph" \
				"$unicode.${UTF8//\//}.$fontindex" \
		      && echo $0: $fontindex : another Unicode value \
						   "($unicode $UTF8)" >&2 \
		      && break 
		    done
	      fi
	    fi
	   done
	   unset glyphs || :
	}
    fi
    command ls --classify --literal $1_/ >&2
  }

function Psfmerge ()
  {
    declare -i sort_all=0 font_size=512 pause=0
    unset height || :
    for option in "$@"
    do
	if [ "${1#--height=}" != "$1" ]
	then
	    height=$((${1#--height=}))
	    shift
	elif [ "${1#--size=}" != "$1" ]
	then
	   font_size="${1#--size=}"
	   shift
	   if (( $font_size != 256 & $font_size != 512 ))
	   then
		   echo $0: Supported font sizes are 256 and 512. >&2
		   Usage
	   fi
	elif [ "$1" == "--sort-almost-all" ]
	then
	   sort_all=1
	   shift
	elif [ "$1" == "--pause" ]
	then
	   pause=1
	   shift
	else break
	fi
    done
    if (( $# < 1 ))
    then Usage
    fi
    Check_output
    for infont in "$@"
    do test "${infont##--shift=[0-9]}" == "" \
	-o "${infont##--shift=-[0-9]}" == "" \
	-o "${infont##--range=*-*}" == "" -o -r "$infont" \
	|| { echo "$0: Can't read $infont " >&2; exit 2; }
    done
    orig_dir="`pwd`"

    unset infont1_range shift || :
    while (( $# > 1 ))
    do
	if [ "${1##--range=*-*}" == "" ]
	then
	    infont1_range="$1"
	    shift
	elif [ "${infont##--shift=[0-9]}" == "" \
	    -o "${infont##--shift=-[0-9]}" == "" ]
	then
	    shift="$1"
	    shift
	else break
	fi
    done
    infont1="$1"
    mkdir $tmp_file.dir
    command cp "$infont1" $tmp_file.dir
    cd $tmp_file.dir
#set -x
    psfsplit ${shift:+"$shift"} ${infont1_range:+"$infont1_range"} \
							"${infont1##*/}"
    set +o noglob
    cd $tmp_file.dir/"${infont1##*/}_"

    if (( $sort_all ))
    then
	unsorted_chars="`command ls --literal --sort=extension U+????.*.0x0[01]?`"
    else
	unsorted_chars="`command ls --literal --sort=extension U+????.*.0x0??`"
    fi
    shift

    unset infont_range shift || :
    for infont in "$@"
    do
	if [ "${infont##--range=*-*}" == "" ]
	then
	    infont_range="$infont"
	elif [ "${infont##--shift=[0-9]}" == "" \
	    -o "${infont##--shift=-[0-9]}" == "" ]
	then
	    shift="$infont"
	else
	    cd $orig_dir
	    command cp --force "$infont" $tmp_file.dir
	    cd $tmp_file.dir
	    [ "${infont1##*/}" == "${infont##*/}" ] \
	    || command ln --force --symbolic "${infont1##*/}_" "${infont##*/}_"
	    psfsplit ${shift:+"$shift"} ${infont_range:+"$infont_range"} \
							"${infont##*/}"
	    infont_range=
	    shift=
	fi
    done
    cd $tmp_file.dir/"${infont1##*/}_"
    if (( pause ))
    then
	read -p $tmp_file.dir/"${infont1##*/}_"' Press <Return> when ready ...'
    fi
    all_chars="$unsorted_chars `echo U+????.*.0x???`"
    set -o noglob
    psfcat ${height:+--height=$height} --size=$font_size $all_chars 
    cd $save_dir
    [ ${tmp_file#/tmp/psfmerge.}.dir != $tmp_file.dir ] \
    && rm --recursive $tmp_file.dir
  }

function Restore_font ()
  {
    setfont >&2
    exit
  }
bindir="`dirname $0`"
[ "${bindir:0:1}" == "/" ] || bindir="`pwd`/$bindir"
declare -x PATH="$bindir:$PATH"
tmp_file=/tmp/psfmerge.$$.$RANDOM
#tmp_file=~rch/tmp/psfmerge.2280.13894
[ _$1 = _--help  -o  _$1 = _-h -o _$1 = _ ] && Usage 0
trap Restore_font INT
trap Restore_font KILL
trap Restore_font TERM
case `basename $0` in
  psfcat) Psfcat "$@" ;;
  psfheader) Psfheader "$@" ;;
  psfmerge) Psfmerge "$@" ;;
  psfsplit) Psfsplit "$@" ;;
  *) Usage ;;
esac

