From b78ccb662b2359a2cb5f60c4040c30004b43a412 Mon Sep 17 00:00:00 2001 From: Yuriy Andamasov Date: Fri, 10 Jul 2026 19:01:35 +0300 Subject: docs-infra: convert webp/svg images for PDF builds (sphinx.ext.imgconverter + ImageMagick/librsvg) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated by [robots](https://vyos.io) --- docker/Dockerfile | 12 +++++++++++- docker/im-convert.sh | 47 +++++++++++++++++++++++++++++++++++++++++++++++ docs/conf.py | 26 ++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 docker/im-convert.sh diff --git a/docker/Dockerfile b/docker/Dockerfile index fee5c91c..8dddc8a8 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -25,7 +25,9 @@ RUN apt-get update && apt-get install -y \ gosu \ graphviz \ curl \ - dos2unix + dos2unix \ + imagemagick \ + librsvg2-bin RUN pip3 install --break-system-packages \ Sphinx \ @@ -54,4 +56,12 @@ COPY entrypoint.sh /usr/local/bin/entrypoint.sh # "no such file or directory" RUN dos2unix /usr/local/bin/entrypoint.sh +# sphinx.ext.imgconverter's `image_converter` command (docs/conf.py sets +# image_converter = 'im-convert'). Routes .svg sources to rsvg-convert +# directly, since Debian's imagemagick package is built --without-rsvg and +# its built-in SVG coder can't handle embedded base64 raster +# elements. See docker/im-convert.sh for the full rationale. +COPY im-convert.sh /usr/local/bin/im-convert +RUN dos2unix /usr/local/bin/im-convert && chmod +x /usr/local/bin/im-convert + ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] diff --git a/docker/im-convert.sh b/docker/im-convert.sh new file mode 100644 index 00000000..e64f5f31 --- /dev/null +++ b/docker/im-convert.sh @@ -0,0 +1,47 @@ +#!/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 "[0]" "" (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 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. +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" + ;; + *) + exec convert "$src" "$dst" + ;; +esac diff --git a/docs/conf.py b/docs/conf.py index cefd1d84..c61b8984 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -53,6 +53,18 @@ extensions = ['sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.ifconfig', 'sphinx.ext.graphviz', + # LaTeX-only: converts image formats the LaTeX/PDF builder can't + # embed natively (webp, svg, ...) to PNG at build time. The + # LaTeX builder's supported_image_types is ['application/pdf', + # 'image/png', 'image/jpeg'] — without this, unsupported + # images (nearly all of ours are .webp) are silently dropped + # from the PDF output. No effect on the HTML builder, which + # supports webp/svg natively in the browser; imgconverter + # only fires post-transforms when the active builder's + # supported_image_types doesn't already cover the source + # format. See `image_converter` below + docker/im-convert.sh + # for the conversion command this depends on. + 'sphinx.ext.imgconverter', 'notfound.extension', 'autosectionlabel', 'myst_parser', @@ -62,6 +74,20 @@ extensions = ['sphinx.ext.intersphinx', 'sphinx_sitemap', ] +# sphinx.ext.imgconverter: use a thin wrapper (docker/im-convert.sh, installed +# on PATH as `im-convert`) instead of ImageMagick's `convert` directly. +# Debian's `imagemagick` package is built --without-rsvg, so its built-in SVG +# coder (a minimal libxml2-based renderer, not a librsvg wrapper) can't +# rasterize SVGs that embed a base64 raster element — common in +# diagrams exported from draw.io/diagrams.net — and fails with "unable to +# open image `image/png;base64,...'". The wrapper routes .svg sources to +# `rsvg-convert` (from librsvg2-bin) directly and everything else (webp, +# gif, pdf, ...) through ImageMagick's `convert` as usual. If `im-convert` +# isn't on PATH (e.g. a build environment other than docker/Dockerfile), +# imgconverter's own `is_available()` check logs a warning and skips +# conversion rather than failing the build. +image_converter = 'im-convert' + # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] -- cgit v1.2.3 From 6ed37d5e703059af84c7cdd986226cabe5ca8e24 Mon Sep 17 00:00:00 2001 From: Yuriy Andamasov Date: Fri, 10 Jul 2026 19:19:12 +0300 Subject: docs-infra: cap converted-image resolution — PDF must stay under the 25 MiB asset cap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated by [robots](https://vyos.io) --- docker/im-convert.sh | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/docker/im-convert.sh b/docker/im-convert.sh index e64f5f31..7930b8be 100644 --- a/docker/im-convert.sh +++ b/docker/im-convert.sh @@ -18,6 +18,21 @@ # 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 @@ -42,6 +57,12 @@ case "$plain_src" in exec rsvg-convert -o "$dst" "$plain_src" ;; *) - exec convert "$src" "$dst" + # 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 -- cgit v1.2.3 From 9e38b7190a285c7e2e3590d0381ad7892d5b0e8c Mon Sep 17 00:00:00 2001 From: Yuriy Andamasov Date: Fri, 10 Jul 2026 19:26:57 +0300 Subject: docs-infra: install image-conversion packages with --no-install-recommends MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated by [robots](https://vyos.io) --- docker/Dockerfile | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 8dddc8a8..e0947ac7 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -25,7 +25,13 @@ RUN apt-get update && apt-get install -y \ gosu \ graphviz \ curl \ - dos2unix \ + dos2unix + +# Image-conversion toolchain for the PDF build (sphinx.ext.imgconverter via +# docker/im-convert.sh). Installed with --no-install-recommends (Trivy +# DS-0029) on a separate line: the texlive line above deliberately keeps +# recommends, which carry font packages LaTeX needs. +RUN apt-get update && apt-get install -y --no-install-recommends \ imagemagick \ librsvg2-bin -- cgit v1.2.3