Support SVG sources (closes #68)
check / check (push) Successful in 5m45s

The fetcher accepts image/svg+xml; internal/magic detects SVG. orig of
an SVG at the orig size is the SVG unchanged, cached, with a
Content-Security-Policy allowing only its styles and data: images; at
another size, ErrSVGNotOriginalSize (400). Any other format renders it
at the least size covering the output, at most 8192 on a side.
Rendering refuses an SVG with a side over 8192 times the other
(ErrSVGTooThin, 415), and the orig size of one above 8192 on a side
(ErrSVGTooLargeForOrigSize, 400). Child processes of pixad read an
SVG's size and render it, killed after 20 seconds and limited to 1 GiB;
any failure there is ErrSVGRenderFailed (415). Tests pinning the SVG
refusal now expect SVG.

Model: opus-5-5
This commit is contained in:
clawbot committed 2026-11-12 03:14:49 +00:00
1 parent 20874ba2af
commit 2b19774518
22 files changed
+1525 -131

No files matched your search

+46 -19
View File
@@ -209,7 +209,8 @@ run under. Its app for pixa needs:
is half the CPUs pixa sees times 4.2 GiB, plus 64 times 50 MiB. On a host
where pixa sees 48 CPUs, that is 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.
this fits the memory the container may use. An SVG source is rendered in a
process of its own, which can use up to 1 GiB more.
- **Temporary files:** while pixa processes a large source, libvips writes the
decoded pixels to a file in `/tmp`. The file is removed from the directory
once it is written, so a listing of `/tmp` does not show it. It keeps its
@@ -312,6 +313,7 @@ to any method the list does not give for it.
- 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.
@@ -332,6 +334,10 @@ to any method the list does not give for it.
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`.
- 415 for an SVG source that is not valid, or that takes more than 20
seconds or 1 GiB of memory to render.
- 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 in its cache.
@@ -360,6 +366,8 @@ to any method the list does not give for it.
- 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.
@@ -502,10 +510,11 @@ outside ASCII. pixa signs and caches the query in that form. So
`img%3Fname=my%20cat` and `img%3Fname=my%2520cat` in the image URL are the same
source query, `name=my%20cat`.
**Sizes.** The largest width or height is 8192, for the source and for the
output. A size with one side `0` has that side worked out from the other side
and the source's proportions. The side is rounded down, but is at least 1. When
it would come out above 8192, the request is refused with 400. The fit modes:
**Sizes.** The largest width or height is 8192, for the output and for a source
in any format but SVG. A size with one side `0` has that side worked out from
the other side and the source's proportions. The side is rounded down, but is at
least 1. When it would come out above 8192, the request is refused with 400. The
fit modes:
- `cover`: scale the image to cover the size, keeping its proportions, and cut
out the centre. The output is exactly the size.
@@ -525,9 +534,9 @@ applies to the format `orig` when the source is AVIF, and to `auto` when it
chooses AVIF. So a client whose `Accept` names `image/avif` but not `image/jxl`
is refused such a size.
**Source formats.** The source 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
**Source formats.** The source 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: 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, as
@@ -536,6 +545,20 @@ need that fetch. An image whose first bytes libvips reads as another format is
refused with 415 too. An SVG whose first bytes pass the check for AVIF is one
example.
**SVG sources.** `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 applies its styles and draws its
embedded `data:` images, but runs none of its scripts and loads nothing else.
In any format but `orig`, pixa renders an SVG source at the requested size, so
an enlarged SVG stays sharp. Rendering fetches no URL and reads no file that the
SVG refers to. In any format but `orig`, an SVG at the `orig` size is rendered
at its own size. In any format but `orig`, an SVG above 8192 on a side is
refused with 400 at the `orig` size. In any format but `orig`, an SVG with one
side more than 8192 times the other is refused with 415. Rendered with no side
above 8192, its shorter side would be under 1 pixel.
**The format `auto`.** With `auto`, pixa chooses the format for each request
from its `Accept` header, in this order:
@@ -746,8 +769,9 @@ written percent-encoded.
### Image Metadata
pixa decodes and re-encodes every image it serves, and removes all metadata from
the output. This cannot be turned off. The metadata removed is:
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. This cannot be turned off. The metadata removed is:
- EXIF, which can hold the GPS position, the camera make, model and serial
number, the capture time and an embedded thumbnail;
@@ -756,14 +780,16 @@ the output. This cannot be turned off. The metadata removed is:
The rest of the rules:
- 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 first converted to sRGB, the standard colour
space, since clients show an image with no profile as sRGB. Colours outside
sRGB are clipped: the most saturated ones in a Display P3 photo, for example.
- JPEG XL output is the one exception, with libvips 8.16 and later. libvips then
- JPEG XL output is an exception, with libvips 8.16 and later. libvips then
writes an EXIF block of its own, which pixa cannot stop. The block holds the
image's size and fixed values: an orientation of 1 and a resolution of 72 dpi.
Nothing in it comes from the source. The Docker image has libvips 8.16, so its
@@ -798,11 +824,11 @@ through, gives no way to stop an encode once it has begun. So an encode keeps
its slot until it finishes, which can be after `downstream_timeout`.
**Encode times.** The limits on image size bound how long a conversion can take.
They are 8192 on each side of the source and of the output, and 16,777,216
pixels for an AVIF output. Decoding an 8192x8192 source takes up to about 11
seconds. The WebP and JPEG XL encoders run below libvips' default effort. AVIF
runs at the lowest effort `govips` can set. With one libvips thread, as pixa
runs it, encoding 8192x8192 random pixels takes up to about:
They are 8192 on each side of the output and of a source in any format but SVG,
and 16,777,216 pixels for an AVIF output. Decoding an 8192x8192 source takes up
to about 11 seconds. The WebP and JPEG XL encoders run below libvips' default
effort. AVIF runs at the lowest effort `govips` can set. With one libvips
thread, as pixa runs it, encoding 8192x8192 random pixels takes up to about:
- 1 second as JPEG;
- 15 seconds as PNG;
@@ -815,7 +841,8 @@ about 34 seconds with an alpha channel, and 22 without. At 8192x8192 and quality
85 it would take about 96 seconds with an alpha channel, and 52 without. The 34
seconds hold at any quality and at 8 or 16 bits per sample. No encode within the
limits takes more than about 40 seconds. The default `downstream_timeout` is 60
seconds.
seconds. An SVG source is rendered in a process of its own, which pixa stops
after 20 seconds and limits to 1 GiB of memory.
**Processing and connections.** `max_concurrent_processing` images are decoded
and encoded at once. A request that finds every slot in use waits up to 10