blob: 390e6ca4986168f91cff1d4d62650973006e608c [file] [log] [blame]
Marc Kupietzc0ae3c22026-07-17 11:47:07 +02001#!/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 Kupietz1bd536e2026-08-05 17:31:47 +02005# dark grey (default 10% = #1a1a1a). Intended for printing on fabric, where
Marc Kupietzc0ae3c22026-07-17 11:47:07 +02006# 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 Kupietz1bd536e2026-08-05 17:31:47 +020013# GREY is the target grey level in [0,1) per RGB channel (0.10 = 10% = #1a1a1a).
Marc Kupietzc0ae3c22026-07-17 11:47:07 +020014
15in=$1
16out=${2:-${in%.pdf}_fabric.pdf}
Marc Kupietz1bd536e2026-08-05 17:31:47 +020017grey=${3:-0.10}
Marc Kupietzc0ae3c22026-07-17 11:47:07 +020018
19k=$(awk -v g="$grey" 'BEGIN { printf "%.4g", 1 - g }')
20
21tmpdir=$(mktemp -d)
22trap '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.
26qpdf --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.
32perl -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.
39qpdf "$tmpdir/grey.qdf" "$out"
40
41echo "pdf-lighten-black: wrote $out (black -> ${grey} grey)" >&2