Satori
API
Satori converts HTML and CSS into SVG.
import satori from 'satori'
const svg = await satori(
<div style={{ color: 'black' }}>hello, world</div>,
{
width: 600,
height: 400,
fonts: [
{
name: 'Roboto',
data: robotoArrayBuffer,
weight: 400,
style: 'normal',
},
...
],
embedFont: true,
debug: false,
graphemeImages: {},
loadAdditionalAsset,
},
)
Which yields:
'<svg ...><path d="..." fill="black"></path></svg>'
Text (with font data) will be embedded in the SVG as paths.
Playground
https://satori-playground.vercel.app
Documentation
JSX
Satori only accepts JSX elements that are pure and stateless. You can use a subset of HTML
elements (see section below), or custom React components, but React APIs such as useState
and
useEffect
are not supported.
Use without JSX
If you don't have JSX transpiler enabled, you can simply pass React-elements-like objects that have type
, props.children
and props.style
(and other properties too) directly:
await satori(
{
type: 'div',
props: {
children: 'hello, world',
style: { color: 'black' },
},
},
options
)
HTML Elements
Satori supports a limited subset of HTML and CSS features, due to its special use cases. In general, only these static and visible elements and properties that are implemented.
For example, the <input>
HTML element, the cursor
CSS property are not in consideration. And you can't use <style>
tags or external resources via <link>
or <script>
.
Also, Satori does not guarantee that the SVG will 100% match the browser-rendered HTML output since Satori implements its own layout engine based on the SVG 1.1 spec.
You can find the list of supported HTML elements and their preset styles here.
Images
You can use <img>
to embed images but src
, width
, and height
attributes are all required.
await satori(
<img src="https://picsum.photos/200/300" width={200} height={300} />,
options
)
When using background-image
, the image will be stretched to fit the element by default if you don't specify the size.
If you want to render the generated SVG to another image format such as PNG, it would be better to use base64 encoded image data directly as props.src
so no extra I/O is needed.
CSS
Satori uses the same Flexbox layout engine as React Native, and it’s not a complete CSS implementation. However, it supports a subset of the spec that covers most common CSS features:
Property | Supported Values |
---|
display | none , flex |
position | relative , absolute |
margin , padding | Supported |
top , right , bottom , left | Supported |
width , height | Supported |
max-width , max-height | Supported |
min-width , min-height | Supported |
border | Supported |
flex-direction | Supported |
flex-wrap | Supported |
flex-grow | Supported |
flex-shrink | Supported |
flex-basis | Supported except for auto |
align-items | Supported |
align-content | Supported |
align-self | Supported |
justify-content | Supported |
font-family | Supported |
font-size | Supported |
font-weight | Supported |
font-style | Supported |
text-align | Supported |
letter-spacing | Supported |
box-shadow | All supported except spread-radius and inset (works like drop-shadow ) |
border-radius | Supported |
overflow | visible , hidden |
color | Supported |
transform | Support absolute values |
transform-origin | Support one-value and two-value syntax (both relative and absolute values) |
object-fit | contain , cover , none |
opacity | Supported |
background-color | Supported |
background-image | Support linear-gradient , radial-gradient , url |
word-break | Supported |
text-shadow | Supported |
text-transform | Support lowercase , uppercase , capitalize |
background-position | Supported |
background-size | Support two-value size string such as 10px 20% |
white-space | Support normal , pre , pre-wrap and nowrap |
text-overflow | Support clip and ellipsis |
text-decoration | Support line types underline and line-through , and styles dotted , dashed , solid |
line-height | Supported |
background-clip | Support border-box and text |
background-repeat | Supported |
filter | Supported |
Note:
- Three-dimensional transforms are not supported.
- There is no
z-index
support in SVG. Elements that come later in the document will be painted on top. box-sizing
is set to border-box
for all elements.calc
isn't supported.overflow: hidden
and transform
can't be used together.
Typography
Advanced typography features such as kerning, ligatures and other OpenType features are not currently supported.
RTL languages are not supported either.
Fonts
Satori currently supports three font formats: TTF, OTF and WOFF. Note that WOFF2 is not supported at the moment. You must specify the font if any text is rendered with Satori, and pass the font data as ArrayBuffer (web) or Buffer (Node.js):
await satori(
<div style={{ fontFamily: 'Inter' }}>Hello</div>,
{
width: 600,
height: 400,
fonts: [
{
name: 'Inter',
data: inter,
weight: 400,
style: 'normal',
},
{
name: 'Inter',
data: interBold,
weight: 700,
style: 'normal',
},
],
}
)
Multiple fonts can be passed to Satori and used in fontFamily
.
Emojis
To render custom images for specific graphemes, you can use graphemeImages
option to map the grapheme to an image source:
await satori(
<div>Next.js is 🤯!</div>,
{
...,
graphemeImages: {
'🤯': 'https://twemoji.maxcdn.com/v/13.1.0/svg/1f92f.svg',
},
}
)
The image will be resized to the current font-size (both width and height), so it must be a square.
Dynamically Load Emojis and Fonts
Satori supports dynamically loading emoji images (grapheme pictures) and fonts. The loadAdditionalAsset
function will be called when a text segment is rendered but missing the image or font:
await satori(
<div>👋 你好</div>,
{
loadAdditionalAsset: async (code: string, segment: string) => {
if (code === 'emoji') {
return `data:image/svg+xml;base64,...`
}
return loadFontFromSystem(code)
}
}
)
Runtime and WASM
Satori can be used in browser, Node.js (>= 16), and Web Workers.
By default, Satori depends on asm.js for the browser runtime, and native module in Node.js. However, you can optionally load WASM instead by importing satori/wasm
and provide the initialized WASM module instance of Yoga to Satori:
import satori, { init } from 'satori/wasm'
import initYoga from 'yoga-wasm-web'
const yoga = initYoga(await fetch('/yoga.wasm').then(res => res.arrayBuffer()))
init(yoga)
await satori(...)
Contribute
This project uses pnpm. To install dependencies, run:
pnpm install
To start the playground locally, run:
cd playground
pnpm dev
And visit localhost:3000/test.
To start the development mode, run pnpm dev
in the root directory (can be used together with the playground to view it in live).
To start and live-watch the tests, run:
pnpm dev:test
Author