#!/bin/bash
# scan2term - simulation for showing how to split ram pnm image by bunch of lines
# for showing them in terminal, simultaneously, while post-process are running.
# (C) 2026 Felix Hauri - felix@f-hauri.ch
# Licensed under terms of GPL v3. www.gnu.org
# Version: 0.1.0  --  Last update: Tue Jul 14 11:34:14 CEST 2026
#
# Usage:
#          scan2term [Resolution] [Mode] [File]
#   Resolution:   Any integer value. Default to `75`
#   Mode          Any of `Lineart`, `Gray` or `Color`, Default to `Lineart`
#   File          Any PS or PDF file. Default to embed printer-driver-pnm2ppa A4
# If run with environment variable `doRealScan` set, then run real `scanimage`
# program instead of scanner simulation:
#          doRealScan=true /path/to/scan2term.sh 300 Color
#  
# Use - `img2sixel` if your termnal support them. or `catimg`.
#     - `perl` for down image data rate to something under 1Mb/sec.
#
# Note: Enable sixels, windowops and colors in XTerm, by running:
#      xterm -ti vt340 -xrm 'XTerm*allowWindowOps: true' \
#                         -xrm 'XTerm*numColorRegisters: 256'
#
# scan2term.sh depends on: perl-base, file, catimg, netpbm, xdg-utils, sane-utils,
#        libsixel-bin, coreutils, grep, ghostscript, zstd, tesseract-ocr.
#

termPreview() {
    local resol=${1:-75} mode=${2:-Color} mmwidth=${3:-210.22} \
	  mmheight=${4:-297.3} tCaps factor cheight cwidth
    local -i lines cols byteperpoint=3 maxheight maxwidth
    printf -v mmwidth %.2f "$mmwidth"
    printf -v mmheight %.2f "$mmheight"

    [[ ${mode,,} == gr* ]] && byteperpoint=1

    # This require Xresource "XTerm*allowWindowOps: true"
    IFS=\; </dev/tty read -t .02 -rd t -sp $'\e[14;t' _ maxheight maxwidth
    [[ $maxwidth ]] || exit 1

    IFS=\; </dev/tty read -t .02 -rsd t -p $'\e[16t' _ cheight cwidth _
    if ! [[ $cwidth ]]; then
	IFS=\; </dev/tty read -rsd t -t .05 -p $'\e[18t' _ lines cols _
	[[ $cols ]] || exit 1
	cwidth=$(( maxwidth / cols ))
	cheight=$(( maxheight / lines ))
    fi

    IFS= read </dev/tty -rsd c -p $'\e[c' -t .02 tCaps
    case $tCaps in
	*';4;'* )  # Sixel capable (XTerm)
	    maxwidth=$(( maxwidth > 1000 ? 1000 : maxwidth ))
	    termView() {
		img2sixel -
		echo   # Required under XTerm but not under Konsole
	    }
	    ;;
	*';4' ) # Sixel capable (Konsole)
	    maxwidth=$(( maxwidth > 1000 ? 1000 : maxwidth ))
	    termView() {
		img2sixel -
	    }
	    ;;
	* )
	    termView() {
		catimg -w$((2 * cols)) -
	    }
	    ;;
    esac
    cols=$(( maxwidth / cwidth + ( maxwidth % cwidth ? 1 : 0 ) ))

    factor=0000$(( maxwidth * 254000000 / ${mmwidth/.} / resol   ))
    printf -v factor '%.4f' ${factor::-5}.${factor: -5};
    pamscale "$factor" |   # pamscale will promote pbm to pgm
	(
	    # Read and adapt pnm header
	    head='' width='' hlines=4
	    for (( tot=0, lne=0, cnt=1 ; cnt<hlines ; cnt++, lne++ )); do
		IFS= read -r line
		(( cnt == 1 )) && case $line in
		    P[25] ) byteperpoint=1 ;;
		esac
		((tot+=${#line}+1))
		if [[ $line == \#* ]]; then
		    ((cnt+=-1))
		elif  [[ $cnt -eq 2 ]]; then
		    height=${line#* } width="${line% *}"
		    line="${line% *} $cheight" # Adapt nbr of lns to char height
		fi
		head+="$line"$'\n'
	    done
	    # Produce many pnm files of $cheight height...
	    for (( i=0;i<=(height/cheight);i++)); do
		(
		    echo -n "$head"
		    dd count=1 conv=sync iflag=fullblock 2>/dev/null \
		       bs=$(( byteperpoint * width * cheight ))
		) |
		    termView
	    done
	)
}
testPage() {  # Embeded A4 Testpage. From printer-driver-pnm2ppa package
    if [[ -f $testFile ]] ; then
	cat "$testFile"
	return 0
    fi
    zstd -d < <(base64 -d <<-"EOTestPage"
	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
	LqdCf5swvRISG11FVmBFwaVSBU2Dl34=
	EOTestPage
	       )
}
progName="$0"
die() {
    printf >&2 '%s ERROR: %s\n' "$progName" "$*"
    exit 1;
}
if [[ $1 ]]; then
    case $1 in *[^0-9]* ) die "1st argume must be an integer!";; esac
    (( $1 )) || die "Resolution must be a not null positive integer!"
fi

# Define max output resolution to 200DPI for post-processing sample
outRes=$(( ${1:-75} > 200 ? 200 : ${1:-75} ))

dev=pbmraw
case ${2,,} in
    col* ) dev=pnmraw ;;
    gr*  ) dev=pgmraw ;;
    lin* ) dev=pbmraw ;;
