pr_01m47d24b0e6n91zwymwxg0vpx/services/og/src/render.ts
Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Social cards for every page: og.g1t.sh | 1 | /** |
| 2 | * Turns a card into a PNG: satori lays it out as SVG, with the text as | |
| 3 | * outlines, and resvg rasterises that. Both run as WebAssembly, which the | |
| 4 | * caller hands in: as modules in the Worker, as bytes in Node. | |
| 5 | */ | |
| 6 | import { Resvg, initWasm } from "@resvg/resvg-wasm"; | |
| 7 | import satori, { init as initYoga, type Font } from "satori/standalone"; | |
| 8 | ||
| 9 | import { HEIGHT, WIDTH, cardTree } from "./card.ts"; | |
| 10 | import type { Card } from "./resolve.ts"; | |
| 11 | ||
| 12 | export type Assets = { | |
| 13 | yoga: WebAssembly.Module | ArrayBuffer | Uint8Array; | |
| 14 | resvg: WebAssembly.Module | ArrayBuffer | Uint8Array; | |
| 15 | fonts: Font[]; | |
| 16 | }; | |
| 17 | ||
| 18 | let ready: Promise<void> | null = null; | |
| 19 | ||
| 20 | function start(assets: Assets): Promise<void> { | |
| 21 | ready ??= Promise.all([initYoga(assets.yoga), initWasm(assets.resvg)]).then( | |
| 22 | () => undefined, | |
| 23 | (error) => { | |
| 24 | ready = null; | |
| 25 | throw error; | |
| 26 | }, | |
| 27 | ); | |
| 28 | return ready; | |
| 29 | } | |
| 30 | ||
| 31 | /** The card as SVG, for tests and for looking at without rasterising. */ | |
| 32 | export async function cardSvg(card: Card, assets: Assets): Promise<string> { | |
| 33 | await start(assets); | |
| 34 | // Satori takes React elements; the tree is the same shape without React. | |
| 35 | return satori(cardTree(card) as unknown as Parameters<typeof satori>[0], { | |
| 36 | width: WIDTH, | |
| 37 | height: HEIGHT, | |
| 38 | fonts: assets.fonts, | |
| 39 | }); | |
| 40 | } | |
| 41 | ||
| 42 | export async function cardPng(card: Card, assets: Assets): Promise<Uint8Array> { | |
| 43 | const svg = await cardSvg(card, assets); | |
| 44 | const resvg = new Resvg(svg, { fitTo: { mode: "width", value: WIDTH }, font: { loadSystemFonts: false } }); | |
| 45 | try { | |
| 46 | return resvg.render().asPng(); | |
| 47 | } finally { | |
| 48 | resvg.free(); | |
| 49 | } | |
| 50 | } |