The scan is a bilevel 300 dpi JBIG2 layer plus lower-resolution colour, so storing every plate as 8-bit PNG only added size. tools/optimize_plates.py writes copies under work/plates-opt (bilevel to generic-mode, lossless JBIG2; genuine halftone to JPEG q88) and repoints work/pages at them; plates/ and src/ are untouched. The Dockerfile builds jbig2enc and adds qpdf, ghostscript and img2pdf. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
40 lines
2.7 KiB
Bash
Executable File
40 lines
2.7 KiB
Bash
Executable File
#!/bin/sh
|
|
# Assemble: preamble, title (p0001), colophon, remaining pages in PDF-page order,
|
|
# endnotes; compile with XeLaTeX (3 passes: marks/backrefs).
|
|
set -e
|
|
cd "$(dirname "$0")"
|
|
OUT=ross-1988-retypeset.pdf
|
|
# Two builds must never share work/: they share main.aux, and the second reads
|
|
# the first's half-written copy and dies at \begin{document} with "Undefined
|
|
# control sequence" or "File ended while scanning use of \enotez@note" — which
|
|
# reads like an unbalanced brace in a page file and is nothing of the kind.
|
|
# mkdir is atomic, so it is the lock. The way a second build actually gets
|
|
# started is mundane: someone runs `make` again while an earlier build is still
|
|
# going. Check `docker ps` before starting one — a build takes several minutes
|
|
# and gives no output until it finishes, so a running one is easy to forget.
|
|
if ! mkdir work/.buildlock 2>/dev/null; then
|
|
echo "build.sh: another build holds work/.buildlock — refusing to start." >&2
|
|
echo " check: docker ps (if nothing is running: rmdir work/.buildlock)" >&2
|
|
exit 1
|
|
fi
|
|
trap 'rmdir work/.buildlock 2>/dev/null' EXIT INT TERM
|
|
# Three full passes run below regardless, so a stale aux buys nothing.
|
|
rm -f work/main.aux work/main.toc work/main.out
|
|
python3 tools/polish.py >/dev/null # typographic pass: src/pages -> work/pages
|
|
# Re-encode the plates to match what they actually hold: bilevel type goes to
|
|
# lossless JBIG2, genuine halftone stays 8-bit as JPEG. Writes copies under
|
|
# work/plates-opt and repoints work/pages at them -- plates/ and src/ are
|
|
# never touched, so deleting work/ reverts it. Must run AFTER polish.py, which
|
|
# is what creates work/pages.
|
|
python3 tools/optimize_plates.py
|
|
{ cat src/preamble.tex
|
|
printf '%s\n' '\begin{document}\immediate\openout\unsurefile=unsure.log\immediate\openout\erratafile=errata.log\immediate\openout\qslipfile=qslips.log'
|
|
printf '\\input{%s}\n' work/pages/p0001.tex src/colophon.tex
|
|
for f in work/pages/p*.tex; do [ "$f" = work/pages/p0001.tex ] || printf '\\input{%s}\n' "$f"; done
|
|
printf '%s\n' '\notesection' '\printerrata' '\immediate\closeout\unsurefile\immediate\closeout\erratafile\immediate\closeout\qslipfile\end{document}'
|
|
} > src/main.tex
|
|
for i in 1 2; do xelatex -interaction=nonstopmode -halt-on-error -output-directory=work src/main.tex >work/pass$i.log 2>&1 || { grep -E "^!" -A3 work/pass$i.log; exit 1; }; done
|
|
xelatex -interaction=nonstopmode -halt-on-error -output-directory=work src/main.tex | grep -E "Overfull|Warning: .*(undefined|multiply)" | sort | uniq -c || true
|
|
cp work/main.pdf "$OUT"; cp work/unsure.log . 2>/dev/null || true; cp work/errata.log . 2>/dev/null || true; cp work/qslips.log . 2>/dev/null || true
|
|
echo "built $OUT: $(pdfinfo "$OUT" | grep Pages)"
|