esac

testFile=''
if [[ $3 ]]; then
    [[ -e $3 ]] || die "File '$3' not found."
    [[ -r $3 ]] || die "File '$3' not readable."
    case $(file -b --mime-type "$3") in
	application/postscript|application/pdf ) : ;;
	* ) die "File '$3' could not be proceeded by Ghostscript!" ;;
    esac
    testFile="$3"
fi

# shellcheck disable=SC2034
vars="$(set)" start=$EPOCHREALTIME

# Indepenant FD for graphic
exec {txtViewFd}>&1

# Real scan tool:
if [[ $doRealScan ]]; then
    scanimage --mode ${2:-Lineart} --format pnm --resolution ${1:-75} \
	      -l 0 -t .2 -x 210.22 -y 297.30
else
# Simulation scan tool ( gs + perl ):
    gs -sDEVICE=$dev -q -sOutputFile=- -dNOPAUSE -dBATCH -dSAFER -dLastPage=1 \
       -r"${1:-75}"  -  < <(testPage) -c quit |
	perl -e 'while (sysread STDIN,my($buff),128) {
		syswrite STDOUT,$buff;select undef,undef,undef,.0001 }'
fi |
# Uncoment this to show *real* overall data rate.
#   tee >( len=$(wc -c); bc -l <<<"$len / ($EPOCHREALTIME-$start)" >/dev/tty ) |

    tee >( termPreview "${@:1:2}" >&$txtViewFd ) |

    # Post-processing sample
    if (( ${1:-75} == outRes)) ; then
	cat
    else
	factor=0000$(( outRes * 100000 / ${1:-75} ))
	printf -v factor '%.4f\n' ${factor::-5}.${factor: -5}
	pamscale $factor
    fi |
    pamtopng |
    tesseract --dpi "$outRes" - "/tmp/dummyScan" pdf 2> >(
        grep -v 'Error in \(pixScanForForeground\|boxClipToRectangle\)' >&2
    )

# Uncoment this two lines to see how `termPreview` impact main bash environment.
# diff <(echo "$vars") <(set|grep -v ^vars=) |
#    sed -nE 's/^< (.*)/\o33[2m-\1\o33[0m/p;s/^> (.*)/\o33[1m+\1\o33[0m/p'

xdg-open /tmp/dummyScan.pdf 
