1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
#!/bin/bash
# Wrapper invoked by sphinx.ext.imgconverter as the `image_converter` command
# (docs/conf.py sets image_converter = 'im-convert').
#
# Sphinx calls it as: im-convert "<src>[0]" "<dst>" (the "[0]" frame-index
# suffix is ImageMagick syntax for "first frame/page", appended unconditionally
# by sphinx/ext/imgconverter.py).
#
# Debian's `imagemagick` package is built --without-rsvg (LGPL licensing), so
# its own SVG coder is a minimal libxml2-based renderer rather than a wrapper
# around librsvg. That built-in coder cannot handle SVGs with an embedded
# base64-encoded raster <image> element (common in diagrams exported from
# draw.io/diagrams.net): it fails with
# "convert-im6.q16: unable to open image `image/png;base64,...'"
# because it tries to open the data-URI payload as if it were a file path.
# `rsvg-convert` (from librsvg2-bin) handles the same file correctly.
#
# Route .svg sources through rsvg-convert directly; everything else (webp,
# gif, pdf, ...) goes through ImageMagick's `convert` as before — webp support
# is compiled into this ImageMagick build, so that path needs no delegate.
#
# Output size is constrained: the docs pipeline's per-file size gate
# (scripts/docs_gates/gates.py) enforces the Cloudflare Workers 25 MiB
# asset cap on the built PDF, and naive full-resolution truecolor PNG
# conversion inflates it to ~183 MiB (the ~170 source .webp files are
# lossy-compressed; decoded to truecolor PNG they explode ~6x). The raster
# policy below (cap at 1000px on the long edge — only shrinking, never
# enlarging — plus non-dithered 128-color palette quantization and max PNG
# compression) brings the summed image payload to ~10 MiB (~15 MiB final
# PDF). Sphinx's imgconverter fixes the destination extension to .png (from
# the conversion rule's target mimetype) and pdflatex picks its decoder by
# extension, so switching photographic sources to JPEG is not available
# here — resolution + palette are the levers. 1000px at the PDF's ~6.3in
# text width is ~158 dpi; spot-checked legible on screenshots, diagrams,
# and photos alike.
set -euo pipefail
# sphinx.ext.imgconverter's is_available() probes the converter with a
# single-arg call (`im-convert -version`) before ever doing a real
# conversion. Delegate straight to ImageMagick's own -version so the probe
# succeeds — is_available()'s result is cached at the class level for the
# whole build, so a failed probe here silently disables ALL conversion
# (webp included), not just SVG.
if [ "$#" -lt 2 ]; then
exec convert "$@"
fi
src=$1
dst=$2
# Strip ImageMagick's trailing frame-index suffix, e.g.
# "/path/to/file.svg[0]" -> "/path/to/file.svg".
plain_src=${src%\[*\]}
case "$plain_src" in
*.svg|*.SVG)
exec rsvg-convert -o "$dst" "$plain_src"
;;
*)
# See the size-cap rationale in the header comment. `1000x1000>`
# only shrinks images larger than 1000px on either edge; `+dither`
# disables dithering (IM6 semantics: -dither enables, +dither
# disables), which quantizes flat UI colors cleanly AND compresses
# far better than dithered output; `-quality 95` for PNG means
# zlib level 9 + adaptive row filtering.
exec convert "$src" -resize '1000x1000>' -strip +dither -colors 128 -quality 95 "$dst"
;;
esac
|