Serve SVG originals like any other image: resized, cropped, and encoded as
PNG, WebP, AVIF, or JPEG. An SVG renders straight at the size each request
needs, so it stays sharp at any size. This guide assumes ImagePipe is running
in your app (see Getting started with Phoenix)
or as image_pipe_server.
Install SVG support
Plug
Add image_pipe_svg next to image_pipe:
# mix.exs
defp deps do
[
{:image_pipe, "~> 0.2.0"},
{:image_pipe_svg, "~> 0.1"}
]
endimage_pipe_svg downloads a precompiled native library, so the build needs
no Rust toolchain. ImagePipe renders SVG originals as soon as the package is
compiled in. Without the package, an SVG original fails with 415.
image_pipe_server
Every server image renders SVG originals.
Add fonts for SVG text
SVG text renders only in fonts you supply. ImagePipe never uses the machine's own fonts, so the same SVG renders the same everywhere. Without fonts, SVG text isn't drawn at all.
Point font_dirs at directories of TrueType or OpenType files (.ttf,
.otf, .ttc, .otc), which are searched with their subdirectories. Set
fallback_family to one of those fonts for text in a family you didn't
supply, or in a generic family such as sans-serif:
Plug
# lib/my_app/application.ex
children = [
{ImagePipe,
name: MyApp.Images,
sources: [...],
svg: [font_dirs: ["/usr/share/fonts/noto"], fallback_family: "Noto Sans"]},
MyAppWeb.Endpoint
]image_pipe_server
[processing.svg]
font_dirs = ["/fonts"]
fallback_family = "Noto Sans"Mount the font directory into the container:
docker run --read-only --tmpfs /tmp -p 8080:8080 -v ./config.toml:/etc/image_pipe/config.toml:ro -v ./images:/data/images:ro -v ./fonts:/fonts:ro ghcr.io/hlindset/image_pipe_server:0.2
ImagePipe loads the fonts when it starts and keeps them until it restarts.
A directory that doesn't exist, a file that isn't a valid font, or a
fallback_family that none of the fonts has stops ImagePipe from starting.
Cache keys include the fonts and the fallback family, so changing either
makes every cached image miss once.
Limit the work an SVG can cause
A small SVG can describe a lot of work, such as hundreds of blurred layers,
so ImagePipe estimates the cost of rendering each SVG at the requested size
and rejects one that's too expensive before rendering it. The defaults suit
most images. To allow heavier SVGs, raise max_render_work, the estimated
rendering work, which defaults to about a second of rendering:
Plug
svg: [max_render_work: 500_000_000]The svg option of ImagePipe.config/1 lists the other limits.
image_pipe_server
[processing.svg]
max_render_work = 500000000The [processing] reference
lists the other limits.
An SVG original is rejected too when the whole SVG is larger than the input pixel limit at its own size, unless the request shrinks it below the limit, or has:
- more than 200,000 elements, counting reused content at each use, such as
<use>copies and markers at each point of a line - entities that expand past 1,000,000 characters
- more than 10,000 bytes of text in one
<text>element, or 1,000,000 in all - shapes that reach more than 32,767 pixels from the image at the size it renders, which the renderer can't draw
Rejected SVGs fail with 413. An SVG fails with 415 when it isn't valid
XML, nests elements more than 1,024 levels deep, chains references such as
<use> too deep, has elements that refer to each other in a loop, defines
an entity whose value holds markup, or has a number larger than 10^15.
Embed images in SVGs
ImagePipe never reads a file or opens a connection for an SVG, so references
to other files and URLs, in <image>, <use>, or CSS, render as nothing.
Embed images as data: URLs instead. They render when they are PNG, JPEG,
GIF, or WebP and their media type matches their contents, and as nothing
otherwise. Embedded SVGs nest at most two levels deep. An embedded image
over the input pixel limit, or SVGs nested deeper, fail the request with
413.
Size SVGs in requests
An SVG's own size, rounded to whole pixels, is its width and height at
96 pixels per inch, or its viewBox when those are missing, or the extent
of its content when it has neither. Crops and regions measure the SVG at that
size, and resizes keep its aspect ratio. In a very small SVG, rounding
changes that ratio: a viewBox of 0 0 1 1.5 is 1×2 pixels, so a resize
makes it twice as tall as it is wide. Give such an SVG a larger width and
height.
A resize renders the SVG at the requested size, larger than its own size
too, without enlarge, except in two
cases:
- The SVG at that size would be larger than the input pixel limit. It renders as large as the limit allows, and the rendered image is enlarged the rest of the way, so its edges blur. A crop with a large resize renders the whole SVG at the zoomed scale, so it reaches the limit sooner.
- The request trims in its first
group. The SVG renders at its
own size, and
enlargeapplies as it does to a raster image.
Without format, the result is a PNG, or
AVIF or WebP when the browser accepts them, unless an opaque
bg fills its transparency. SVG watermarks
render at the size they are drawn, so a small logo stays sharp at any
wm-scale.
Check that SVGs render
Turn on the default request log:
Plug
# lib/my_app/application.ex, in start/2
ImagePipe.Telemetry.attach_default_logger()image_pipe_server
[telemetry]
log_requests = trueThen request an SVG original at a width larger than its own. Use your
server's address, and for a Plug add the path you forwarded to
ImagePipe.Plug, such as /images:
curl -o logo.png "http://localhost:8080/w=400/src/logo.svg"
The log names what rendered (an original or a watermark), the SVG's own size, and the size it rendered at:
[info] image_pipe source svg_render: ok (original 40x20 rendered at 400x200, 0 images)A rejected SVG names the limit, such as
svg_render: processing_error (original, input_limit, svg_render_work limit).
The [:source, :svg_render] event
lists every limit.