summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xbin/hc209
1 files changed, 209 insertions, 0 deletions
diff --git a/bin/hc b/bin/hc
new file mode 100755
index 0000000..bf1006d
--- /dev/null
+++ b/bin/hc
@@ -0,0 +1,209 @@
+#! /bin/sh
+#
+# hc - hard copy helper
+#
+# SYNOPSIS
+# hc {-e|--encode} [ENCODE_OPTION...] < FILE
+# hc {-d|--decode} IMAGE_FILE...
+#
+# DESCRIPTION
+# hc encodes arbitrary data for printing, and decodes scanned images back
+# into data.
+#
+# ENCODING
+# When invoked with the --encode flag, then hc will read data from stdin, and
+# write a PDF file to stdout. The data will be encoded using QR codes.
+#
+# Following options are available to customize the encoding process:
+#
+# --lhead=TeX (default: empty)
+# --rhead=TeX (default: empty)
+# --lfoot=TeX (default: current date in ISO 8601 format.
+# --rfoot=TeX (default: page numbering)
+# TeX code to use for headers and footers.
+#
+# --show-texlog
+# Print TeX log.
+#
+# DECODING
+# When invoked with the --decode flag, then hc will read the specified
+# (image) files, and write the decoded data to stdout. Images can be
+# specified in any order, and might be listed multiple times.
+#
+# Sometimes hc won't be able to locate and/or decode all QR codes found in
+# the images. Problematic QR codes can be identified with
+#
+# zbarimg --display IMAGE_FILE...
+#
+# Following techniques might help to make problematic QR codes readable:
+#
+# - rescan the image
+#
+# - rotate the image (try various degrees)
+# example: convert old.tif -rotate 5 new.tif
+#
+# - increase brightness and/or contrast
+# example: convert old.tif -brightness-contrast 10x20 new.tif
+#
+# - extract the QR code manually
+# e.g. by opening the image containing the problematic QR code with some
+# image viewer, zoom in, and take a screenshot (e.g. using scrot -s)
+#
+# EXAMPLES
+# echo secret | gpg -c | hc -e --lhead=mypass > /tmp/mypass.pdf
+# convert -density 288 /tmp/mypass.pdf tif:- | hc -d /dev/stdin | gpg -d
+#
+
+set -efu
+
+main() {(
+
+ _args=$(getopt -n "$0" -s sh \
+ -o de \
+ -l decode,encode,lhead:,rhead:,lfoot:,rfoot:,show-texlog \
+ -- "$@")
+ if \test $? != 0; then exit 1; fi
+ eval set -- "$_args"
+ unset _args
+
+ unset command
+
+ lhead=
+ rhead=
+ lfoot=$(date -Id)
+ rfoot='\thepage/\pageref{LastPage}'
+
+ show_texlog=false
+
+ set_command() {
+ if test -z "${command+1}"; then
+ command=$1
+ else
+ echo "hc: error: only one command can be used" >&2
+ exit 1
+ fi
+ }
+
+ while :; do case $1 in
+ -d|--decode) set_command decode; shift;;
+ -e|--encode) set_command encode; shift;;
+ --lhead) lhead=$2; shift 2;;
+ --rhead) rhead=$2; shift 2;;
+ --lfoot) lfoot=$2; shift 2;;
+ --rfoot) rfoot=$2; shift 2;;
+ --show-texlog) show_texlog=true; shift;;
+ --) shift; break;;
+ esac; done
+
+ # Separates a part's head from its body.
+ body_separator=' '
+
+ # Arbitrary limit. This number is really only needed for its width, to
+ # calculate max_body_size. The value could be made configurable.
+ max_part_number=9999
+
+ # QR Code version 40, ECC Level H, alphanumeric capacity: 1852
+ max_body_size=$(expr \
+ 1852 \
+ - ${#body_separator} \
+ - ${#max_part_number}
+ )
+
+ split_parts() {
+ base32 -w"$max_body_size" |
+ nl -s"$body_separator" -w1
+ }
+ join_parts() {
+ sort -t"$body_separator" -nu -k1 |
+ awk --field-separator "$body_separator" '
+ $1 ~ /^[1-9][0-9]*$/{
+ print "found part " $1 > "/dev/stderr"
+ for (i=2; i<=NF; i++) print $i
+ }
+ ' |
+ base32 -d
+ }
+
+ cmd_"$command" "$@"
+)}
+
+cmd_encode() {(
+ unset workdir
+ trap cleanup EXIT
+ cleanup () {
+ { echo "$part_files" | xargs --no-run-if-empty rm -v || :
+ rm -v "$workdir/out.aux" || :
+ rm -v "$workdir/out.log" || :
+ rm -v "$workdir/out.pdf" || :
+ rm -v "$workdir/out.tex" || :
+ rmdir -v "$workdir" || :
+ trap - EXIT
+ } >&2
+ }
+ workdir=$(mktemp --tmpdir -d hc.decode.XXXXXXXX)
+ cd "$workdir"
+
+ part_files=$(split_parts | nl -w1 | {
+ while read -r i part; do
+ f=$workdir/part-$i.png
+ printf %s "$part" |
+ qrencode \
+ --level=H \
+ --margin=5 \
+ --output="$f" \
+ --verbose
+ echo "$f"
+ done
+ })
+
+ {
+ echo '\documentclass[paper=a4]{scrartcl}'
+ echo '\usepackage{lastpage}'
+ echo '\usepackage{graphicx}'
+ echo '\setlength{\textheight}{640pt}'
+ echo '\setlength{\parindent}{0mm}'
+ echo '\usepackage[headsepline,footsepline,plainfootsepline]{scrlayer-scrpage}'
+ echo '\def\chead#1{\cohead{#1}\cehead{#1}}'
+ echo '\def\lhead#1{\lohead{#1}\lehead{#1}}'
+ echo '\def\rhead#1{\rohead{#1}\rehead{#1}}'
+ echo '\def\cfoot#1{\cofoot{#1}\cefoot{#1}}'
+ echo '\def\lfoot#1{\lofoot{#1}\lefoot{#1}}'
+ echo '\def\rfoot#1{\rofoot{#1}\refoot{#1}}'
+ echo '\chead{}'
+ echo '\lhead{\texttt{'"$lhead"'}}'
+ echo '\rhead{\texttt{'"$rhead"'}}'
+ echo '\cfoot{}'
+ echo '\lfoot{\texttt{'"$lfoot"'}}'
+ echo '\rfoot{\texttt{'"$rfoot"'}}'
+ echo '\begin{document}'
+ echo '\begin{flushleft}'
+ echo "$part_files" | while read -r f; do
+ echo '\includegraphics[scale=.368]{'"$f"'}'
+ done
+ echo '\end{flushleft}'
+ echo '\end{document}'
+ } > out.tex
+
+ while :; do
+ pdflatex \
+ -no-shell-escape \
+ -interaction=batchmode \
+ out.tex \
+ >&2
+ if test "$show_texlog" = true; then
+ cat out.log >&2
+ fi
+ if grep -q "LaTeX Warning: Reference \`LastPage' on page 1 undefined on input line \\([0-9]*\\)." out.log; then
+ continue
+ fi
+ break
+ done
+
+ cat out.pdf
+)}
+
+cmd_decode() {(
+ zbarimg --raw "$@" | join_parts
+)}
+
+main "$@"