| Marc Kupietz | c0ae3c2 | 2026-07-17 11:47:07 +0200 | [diff] [blame] | 1 | #!/bin/sh -eu |
| 2 | # pdf-lighten-black IN.pdf [OUT.pdf] [GREY] |
| 3 | # |
| 4 | # Produce a copy of IN.pdf in which all *vector* pure black is replaced by a |
| Marc Kupietz | 1bd536e | 2026-08-05 17:31:47 +0200 | [diff] [blame] | 5 | # dark grey (default 10% = #1a1a1a). Intended for printing on fabric, where |
| Marc Kupietz | c0ae3c2 | 2026-07-17 11:47:07 +0200 | [diff] [blame] | 6 | # solid black ink bleeds out, making QR codes hard to scan and text fuzzy. |
| 7 | # |
| 8 | # Works on the PDF content streams directly (qpdf QDF round-trip), so text |
| 9 | # stays text and vector graphics stay vector -- nothing is rasterized and the |
| 10 | # file works at any print resolution. Colors inside embedded raster images |
| 11 | # (e.g. screenshots) are not touched. |
| 12 | # |
| Marc Kupietz | 1bd536e | 2026-08-05 17:31:47 +0200 | [diff] [blame] | 13 | # GREY is the target grey level in [0,1) per RGB channel (0.10 = 10% = #1a1a1a). |
| Marc Kupietz | c0ae3c2 | 2026-07-17 11:47:07 +0200 | [diff] [blame] | 14 | |
| 15 | in=$1 |
| 16 | out=${2:-${in%.pdf}_fabric.pdf} |
| Marc Kupietz | 1bd536e | 2026-08-05 17:31:47 +0200 | [diff] [blame] | 17 | grey=${3:-0.10} |
| Marc Kupietz | c0ae3c2 | 2026-07-17 11:47:07 +0200 | [diff] [blame] | 18 | |
| 19 | k=$(awk -v g="$grey" 'BEGIN { printf "%.4g", 1 - g }') |
| 20 | |
| 21 | tmpdir=$(mktemp -d) |
| 22 | trap 'rm -rf "$tmpdir"' EXIT |
| 23 | |
| 24 | # QDF mode writes an editable, uncompressed PDF whose stream lengths can be |
| 25 | # repaired by fix-qdf after editing. |
| 26 | qpdf --qdf --object-streams=disable "$in" "$tmpdir/in.qdf" |
| 27 | |
| 28 | # Rewrite the color-setting operators for pure black: |
| 29 | # `0 0 0 rg/RG` (DeviceRGB), `0 g/G` (DeviceGray), `0 0 0 sc/scn` variants, |
| 30 | # and `0 0 0 1 k/K` (DeviceCMYK, mapped to (1-GREY) K). |
| 31 | # The lookarounds keep numbers like `10 g` or names like `/GS0 gs` intact. |
| 32 | perl -pe " |
| 33 | s/(?<![\\d.])0 0 0 (rg|RG|scn|sc|SCN|SC)(?![\\w])/$grey $grey $grey \$1/g; |
| 34 | s/(?<![\\d.])0 (g|G)(?![\\w])/$grey \$1/g; |
| 35 | s/(?<![\\d.])0 0 0 1 (k|K)(?![\\w])/0 0 0 $k \$1/g; |
| 36 | " "$tmpdir/in.qdf" | fix-qdf > "$tmpdir/grey.qdf" |
| 37 | |
| 38 | # Rewrite as a normal, compressed PDF. |
| 39 | qpdf "$tmpdir/grey.qdf" "$out" |
| 40 | |
| 41 | echo "pdf-lighten-black: wrote $out (black -> ${grey} grey)" >&2 |