@feliperohdee/satori 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,503 @@
1
+ **Satori**: Enlightened library to convert HTML and CSS to SVG.
2
+
3
+ ## Improvements Over Original
4
+
5
+ This fork extends the [original Satori](https://github.com/vercel/satori) with the following features and fixes:
6
+
7
+ - **`background-position` with keyword sizes** — correct positioning when combined with `background-size: cover`, `contain`, or `auto`
8
+ - **`mix-blend-mode`** — full support
9
+
10
+ ## Overview
11
+
12
+ Satori supports the JSX syntax, which makes it very straightforward to use. Here’s an overview of the basic usage:
13
+
14
+ ```jsx
15
+ // api.jsx
16
+ import satori from 'satori'
17
+
18
+ const svg = await satori(
19
+ <div style={{ color: 'black' }}>hello, world</div>,
20
+ {
21
+ width: 600,
22
+ height: 400,
23
+ fonts: [
24
+ {
25
+ name: 'Roboto',
26
+ // Use `fs` (Node.js only) or `fetch` to read the font as Buffer/ArrayBuffer and provide `data` here.
27
+ data: robotoArrayBuffer,
28
+ weight: 400,
29
+ style: 'normal',
30
+ },
31
+ ],
32
+ },
33
+ )
34
+ ```
35
+
36
+ Satori will render the element into a 600×400 SVG, and return the SVG string:
37
+
38
+ ```js
39
+ '<svg ...><path d="..." fill="black"></path></svg>'
40
+ ```
41
+
42
+ Under the hood, it handles layout calculation, font, typography and more, to generate a SVG that matches the exact same HTML and CSS in a browser.
43
+
44
+ <br/>
45
+
46
+ ## Documentation
47
+
48
+ ### JSX
49
+
50
+ Satori only accepts JSX elements that are pure and stateless. You can use a subset of HTML
51
+ elements (see section below), or custom React components, but React APIs such as `useState`, `useEffect`, `dangerouslySetInnerHTML` are not supported.
52
+
53
+ #### Experimental: builtin JSX support
54
+
55
+ Satori has an experimental JSX runtime that you can use without having to install React. You can enable it on a per-file basis with [`@jsxImportSource` pragmas](https://www.typescriptlang.org/tsconfig/#jsxImportSource). In the future, it will autocomplete only the subset of HTML elements and CSS properties that Satori supports for better type-safety.
56
+
57
+ ```tsx
58
+ /** @jsxRuntime automatic */
59
+ /** @jsxImportSource satori/jsx */
60
+
61
+ import satori from 'satori';
62
+ import { FC, JSXNode } from 'satori/jsx';
63
+
64
+ const MyComponent: FC<{ children: JSXNode }> = ({ children }) => (
65
+ <div style={{ color: 'black' }}>{children}</div>
66
+ )
67
+
68
+ const svg = await satori(
69
+ <MyComponent>hello, world</MyComponent>,
70
+ options,
71
+ )
72
+ ```
73
+
74
+ #### Use without JSX
75
+
76
+ If you don't have JSX transpiler enabled, you can simply pass [React-elements-like objects](https://reactjs.org/docs/introducing-jsx.html) that have `type`, `props.children` and `props.style` (and other properties too) directly:
77
+
78
+ ```js
79
+ await satori(
80
+ {
81
+ type: 'div',
82
+ props: {
83
+ children: 'hello, world',
84
+ style: { color: 'black' },
85
+ },
86
+ },
87
+ options
88
+ )
89
+ ```
90
+
91
+ ### HTML Elements
92
+
93
+ 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.
94
+
95
+ 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>`.
96
+
97
+ 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](https://www.w3.org/TR/SVG11).
98
+
99
+ You can find the list of supported HTML elements and their preset styles [here](https://github.com/feliperohdee/satori/blob/main/src/handler/presets.ts).
100
+
101
+ #### Images
102
+
103
+ You can use `<img>` to embed images. However, `width`, and `height` attributes are recommended to set:
104
+
105
+ ```jsx
106
+ await satori(
107
+ <img src="https://picsum.photos/200/300" width={200} height={300} />,
108
+ options
109
+ )
110
+ ```
111
+
112
+ When using `background-image`, the image will be stretched to fit the element by default if you don't specify the size.
113
+
114
+ 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 (or buffer) directly as `props.src` so no extra I/O is needed in Satori:
115
+
116
+ ```jsx
117
+ await satori(
118
+ <img src="data:image/png;base64,..." width={200} height={300} />,
119
+ // Or src={arrayBuffer}, src={buffer}
120
+ options
121
+ )
122
+ ```
123
+
124
+ ### CSS
125
+
126
+ Satori uses the same Flexbox [layout engine](https://yogalayout.com) 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:
127
+
128
+ <table>
129
+ <thead>
130
+ <tr>
131
+ <th>Property</th>
132
+ <th>Property Expanded</th>
133
+ <th>Supported Values</th>
134
+ <th>Example</th>
135
+ </tr>
136
+ </thead>
137
+ <tbody>
138
+
139
+ <tr>
140
+ <td colspan="2"><b>CSS Variables</b></td>
141
+ <td>Supported, including <code>--var-name</code> declaration and <code>var(--var-name)</code> usage with fallback values</td>
142
+ <td><a href="https://og-playground.vercel.app/?share=rVLRTsIwFP2V5hIzTbY4wBjTIC9oos-a8MJLt95tha4lXQfOZf9uOxwRlTeeentO7zntuW0h1RyBwoyL3UoRUtlG4mPb-pqQIIpsgSVGqZbaBJQEnJlNImsMwsOJAkVeWEeM4_hqAPeC2-IXxkW1laxxaCbxY0B9_SQMplZo5TjnU5dqYJkUuXq1WFaeQmXRDNS6rqzImoV2oPL-p3TC0k1udK34wt_c8aMsy46urutNfCIl08kPaPn9lvs47tGuW6m5L3w4x2RIn4VT3DFzfZLPTeBa5i8opQ7JUhvJZ7eu8x-Jv7lqw1TuUr2E-lmJaBKSUTaNx_H4vNqwQgh668dSAW2hHynQBxcNHGYO9M5vOCZ1DjRjssIQsNRr8d5s_Zey-37ndHy4z2WCHKg1NXYhWJa4E4W333tz6L4A">Example</a></td>
143
+ </tr>
144
+
145
+ <tr>
146
+ <td colspan="2"><code>display</code></td>
147
+ <td><code>flex</code>, <code>contents</code>, <code>none</code>, default to <code>flex</code></td>
148
+ <td></td>
149
+ </tr>
150
+
151
+ <tr>
152
+ <td colspan="2"><code>position</code></td>
153
+ <td><code>relative</code>, <code>static</code> and <code>absolute</code>, default to <code>relative</code></td>
154
+ <td></td>
155
+ </tr>
156
+
157
+ <tr>
158
+ <td colspan="2"><code>color</code></td>
159
+ <td>Supported</td>
160
+ <td></td>
161
+ </tr>
162
+
163
+ <tr><td rowspan="5"><code>margin</code></td></tr>
164
+ <tr><td><code>marginTop</code></td><td>Supported</td><td></td></tr>
165
+ <tr><td><code>marginRight</code></td><td>Supported</td><td></td></tr>
166
+ <tr><td><code>marginBottom</code></td><td>Supported</td><td></td></tr>
167
+ <tr><td><code>marginLeft</code></td><td>Supported</td><td></td></tr>
168
+
169
+ <tr><td rowspan="5">Position</td></tr>
170
+ <tr><td><code>top</code></td><td>Supported</td><td></td></tr>
171
+ <tr><td><code>right</code></td><td>Supported</td><td></td></tr>
172
+ <tr><td><code>bottom</code></td><td>Supported</td><td></td></tr>
173
+ <tr><td><code>left</code></td><td>Supported</td><td></td></tr>
174
+
175
+ <tr><td rowspan="3">Size</td></tr>
176
+ <tr><td><code>width</code></td><td>Supported</td><td></td></tr>
177
+ <tr><td><code>height</code></td><td>Supported</td><td></td></tr>
178
+
179
+ <tr><td rowspan="5">Min & max size</td></tr>
180
+ <tr><td><code>minWidth</code></td><td>Supported except for <code>min-content</code>, <code>max-content</code> and <code>fit-content</code></td><td></td></tr>
181
+ <tr><td><code>minHeight</code></td><td>Supported except for <code>min-content</code>, <code>max-content</code> and <code>fit-content</code></td><td></td></tr>
182
+ <tr><td><code>maxWidth</code></td><td>Supported except for <code>min-content</code>, <code>max-content</code> and <code>fit-content</code></td><td></td></tr>
183
+ <tr><td><code>maxHeight</code></td><td>Supported except for <code>min-content</code>, <code>max-content</code> and <code>fit-content</code></td><td></td></tr>
184
+
185
+ <tr><td rowspan="5"><code>border</code></td></tr>
186
+ <tr><td>Width (<code>borderWidth</code>, <code>borderTopWidth</code>, ...)</td><td>Supported</td><td></td></tr>
187
+ <tr><td>Style (<code>borderStyle</code>, <code>borderTopStyle</code>, ...)</td><td><code>solid</code> and <code>dashed</code>, default to <code>solid</code></td><td></td></tr>
188
+ <tr><td>Color (<code>borderColor</code>, <code>borderTopColor</code>, ...)</td><td>Supported</td><td></td></tr>
189
+ <tr><td>
190
+ Shorthand (<code>border</code>, <code>borderTop</code>, ...)</td><td>Supported, i.e. <code>1px solid gray</code><br/>
191
+ </td><td></td></tr>
192
+
193
+ <tr><td rowspan="6"><code>borderRadius</code></td></tr>
194
+ <tr><td><code>borderTopLeftRadius</code></td><td>Supported</td><td></td></tr>
195
+ <tr><td><code>borderTopRightRadius</code></td><td>Supported</td><td></td></tr>
196
+ <tr><td><code>borderBottomLeftRadius</code></td><td>Supported</td><td></td></tr>
197
+ <tr><td><code>borderBottomRightRadius</code></td><td>Supported</td><td></td></tr>
198
+ <tr><td>Shorthand</td><td>Supported, i.e. <code>5px</code>, <code>50% / 5px</code></td><td></td></tr>
199
+
200
+ <tr><td rowspan="11">Flex</td></tr>
201
+ <tr><td><code>flexDirection</code></td><td><code>column</code>, <code>row</code>, <code>row-reverse</code>, <code>column-reverse</code>, default to <code>row</code></td><td></td></tr>
202
+ <tr><td><code>flexWrap</code></td><td><code>wrap</code>, <code>nowrap</code>, <code>wrap-reverse</code>, default to <code>wrap</code></td><td></td></tr>
203
+ <tr><td><code>flexGrow</code></td><td>Supported</td><td></td></tr>
204
+ <tr><td><code>flexShrink</code></td><td>Supported</td><td></td></tr>
205
+ <tr><td><code>flexBasis</code></td><td>Supported except for <code>auto</code></td><td></td></tr>
206
+ <tr><td><code>alignItems</code></td><td><code>stretch</code>, <code>center</code>, <code>flex-start</code>, <code>flex-end</code>, <code>baseline</code>, <code>normal</code>, default to <code>stretch</code></td><td></td></tr>
207
+ <tr><td><code>alignContent</code></td><td>Supported</td><td></td></tr>
208
+ <tr><td><code>alignSelf</code></td><td>Supported</td><td></td></tr>
209
+ <tr><td><code>justifyContent</code></td><td>Supported</td><td></td></tr>
210
+ <tr><td><code>gap</code></td><td>Supported</td><td></td></tr>
211
+
212
+ <tr><td rowspan="5">Font</td></tr>
213
+ <tr><td><code>fontFamily</code></td><td>Supported</td><td></td></tr>
214
+ <tr><td><code>fontSize</code></td><td>Supported</td><td></td></tr>
215
+ <tr><td><code>fontWeight</code></td><td>Supported</td><td></td></tr>
216
+ <tr><td><code>fontStyle</code></td><td>Supported</td><td></td></tr>
217
+
218
+ <tr><td rowspan="13">Text</td></tr>
219
+ <tr><td><code>tabSize</code></td><td>Supported</td><td></td></tr>
220
+ <tr><td><code>textAlign</code></td><td><code>start</code>, <code>end</code>, <code>left</code>, <code>right</code>, <code>center</code>, <code>justify</code>, default to <code>start</code></td><td></td></tr>
221
+ <tr><td><code>textIndent</code></td><td>Supported, including negative values (hanging indent)</td><td></td></tr>
222
+ <tr><td><code>textTransform</code></td><td><code>none</code>, <code>lowercase</code>, <code>uppercase</code>, <code>capitalize</code>, defaults to <code>none</code></td><td></td></tr>
223
+ <tr><td><code>textOverflow</code></td><td><code>clip</code>, <code>ellipsis</code>, defaults to <code>clip</code></td><td></td></tr>
224
+ <tr><td><code>textDecoration</code></td><td>Support line types <code>underline</code> and <code>line-through</code>, and styles <code>dotted</code>, <code>dashed</code>, <code>double</code>, <code>solid</code></td><td><a href="https://og-playground.vercel.app/?share=pVPLTsMwEPwVaytUkAKkPCRklV4oXwDHXhx7YxtcO3Ic2hLl37GTtEKIQynywTvjndGstG6BO4FAYS70x8oSUoedwce2TTUhCrVUgZLpLM_PptlAbrQI6gcndF0ZtotsaXC7Z1O91B550M7GN-5Ms7b714oJoa2kZJaPTMH4u_SuseLJGeejYlKW5cHN2fCiP5GS25uRkqxK8gS6bmUXqUiTHMYgAbdhidx5NmawzuI0di9SMb-OzceoYiT0Ro_SAzpan5ovg4qzSdVbfCf-noIIFwIK4lH0bgM8KQ0RrFbRqjDNMNyAT8rUFAbJhHP-_1CDl5cFO8-z_lzdX_ySb39DBq5KTjXQFvoVBfqQ5xkMOwz0LgGBRSOBlszUmAGu3Zt-3VXpA4RNj6JP2rPndYECaPANdhkEVsQOhca4jfNGQPcF">Example</a></td></tr>
225
+ <tr><td><code>textShadow</code></td><td>Supported</td><td></td></tr>
226
+ <tr><td><code>lineHeight</code></td><td>Supported</td><td></td></tr>
227
+ <tr><td><code>letterSpacing</code></td><td>Supported</td><td></td></tr>
228
+ <tr><td><code>whiteSpace</code></td><td><code>normal</code>, <code>pre</code>, <code>pre-wrap</code>, <code>pre-line</code>, <code>nowrap</code>, defaults to <code>normal</code></td><td></td></tr>
229
+ <tr><td><code>wordBreak</code></td><td><code>normal</code>, <code>break-all</code>, <code>break-word</code>, <code>keep-all</code>, defaults to <code>normal</code></td><td></td></tr>
230
+ <tr><td><code>textWrap</code></td><td><code>wrap</code>, <code>balance</code>, defaults to <code>wrap</code></td><td></td></tr>
231
+
232
+ <tr><td rowspan="7">Background</td></tr>
233
+ <tr><td><code>backgroundColor</code></td><td>Supported, single value</td><td></td></tr>
234
+ <tr><td><code>backgroundImage</code></td><td><code>linear-gradient</code>, <code>repeating-linear-gradient</code>, <code>radial-gradient</code>, <code>repeating-radial-gradient</code>, <code>url</code>, single value</td><td></td></tr>
235
+ <tr><td><code>backgroundPosition</code></td><td>Supports keywords (<code>center</code>, <code>left</code>, <code>right</code>, <code>top</code>, <code>bottom</code>), percentages, lengths, and mixed values. Works correctly with <code>backgroundSize: cover</code>, <code>contain</code>, and <code>auto</code>.</td><td></td></tr>
236
+ <tr><td><code>backgroundSize</code></td><td>Support <code>cover</code>, <code>contain</code>, <code>auto</code>, and two-value sizes i.e. <code>10px 20%</code></td><td><a href="https://og-playground.vercel.app/?share=ZZXXjqNIFIZfpeWb2RW9Itik3tmRwAQDJgeDNTfkHEwwYdTvvnhGWq003HDOqZ8fDlX11Y9D2Ebx4ePwNcqf35u3t2Fcq_ifHz9e8dtbFudpNn68wRD0_qsy59GY_b8Q-GGZ9u3UREbcxf4u_tK0f_U_4y-_acx8i3dF2Dajnze_jwu1n74EU1_9Efmj_5G_CmDXpH8H_hBjp_fcoVVjhiQ-ban9Ukw7Y-10j3j9ldtnyttvND6LubMHTABVrO4YJypeXGSdBtuwwCRWANMRpy7W49jmUFvuyU0fnmZeduAiZ6ZZaueUXM0V5VDBNs2OtfX6MimW0iTTBD0JeYbFeSOrpLGR5_iMkrHGYSAGEFJl77xRCsjNDC_MmIHV8KDOdsboszJAXchEZifrokEtSyrT5cUkbkifODIuyKC2oblXy8yAqyt-VLcJr8UhYJc2PGoWrW4dppebvMnexThypKZD-IRPx2dFjjnj3eAxqMU1XLTipNKFhrv-1Te3ZWE41aKHtHUogVtC98ZlniyqI9lkcOS5xQnRSlTV0-tKaHckalSGADnQlda0i12YNG7wcO3J-IrGTiWLXtviZ6tc1LDpTrmyhe5uybE1CSYwMHinsikaLkdSepmcxdufvxhlwGcYZFBUF9yqXt9fIT5MC0L3H99JUi4UOqz4PPHkFsq72zM8WjYnYGqRWukUlGh4gcFkBi7zyiOuA0c-D4-VW-zWoSnBLSFHV2qkeLMWcwGd7jWpWVCi5AgElPbN7TB1vJpQbagUW4USzTgmUwZs5ckWiKtbeoovGVYKVZYplg37_NjrsGiWVdUBSYO2HADFotULPEJ1kl-3_QMDRpkEjjNQJkq5rpYMXk6gMN14z9Nh9lIhURHsoqlSm5QUTb2sFTs1ag4JjyqDx8eRoJY24GdbW3E7mrfC2jYpulOIfsX9q8znWkYJIU1Lpl3RHbd1jvHq2hVJ27w8Hu0DQtHereC9qSlsH2pBzdY8HbMclOVBaxkCNQPbCS280YnTjDj1vRVdXk2GTcHUtcLIC2kuZmc-F0CjT5O0bxLguUZX0-46gocAOemyO9ud16EQRYoEzoUHKbUT2c6Z6iVo8ikGz_c28GlfMMZFXId-3ndkk55Y6pEpLu00ERw5DiE5ibPw6Jg0pxiXMCDPY5Xv0ooyy07SOY2ItUJA0GcAc95NKKS17ERr91XBFBjo7kTiSi-nM-MjY9_WyXH_6BRO6OxOPK9k9bA925tNd9HmcmRzKI6YXmAFWWNmoOixTTtukLU1GRFeZIuaqTM24QNH063N0Ubq86kAJkdSfl7B89Dt0_KkVXVtMRYgb4Wsogggys-O-89XYZGtuDtasBrKY6ZmsIg42314VQNix3wBqQC70a0ODgxY3BbqKvgnxsscyioJTTAUoJHSrXUiwWwtLqtAakcVCILGBrn4C1p0-WKUzlCXhIAKTFUJTXzV9zXLWaU56fX5_OfvsDy3VdvvsJyzfIy_vEY_P783376CO8u_fW8O74e2G_O2GQ4fPw4_wX34IHZwH35h_fBxeiVRHEzp4SPxqyF-P8R1W-TW2r3OhHH-me0-yc5rtg7i6PAx9lP8-X4Y_WBXZHFVtXPbV9Hh818">Example</a></td></tr>
237
+ <tr><td><code>backgroundClip</code></td><td><code>border-box</code>, <code>text</code></td><td></td></tr>
238
+ <tr><td><code>backgroundRepeat</code></td><td><code>repeat</code>, <code>repeat-x</code>, <code>repeat-y</code>, <code>no-repeat</code>, defaults to <code>repeat</code></td><td></td></tr>
239
+
240
+ <tr><td rowspan="5"><code>transform</code></td></tr>
241
+ <tr><td>Translate (<code>translate</code>, <code>translateX</code>, <code>translateY</code>)</td><td>Supported</td><td></td></tr>
242
+ <tr><td>Rotate</td><td>Supported</td><td></td></tr>
243
+ <tr><td>Scale (<code>scale</code>, <code>scaleX</code>, <code>scaleY</code>)</td><td>Supported</td><td></td></tr>
244
+ <tr><td>Skew (<code>skew</code>, <code>skewX</code>, <code>skewY</code>)</td><td>Supported</td><td></td></tr>
245
+
246
+ <tr>
247
+ <td colspan="2"><code>transformOrigin</code></td>
248
+ <td>Support one-value and two-value syntax (both relative and absolute values)</td>
249
+ <td></td>
250
+ </tr>
251
+
252
+ <tr>
253
+ <td colspan="2"><code>objectFit</code></td>
254
+ <td>Supported</td>
255
+ <td><a href="https://og-playground.vercel.app/?share=7VVNj5swEP0ro6mqJFJaslJVVVbYQ6X2F_TIBewBvHVsZMwmEeK_75BAAvsl7WUPq-WC5j0P896zNLQonSIUuFxBfAttYgHyxsqgnYXf7rBsQZbaKE8WutWZB_AUGm9hq_Q91OFoKG4HBmCvVSgF3Gw26xEqSRdlmGNK15VJjwIWuaHD4oJnqfxfeNdYxdSXPM8nlPOKPMM31QFqZ7RiIWpxprvudjzXjoq7M7KNWOeJZaB_DfKXA83s2PrYzMs6L0Z_TEzNrI5gN8i46NtyrpeCS70rrhVr8DJOsAyhqkUU8XBJpTPqu3TRz83mwPOiyhYJTntOWuKW-WHYVE3ccs8Mf2pzYmjB2r9OjU59PUu67I5k-Kt7PtfGDFcyt98_0TWDaBrCh05Eunvyn5HMI7Eh1fZdQ-FMXonkhUTeK5Bapoa-Kbd_aybX3bZKbAeQWFyjq_r1XaNo8aQOxS9eUnhWg6LfWKgoawoUeWpqWiPt3J3-d6z6P0HYnyr-Ts7X9GeXkUIRfEPdGkOa8YmSjHF7543C7gE">Example</a></td>
256
+ </tr>
257
+
258
+ <tr>
259
+ <td colspan="2"><code>objectPosition</code></td>
260
+ <td>Supports keywords (<code>top</code>, <code>bottom</code>, <code>left</code>, <code>right</code>, <code>center</code>), percentages (e.g., <code>25% 75%</code>), lengths (e.g., <code>10px 20px</code>), and mixed values (e.g., <code>left 20%</code>). Defaults to <code>center</code> (<code>50% 50%</code>).</td>
261
+ <td><a href="https://og-playground.vercel.app/?share=7VTBitswEP2VQaUkgbTOQilFxHsotOceevTFlsa2torGyHKTYPzvHSV24rS7Xfayp_XFzHsj6b0nMb1QpFFIsVxBeg995gDKzqlgyMFXOix7ULWx2qODYXXmATyGzjvYavMb2nC0mPYjA7A3OtQS7jab9QTVaKo63GLatI3NjxIWpcXD4oIXufpVeeqcZupdWZYzirxGz_Bdc4CWrNEsRC_O9DDcT339pHg4I9uEdZ5YBuJvlL8caWanpX-beVrnxeinmakbqxM4jDIu-rac66Xg0uyqa8UavEozUYfQtDJJ-HCFNVn9UVHyebM58HlJ46pMzNectKQ98-NhczVpz2tu8H9tzgwtWPv7udG5r0dJKh5Qhe8m8opcyI17vOUHtSa-rNiHLqAfL-82qPgl17SSeVxv2XFfQSHQ7lWz4-j-k9wTwb1Wbq3KLX7QtH8-ukAN-LjtC9O7zpBV5gaAzIm1oCbu2grZi5MPIb_wMBBn3ULGySA0Fl0lZJnbFtcCd_Rgfh6bOHHD_lTxPiXf-7ddgVrI4Dsc1iLkBXfUaC3tyVsthj8">Example</a></td>
262
+ </tr>
263
+
264
+ <tr>
265
+ <td colspan="2"><code>opacity</code></td>
266
+ <td>Supported</td>
267
+ <td></td>
268
+ </tr>
269
+
270
+ <tr>
271
+ <td colspan="2"><code>boxSizing</code></td>
272
+ <td>Supported</td>
273
+ <td></td>
274
+ </tr>
275
+
276
+ <tr>
277
+ <td colspan="2"><code>boxShadow</code></td>
278
+ <td>Supported</td>
279
+ <td></td>
280
+ </tr>
281
+
282
+ <tr>
283
+ <td colspan="2"><code>overflow</code></td>
284
+ <td><code>visible</code> and <code>hidden</code>, default to <code>visible</code></td>
285
+ <td></td>
286
+ </tr>
287
+
288
+ <tr>
289
+ <td colspan="2"><code>filter</code></td>
290
+ <td>Supported</td>
291
+ <td></td>
292
+ </tr>
293
+
294
+ <tr>
295
+ <td colspan="2"><code>mixBlendMode</code></td>
296
+ <td>Supported</td>
297
+ <td></td>
298
+ </tr>
299
+
300
+ <tr>
301
+ <td colspan="2"><code>clipPath</code></td>
302
+ <td>Supported</td>
303
+ <td><a href="https://og-playground.vercel.app/?share=XVJNb9wgEP0rI6poW8lJnX6pstpe0h7aQ1UlrXLJBZvBZosZBDgbZ7X_PQMbZze5wPCGmXmPx1Z0pFA04osytzcOIKbZ4tftNscAA5p-SA2szuv6ZFXtwY1RaXiBKRO9lTOj2uLdgub4uwnYJUOOcx3ZaXRLVlrTu58Jx5hT6BKGJbWeYjJ6viAGXZ7_PN3K7n8faHLqgiwFzr_SWj9N5aorc48NvH93BF0_avlU1wXd7W7ctxws0l-KP8j_8FhypP4Y8lIp4_oGzg_YgSKzY6FDau2EC0WAzhr_R5Z39GTnntzrj_UJ1BU34Z3jKi_lVEGd4zerfXEmDlCoA_yLqKCdIdKIQBrSgLChYNUqgpWhx5igo9FLZzBW8Bvv0tk6AjrZWoww0wSJoAsoE4KerD2NianDNbYgvbemk9m8mGdwLbqstEyxXMHNL1F2CTTXTyFPkE6BYbP6wIV81dMGAzeGS_b0tJWZ7y95K6-6YHzi4WTzNU2hdNUylrbtZKyKZ8Wft2wQy112UQnyhZRotqL4IZrP7IfY-yWabI5Q2E69aLS0ESuBI63N39nnv5425cR98r_4MbaoRJPChLtKJNnyjQGtpfKMYvcA">Example</a></td>
304
+ </tr>
305
+
306
+ <tr>
307
+ <td colspan="2"><code>lineClamp</code></td>
308
+ <td>Supported</td>
309
+ <td><a href="https://og-playground.vercel.app/?share=5VPBbtQwEP2VkRFakNKSshxQBBwoXDhwaEFc9uLYk6xbx2PZk-6G1Up8DR_GlzDOkgr13FtPGb_xvPf8ojkoQxZVo95Zd7cJAJknj-8Ph1IDbNH1W25gdVHXz1fVCdw5y9sHmHU5ej0J2nncL2ipP7mEhh0F6Rny4xCWbtTWutA3cFH_Q1ptbvtEY7CX5CnJxLOu6-7ZKPC1-4kNrF_P0PG4CR9KsZh_aP9_X60nc7tQAXgX8NLrIQrbPTjo1LvwkZhpkJF1HferU69IAcxiAN8zWmgnyDQgUAe8RdhR8naVwQsFZgZDQ9TBYa7gK-75_CYDBt16zDDRCExgEmpG6EbvzzLLy-EHtqBj9M7oElguGjKLocQ0q3iZEPIr1Iahk_kxFQUdLLjA2CcZlKuRdpiEGK7GzGetLn6_6Dt9bZKLLOIkz-8l0DSzdjrPtO3ovM3nc6KvJNJHyHa1ho368-s3vDBihQb5fVayEa-BX27UE093-apKUZxNqeag5v1Szdu6rtRpAVXzphwstmOvmk77jJXCgW7ctymW7eXdfBKesiSfhxatajiNeKwU61ZubNF7mmNUx78">Example</a></td>
310
+ </tr>
311
+
312
+ <tr><td rowspan="5">Mask</td></tr>
313
+ <tr><td><code>maskImage</code></td><td><code>linear-gradient(...)</code>, <code>radial-gradient(...)</code>, <code>url(...)</code></td><td><a href="https://og-playground.vercel.app/?share=pZJfb9MwFMW_imVp2ZDS5s_I1kULSMAkhgRoYlJf-uLYN8ltHTvYDm2o-t2xuxXBXvcQXed3rONj37unXAugJb0V-GulCLFuklDt92FNSAfYdq4k51manp3HT3CLwnUvmEA7SDZ52kjYnWhYf0ID3KFWXuNajr06qUxiq-4d9DZIoByYk7QercNm-qg9VOH8_-XG8x_4G0pymf-Dls9pr9L0mdaMb1qjRyW8x2jkRefcYMskwZ61YOejCrFtN-e6T4ZOOz3LinyRL65v3ubZdTZrari8KkQmbhh_jzuJdWXqWTbP51n0s1oUUdNX66GNuNFD5TP6MkXbKsvTNOK2sqatI9yhqGD60vHPHxq2fMDv67v022NbNA9vTjfqmd3ch0w-p2ECmZy1oXrLC46GSyDMkSI9C19MajlCTJxhPj8zftNfoyXUG3RfX21HkuTYBf-whhhowGMOBBXpXC_DWYfDSr1bqdvET46vNKZ6CH22tNzT44zQMrxDTJ-miJahL1RAPba0bJi0EFPo9RofpyGMoNse_7xRaOZdX4OgpTMjHGLqWO13dCCl3mojBT38AQ">Example</a></td></tr>
314
+ <tr><td><code>maskPosition</code></td><td>Supported</td><td><a href="https://og-playground.vercel.app/?share=pVJda9swFP0rQlC3Ayf-6NKmpt5gW2Ed7KOskJe8yNK1fRNZ8iR5iRfy3yelCayFPfXBvtK5h3uP7j07yrUAWtBbgb-XihDrRgnlbhfOhLSATesKcp6l6dl5_ARuULj2BSbQ9pKNHq0lbE9oOH9CA9yhVj7HtRw6dcoyiY26d9DZkALlwJxSq8E6rMeP2oMq9H-erj3-E_9AQS7zf6DFUe1Vmh7RivF1Y_SghK8xGHnROtfbIkmwYw3Y6aCCbNtOue6SvtVOT7JZPs_n1zdv8-w6m9QVXF7NRCZuGH-PW4lVaapJNs2nWfSrnM-iuitXfRNxo_vSa_RhjDZllqdpxG1pTVNFuEVRwvil5Z8_1GzxgN9Xd-m3x2ZWP7w5vahjdn0fNHmdhglkctKE6EtecDRcAmGOzNKz8MWkkgPExBnm9TPjSc8K_dAWjxP3O-q35PA_MRZQrdF9fX1DkiSHRfnZG2KgBo9zIKhI6zr5stl_RAXafr9U75bqNvEe9JHGVPeBammxowe30SJMNKZPfqRF2DAVUA0NLWomLcQUOr3Cx7EPZnabw80XCra46yoQtHBmgH1MHas8owUp9UYbKej-Lw">Example</a></td></tr>
315
+ <tr><td><code>maskSize</code></td><td>Support two-value size i.e. <code>10px 20%</code></td><td><a href="https://og-playground.vercel.app/?share=pVLfb9MwEP5XLEvLhpQ2P0a3LlpAAiYxJEATk_rSF8e-JNc6drAd2lD1f8duV8H6yoN19ved7j7ffTvKtQBa0HuBv5aKEOtGCeVuF-6EtIBN6wpymaXpxWV8BDcoXHuGCbS9ZKNHawnbExrun9AAd6iV57iWQ6dOLJPYqEcHnQ0UKAfmRK0G67AeP2oPqtD_NV17_Af-hoJc5_9Aixe1N2n6glaMrxujByV8jcHIq9a53hZJgh1rwE4HFWTbdsp1l_StdnqSzfJ5Pr-9e5tnt9mkruD6ZiYyccf4e9xKrEpTTbJpPs2in-V8FtVdueqbiBvdl16jD2O0KbM8TSNuS2uaKsItihLGLy3__KFmiyf8vnpIvz03s_rpzelHHbPrx6DJ6zRMIJOTJkRf8oqj4RIIc2SWXoQTk0oOEBNnmNfPjE96Veg4Gr-ffkvyvztaQLVG9_X_O5EkOWzID90QAzV4nANBRVrXyfNm52oCv98v1buluk-863ykMdV98IilxY4e_EWLMMOYHh1Ii7BTKqAaGlrUTFqIKXR6hc9jH-zrNoeXLxSM8NBVIGjhzAD7mDpW-YwWpNQbbaSg-z8">Example</a></td></tr>
316
+ <tr><td><code>maskRepeat</code></td><td><code>repeat</code>, <code>repeat-x</code>, <code>repeat-y</code>, <code>no-repeat</code>, defaults to <code>repeat</code></td><td><a href="https://og-playground.vercel.app/?share=nVbpjqNIEn6VkqXVzMg1AhtjQ-3MStwGA-Ywl9U_hssJ5jSHAbf63TdxdfXUzh4_FhllHF8cGZkm4usirKJ48bb4LUrvX8qXl7ab8vj3r19n-uUliVOQdG8vP61Q9G8_vb4LhzTqkr_IorStc3-C0ksejx_SmWbTJg67tCqhLqzyvig_tH6eglLs4qKdVXHZxc2H6tq3XXqZmAoKyzn-v6ovUG6mj_jtBVt_Ejnfs92i6Hdp4IcZaKq-jKCPvsl_jvzOf0sLH8RIXYK_B34bbzevqU0fjQE9CKCi4KOaVsJZAFK0PvMaQ3lwYbPiSNqzgHJV00BFqmk34XaGiEbucHlxslDqMNtRAJpyJkUpM0NTFAcXzqco6ztPUSbggs-8BTgY_AMvwl9UU9Qz_lPvPP0-WaiEz6zinkKoPwLIs9_lkGcATGEO-o6jTUANn3iKeJJ2F-6CJ58PJp8_ICFzA7QeFZqSbqHwBOW1zSeow62UY6HeAxNPzgKZnk18E7jfU2LHzbFMulBY5ZHAgVhYtUGpbGMWTT3HuHuFtZ35wLFRzyRScQ-2EDNEQkuKeaJaDM0GmJSLrNcrzGYQr5uDyFBA20vZ-VqbBuf98BkWRqGZUhXtjeGYEvcIizC5DB9yQU7niRiPpwyXH9QkP8RJdqF9unrEDo56Luig_fXD9yf_3NlVr2GRw3zye5DS01nwtp4j3SNXJ8VU_IH_eD9ygfjifEVTf2-gIVvd5TUO8-CzYC3l8rNWJOo750J-cHBfRKqB6rMf4t2-1mDsPCiN5Bn_uhk15t3umJGT79h9JPCQJ_tP9oSM_YfcP-rGwFrAPViZIUAbiH2v97P-p81BsHcJDc-ZWvGSwfHWkRZUm-8UDuWsMsJM7ITXvl8YYxpVaWIcDFuQMp-lDYZXUyUzNVLX4KYTMBgGNxwFaEUoF84QWe6mXMYz13GVeU91ISF0sPF8oDNpbYtxHVWOmSxRzOXLyHCzEenHcHk5as7liJUdRl2SjhWUE3PZ2gdM43nDPrEUGwnJxHISvL6WuHksd8qjuK66bTDEQyKKHu5qIGNFxvAo5byyizvKZ1y7x28hf-CjZXAw2EGzWn_bP04b2lbtMx3Y0jpXaDNktVtXbPH7o8A3k8q5jhVNxsBWhe0F8jqqYqJnr17XAO9o1UZrAcDRo3tY7Zc5wGhikM4Wilz3YTH1akMOy1XMMGOVEZokexWWlo7HMrJ4mBzVqM7u8aSyN7SWb870cAKdUCqJ6c2z3xr6gV61QqrD22-O_pRosq3iHQHGzFRQ5Yisjo61Tseq0VByWxKmdVrHa9fRk122bBpiUHnDCTG_OR28OudwcW0E4qGozUaOdnHZRLesiUbXkbg1bku7lGdSrNTYBlHqgxtVdr9RjvZyd8xXd4BUYBvou7VvXLoxGkldoSrXdvmzRHnurQiLi9WfDPsgi_dJE5cB4pIZG6gaVm1qkrxQVSUqZU3d0bDMtxfdU3kSiVwJKVrPXy8HaWAJzNBDjq4entTprFOeWL32HNwX1u2dXVfSQJQeeyMecFNHn6ezUUmaO9md4uaKL3twybpTNuQ1QYGym8gitAjRu7DoFaQkWHI7miNzZ2UUsuEE2LlmjNrpp5XTh3mM-eXt_mi2O1kor9tBsfwbxgILhzkJ68PRPh25nlhRkxTTuWQq6dWVpp3BVPWq6ree34P7ivDPAljuwsQbB8fxjnTSn-lsx7sPMdwQe48zB3lI0rI02Vgz2kBmsColkI3M3QuwOSUEzZuCd99fNIykkP0YINFwL6LNUiQmKiAPoo5PqkVFGc1fEaQtxrW6psdO4C5IhRxC1916J3mrxeDerWKcRC_0etLjCiR7IexI2eG0w57VB4S0zd53hq0xZI7sNuqZ2ka4KhmHvBQke2sbO_gn013TtlADlAmPyZEcSqsdVrqFl8IbmAgqnY9pFukjifCFN1mcGPOEFPsIAi-V0rgqfdMZSlIkpEGa4ua3J44SQ4thMqQRr8ujjuxUKgCIq3Iaheg6fbOvF2qN895725IMC-eaTAIA_P77Lx_NvfDbTJw79NvLH3laxn7zK2j8KIUTwM9d9dLMPf71Jcj7-PWla_yyrf0G6n7545P9-3AwTyj1-LJaz8tn90Zcx_48VjRP4tcfSicOsrRTPmXwl6GhvYPlWOQfg4O2V9fnicZ8x0B92OyOLDWIKV2dnbz097B5XMGgMCKICnsK1_MHGk0VczNCzBp-2DG9IDeaKQ4iSwHlJEIsNSrXp49N4Ix9-PjUXGCjiyYcUyb8HhbhfcYpDPmIijDVruPguUYlCjBmho4KMzxUk6Yhpn2-zDDILNeqJ0g_LKDDWDI7v0_dKLMZpJWVyHM45NeqOazgikMfhgJt1KvVzuts66QiOBd5G8D9RuukjgQrFRliCZvOnMvyx0H8ezH_n-P808v_ONQ_Qf_laL99-1L-40v5GwLHXLguXhdVPQ-l7eLt6-I50C7eZpevi_eRd_E2D5GLKA56sHi7-Hkbvy7iorqmp6me5-VueHLQ0Tx5ckUQR4u3runjb6-Lzg8gIonzvBqqJo8W3_4J">Example</a></td></tr>
317
+
318
+ <tr>
319
+ <td rowspan="2"><code>WebkitTextStroke</code>
320
+ <td><code>WebkitTextStrokeWidth</code></td>
321
+ <td>Supported</td>
322
+ <td></td>
323
+ </tr>
324
+ <tr>
325
+ <td><code>WebkitTextStrokeColor</code></td>
326
+ <td>Supported</td>
327
+ <td></td>
328
+ </tr>
329
+
330
+ </tbody>
331
+ </table>
332
+
333
+ Note:
334
+
335
+ 1. Three-dimensional transforms are not supported.
336
+ 2. There is no `z-index` support in SVG. Elements that come later in the document will be painted on top.
337
+ 3. `calc` isn't supported.
338
+ 4. `currentColor` support is only available for the `color` property.
339
+ 5. CSS variables (custom properties) are supported, including inheritance, fallback values, and nested variables.
340
+
341
+ ### Language and Typography
342
+
343
+ Advanced typography features such as kerning, ligatures and other OpenType features are not currently supported.
344
+
345
+ RTL languages are not supported either.
346
+
347
+ #### Fonts
348
+
349
+ 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):
350
+
351
+ ```js
352
+ await satori(
353
+ <div style={{ fontFamily: 'Inter' }}>Hello</div>,
354
+ {
355
+ width: 600,
356
+ height: 400,
357
+ fonts: [
358
+ {
359
+ name: 'Inter',
360
+ data: inter,
361
+ weight: 400,
362
+ style: 'normal',
363
+ },
364
+ {
365
+ name: 'Inter',
366
+ data: interBold,
367
+ weight: 700,
368
+ style: 'normal',
369
+ },
370
+ ],
371
+ }
372
+ )
373
+ ```
374
+
375
+ Multiple fonts can be passed to Satori and used in `fontFamily`.
376
+
377
+ > [!TIP]
378
+ > We recommend you define global fonts instead of creating a new object and pass it to satori for better performance, if your fonts do not change. [Read it for more detail](https://github.com/feliperohdee/satori/issues/590)
379
+
380
+ #### Emojis
381
+
382
+ To render custom images for specific graphemes, you can use `graphemeImages` option to map the grapheme to an image source:
383
+
384
+ ```jsx
385
+ await satori(
386
+ <div>Next.js is 🤯!</div>,
387
+ {
388
+ ...,
389
+ graphemeImages: {
390
+ '🤯': 'https://cdnjs.cloudflare.com/ajax/libs/twemoji/14.0.2/svg/1f92f.svg',
391
+ },
392
+ }
393
+ )
394
+ ```
395
+
396
+ The image will be resized to the current font-size (both width and height) as a square.
397
+
398
+ #### Locales
399
+
400
+ Satori supports rendering text in different locales. You can specify the supported locales via the `lang` attribute:
401
+
402
+ ```jsx
403
+ await satori(
404
+ <div lang="ja-JP">骨</div>
405
+ )
406
+ ```
407
+
408
+ Same characters can be rendered differently in different locales, you can specify the locale when necessary to force it to render with a specific font and locale. Check out [this example](https://og-playground.vercel.app/?share=nVLdSsMwFH6VcEC86VgdXoyweTMVpyiCA296kzWnbWaalCZ160rfwAcRH8Bn0rcwWVdQEYTdnJzz_ZyEnNNArDkChQkXz5EixNha4rRpfE4IF6aQrKbkOJG4OQ461OfnosTYCq0cF2tZ5apnMxRpZh18EoZHPbgW3Ga_sIJxLlS6Q4sNGbnQU0yKVM0t5sa3R2Wx7KlVZaxI6pl2oPLX_KQTh1-yXEj_6LlnAhLBLXOJYJLMY61MBN_VD2KLlIzGe2jJ4qe01JXiMy116bqsM2Gxc7Stj2edcmIKpohkKp1GsGKD6_sI9hQhn2-vHy_ve-HQK_9ybbPB7O4Q1-LxENfVzX-uydDtgTshAF348RqgDeymB3QchgF04wV66guOyyoFmjBpMADM9Uos6sLvk13vKtfH__FFvkQO1JYVtu0X) to learn more.
409
+
410
+ Supported locales are exported as the `Locale` enum type.
411
+
412
+ #### Dynamically Load Emojis and Fonts
413
+
414
+ 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:
415
+
416
+ ```jsx
417
+ await satori(
418
+ <div>👋 你好</div>,
419
+ {
420
+ // `code` will be the detected language code, `emoji` if it's an Emoji, or `unknown` if not able to tell.
421
+ // `segment` will be the content to render.
422
+ loadAdditionalAsset: async (code: string, segment: string) => {
423
+ if (code === 'emoji') {
424
+ // if segment is an emoji
425
+ return `data:image/svg+xml;base64,...`
426
+ }
427
+
428
+ // if segment is normal text
429
+ return loadFontFromSystem(code)
430
+ }
431
+ }
432
+ )
433
+ ```
434
+
435
+ ### Runtime Support
436
+
437
+ Satori can be directly used in browser, Node.js (>= 16), and Web Workers. It bundles its underlying WASM dependencies as base64-encoded strings and loads them at runtime.
438
+
439
+ If there is a limitation on dynamically loading WASM (e.g. Cloudflare Workers), you can use the Standalone Build which is mentioned below.
440
+
441
+ #### Standalone Build of Satori
442
+
443
+ Satori's standalone build doesn't include Yoga's WASM binary by default, and you need to load it manually before using Satori.
444
+
445
+ First, you need to download the `yoga.wasm` binary from [Satori build](https://unpkg.com/satori/) and provide it yourself. Let's use `fetch` to load it directly from the CDN as an example:
446
+
447
+ ```jsx
448
+ import satori, { init } from 'satori/standalone'
449
+
450
+ const res = await fetch('https://unpkg.com/satori/yoga.wasm')
451
+ const yogaWasm = await res.arrayBuffer()
452
+
453
+ await init(yogaWasm)
454
+
455
+ // Now you can use satori as usual
456
+ const svg = await satori(...)
457
+ ```
458
+
459
+ Of course, you can also load the `yoga.wasm` file from your local disk via `fs.readFile` in Node.js or other methods.
460
+
461
+ ### Font Embedding
462
+
463
+ By default, Satori renders the text as `<path>` in SVG, instead of `<text>`. That means it embeds the font path data as inlined information, so succeeding processes (e.g. render the SVG on another platform) don’t need to deal with font files anymore.
464
+
465
+ You can turn off this behavior by setting `embedFont` to `false`, and Satori will use `<text>` instead:
466
+
467
+ ```jsx
468
+ const svg = await satori(
469
+ <div style={{ color: 'black' }}>hello, world</div>,
470
+ {
471
+ ...,
472
+ embedFont: false,
473
+ },
474
+ )
475
+ ```
476
+
477
+ ### Pixel Grid Rounding
478
+
479
+ Set `pointScaleFactor` to control how layout values are rounded to the pixel grid. This parameter is passed directly to [Yoga’s `pointScaleFactor`](https://www.yogalayout.dev/docs/getting-started/configuring-yoga#point-scale-factor) and improves rendering precision on high-DPI displays.
480
+
481
+ ```jsx
482
+ const svg = await satori(
483
+ <div style={{ color: 'black' }}>hello, world</div>,
484
+ {
485
+ ...,
486
+ pointScaleFactor: 2,
487
+ },
488
+ )
489
+ ```
490
+
491
+ ### Debug
492
+
493
+ To draw the bounding box for debugging, you can pass `debug: true` as an option:
494
+
495
+ ```jsx
496
+ const svg = await satori(
497
+ <div style={{ color: 'black' }}>hello, world</div>,
498
+ {
499
+ ...,
500
+ debug: true,
501
+ },
502
+ )
503
+ ```