summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYevhen Bondarenko <evgeniy.bondarenko@sentrium.io>2026-07-11 12:08:22 +0200
committerGitHub <noreply@github.com>2026-07-11 12:08:22 +0200
commit91124373b35fb9f90f93b1ecde5c69d1b366d6d6 (patch)
treefeb0c9d7ee29cca29fcbe309e6b7242862d272e9
parentfa98eb5d526599021235c9cec929e99260f61d97 (diff)
parent028cba2b881ba1beeaa21f6d0fd5eccf7fdc2f25 (diff)
downloadvyos-documentation-91124373b35fb9f90f93b1ecde5c69d1b366d6d6.tar.gz
vyos-documentation-91124373b35fb9f90f93b1ecde5c69d1b366d6d6.zip
Merge pull request #2145 from vyos/claude/pdf-image-conversion
docs: render diagrams in PDF builds (imgconverter + ImageMagick/librsvg)
-rw-r--r--docker/Dockerfile17
-rw-r--r--docker/im-convert.sh68
-rw-r--r--docs/conf.py26
3 files changed, 109 insertions, 2 deletions
diff --git a/docker/Dockerfile b/docker/Dockerfile
index ba79bbb1..8598b808 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -27,9 +27,14 @@ RUN apt-get update && apt-get install -y \
curl \
dos2unix
-# pdfinfo (PDF page-count validation in the docs-build workflow) ships in the
-# main poppler-utils package — no recommends needed.
+# PDF-build toolchain extras, 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:
+# - imagemagick + librsvg2-bin: sphinx.ext.imgconverter via docker/im-convert.sh
+# - poppler-utils: pdfinfo for the docs-build workflow's page-count validation
RUN apt-get update && apt-get install -y --no-install-recommends \
+ imagemagick \
+ librsvg2-bin \
poppler-utils
RUN pip3 install --break-system-packages \
@@ -59,4 +64,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 <image>
+# 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..7930b8be
--- /dev/null
+++ b/docker/im-convert.sh
@@ -0,0 +1,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
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 <image> 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']