diff options
Diffstat (limited to 'bin')
-rwxr-xr-x | bin/hc | 209 |
1 files changed, 209 insertions, 0 deletions
@@ -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 "$@" |