Support SVG sources (closes #68)
check / check (push) Canceled after 0s

The fetcher accepts image/svg+xml again and internal/magic detects SVG.
A raster format renders the SVG at the least size that covers the
output, held to 8192 on a side, through libvips' thumbnail, which
renders at any scale, so an enlarged SVG stays sharp. An SVG's own size
is not limited; one with a side more than 8192 times the other is
ErrSVGTooThin, answered 415. orig of an SVG at the orig size is the SVG
unchanged, cached as a variant; at any other size it is
ErrSVGNotOriginalSize, answered 400. The cache reads up to 1024 first
bytes of a file to find its format. Render time and memory grow with
what the SVG draws: P1 in TODO.md. Tests that pinned SVG as refused now
expect it to be read.

Model: opus-5-5
This commit is contained in:
sneak committed 2026-11-11 10:08:22 +00:00
1 parent 9f1e9f5253
commit c0560cae4d
20 files changed
+812 -85

No files matched your search

+35 -12
View File
@@ -147,7 +147,7 @@ What the [upaas](https://git.eeqj.de/sneak/upaas) app for pixa needs:
times 50 MiB: on a host where pixa sees 48 CPUs, 24 times 4.2 GiB plus 3.1
GiB, about 104 GiB. Set `PIXA_MAX_CONCURRENT_PROCESSING` and
`PIXA_UPSTREAM_CONNECTIONS` so that this fits the memory the container may
use.
use. Rendering an SVG source can need more, depending on what it draws.
- **Temporary files:** while pixa processes a large source, libvips writes its
decoded pixels to a file in `/tmp`. The file disappears from `/tmp` once it is
written, so a listing of `/tmp` does not show it, but it keeps its space, on
@@ -307,6 +307,7 @@ path under `/v1/` answers 200, in maintenance mode too.
- 304 when `If-None-Match` matches the image's `ETag`.
- 400 for a URL or parameter that is not valid.
- 400 when the format is `auto` and the `Accept` header is not valid.
- 400 for `orig` of an SVG source at a size other than `orig`.
- 406 when the format is `auto` and `Accept` allows none of the formats it
chooses from.
- 401 for a missing or wrong signature.
@@ -327,6 +328,8 @@ path under `/v1/` answers 200, in maintenance mode too.
format its `Content-Type` names.
- 415 when the upstream sends an image whose first bytes libvips reads as
another format.
- 415 for an SVG source with one side more than 8192 times the other, in any
format but `orig`.
- 502 when the upstream answers with an error status.
- 502 for 5 minutes after that for the same source URL, when pixa has
neither the image nor its source image in its cache.
@@ -344,6 +347,8 @@ path under `/v1/` answers 200, in maintenance mode too.
- 304 when `If-None-Match` matches the image's `ETag`.
- 400 for a token that does not decrypt.
- 400 for a token that asks for a size or fit that is not valid.
- 400 for a token that asks for `orig` of an SVG source at a size other than
`orig`.
- 410 once the token has expired.
- 400 when the token's format is `auto` and the `Accept` header is not
valid.
@@ -457,9 +462,9 @@ more than 40 seconds (see below). That includes `orig` of an AVIF source, and
`auto` when it chooses AVIF, so a client whose `Accept` names `image/avif` but
not `image/jxl` is refused such a size.
The source image may be JPEG, PNG, GIF, WebP, AVIF or JPEG XL. The upstream's
`Content-Type` must name its format, and the image's first bytes must match it.
An upstream answering with any other `Content-Type`, SVG included, is refused
The source image may be JPEG, PNG, GIF, WebP, AVIF, JPEG XL or SVG. The
upstream's `Content-Type` must name its format, and the image's first bytes must
match it. An upstream answering with any other `Content-Type` is refused
with 415. So is a body whose first bytes are not those of the format its
`Content-Type` names, such as a JPEG sent as `image/png`, text sent as an image,
or an empty body. pixa then does not fetch that source URL again for 5 minutes,
@@ -468,6 +473,19 @@ would need that fetch. An image whose first bytes libvips reads as another
format, such as an SVG whose first bytes pass the check for AVIF, is refused
with 415 too.
For any format but `orig`, pixa renders an SVG source at the requested size, so
an enlarged SVG stays sharp. At the `orig` size it is rendered at its own size.
The `orig` size of an SVG above 8192 on a side is refused with 400. An SVG with
one side more than 8192 times the other is refused with 415 in those formats:
rendered with no side above 8192 pixels, its shorter side would be under 1
pixel. Rendering loads no file or URL that the SVG refers to.
`orig` of an SVG source is the SVG itself, unchanged, and is cached like any
other image. It is given only at the `orig` size, since pixa does not resize an
SVG as SVG: `orig` of an SVG at any other size is refused with 400. A browser
that opens an SVG from pixa runs none of its scripts, as pixa's
`Content-Security-Policy` allows no inline script.
With the format `auto`, pixa chooses the format for each request from its
`Accept` header, in this order:
@@ -522,7 +540,9 @@ can set, so that no encode takes more than 40 seconds of the default
WebP and 27 as JPEG XL, and 4096x4096, 8192x2048 or 2048x8192 up to about 34 as
AVIF, which at quality 85 would take about 96 at 8192x8192. Without an alpha
channel they take up to about 16, 13 and 22 seconds. Decoding such a source adds
up to about 11 seconds.
up to about 11 seconds. These limits do not bound rendering an SVG source, which
happens within the encode: its time and memory grow with what the SVG draws,
such as filter effects.
The login form (`POST /`) is limited to 5 attempts per minute per client
address, counting an IPv6 client by its /64; an attempt over the limit is
@@ -604,21 +624,24 @@ session.
### Image Metadata
pixa decodes and re-encodes every image it serves, and removes all metadata from
the output: EXIF (GPS position, camera make, model and serial number, capture
pixa decodes and re-encodes every image it serves, except `orig` of an SVG
source, which is the SVG unchanged. It removes all metadata from every image it
re-encodes: EXIF (GPS position, camera make, model and serial number, capture
time, embedded thumbnail), XMP, IPTC and the ICC colour profile. This cannot be
turned off.
- The `orig` format means the source's own format, not the source's bytes: an
`orig` image is re-encoded and stripped like any other.
- For a source in any format but SVG, the `orig` format means the source's own
format, not the source's bytes: an `orig` image is re-encoded and stripped
like any other. For an SVG source, `orig` is the SVG's own bytes, its metadata
included.
- An image with an EXIF orientation is turned upright first, so it displays the
same without the tag; a requested size applies to the upright image.
- An image with an ICC profile is converted to sRGB first, since clients show an
image with no profile as sRGB. Colours outside sRGB, such as the most
saturated ones in a Display P3 photo, are clipped.
- The one exception is JPEG XL with libvips 8.16 and later: libvips then writes
an EXIF block of its own into the image, as govips cannot ask libvips to leave
it out. It holds the image's size and otherwise fixed values, such as an
- JPEG XL is an exception with libvips 8.16 and later: libvips then writes an
EXIF block of its own into the image, as govips cannot ask libvips to leave it
out. It holds the image's size and otherwise fixed values, such as an
orientation of 1 and a resolution of 72 dpi, and nothing from the source.
### Source Hosts