@cf-wasm/og
Version:
Generate Open Graph Images dynamically from HTML/CSS without a browser.
1 lines • 10.5 kB
Source Map (JSON)
{"version":3,"file":"render.cjs","names":["loadDynamicAsset","defaultFont","CustomFont","GoogleFont","SATORI_FONT_CACHE_MAP","satori","createResvg"],"sources":["../../src/core/render.ts"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { loadDynamicAsset } from './asset';\nimport type { EmojiType } from './emoji';\nimport { CustomFont, defaultFont, type FontBuffer, GoogleFont } from './font';\nimport { SATORI_FONT_CACHE_MAP } from './maps';\nimport { createResvg, type ResvgRenderOptions } from './resvg';\nimport {\n\ttype Font as SatoriFont,\n\ttype SatoriOptions,\n\tsatori,\n\ttype VNode,\n} from './satori';\nimport type { MayBePromise, StringWithSuggestions } from './types';\n\n/** Represents a png result of rendered image */\nexport interface PngResult {\n\tpixels: Uint8Array<ArrayBuffer>;\n\n\t/* Output image bytes */\n\timage: Uint8Array<ArrayBuffer>;\n\n\t/** The width of the image */\n\twidth: number;\n\n\t/** The height of the image */\n\theight: number;\n\n\t/** The mime type of the image */\n\ttype: string;\n}\n\n/** Represents a svg result of rendered image */\nexport interface SvgResult {\n\t/** The svg image as string */\n\timage: string;\n\n\t/** The width of the image */\n\twidth: number;\n\n\t/** The height of the image */\n\theight: number;\n\n\t/** The mime type of the image */\n\ttype: string;\n}\n\nexport interface RenderSatoriOptions\n\textends Omit<\n\t\tSatoriOptions,\n\t\t'width' | 'height' | 'fonts' | 'loadAdditionalAsset' | 'debug'\n\t> {}\n\nexport interface RenderResvgOptions extends Omit<ResvgRenderOptions, 'fitTo'> {}\n\nexport type Font = Omit<SatoriFont, 'data'> & FontBuffer;\n\n/** An interface representing options for {@link render} function */\nexport interface RenderOptions {\n\t/**\n\t * The width of the image.\n\t *\n\t * @default 1200\n\t */\n\twidth?: number;\n\n\t/**\n\t * The height of the image.\n\t *\n\t * @default 630\n\t */\n\theight?: number;\n\n\t/**\n\t * Display debug information on the image.\n\t *\n\t * @default false\n\t */\n\tdebug?: boolean;\n\n\t/** The default font to use */\n\tdefaultFont?: FontBuffer;\n\n\t/**\n\t * A list of fonts to use.\n\t *\n\t * @default Noto Sans Latin Regular.\n\t */\n\tfonts?: Font[];\n\n\t/**\n\t * A callback function for loading dynamic assets requested by satori\n\t *\n\t * @param languageCode\n\t * The detected language codes separated using `|` (i.e: `ja-JP|zh-CN|zh-TW|zh-HK`, `devanagari`, etc.)\n\t *\n\t * `emoji` if it's an Emoji\n\t *\n\t * `unknown` if not able to tell.\n\t *\n\t * @param segment Content to render.\n\t * @param next A function which when called returns a Promise resolves to the next result\n\t */\n\tloadAdditionalAsset?: (\n\t\tlanguageCode: StringWithSuggestions<'emoji' | 'unknown'>,\n\t\tsegment: string,\n\t\tnext: () => Promise<string | Font[]>,\n\t) => MayBePromise<undefined | string | Font[]>;\n\n\t/**\n\t * Using a specific Emoji style. Defaults to `twemoji`.\n\t *\n\t * @default 'twemoji'\n\t */\n\temoji?: EmojiType;\n\n\t/**\n\t * The format of response, can be one of `svg` or `png`\n\t */\n\tformat?: 'svg' | 'png';\n\n\t/**\n\t * Passes {@link RenderSatoriOptions} to satori\n\t */\n\tsatoriOptions?: RenderSatoriOptions;\n\n\t/**\n\t * Passes {@link RenderResvgOptions} to resvg\n\t */\n\tresvgOptions?: RenderResvgOptions;\n}\n\nexport interface RenderResult {\n\tasSvg(): Promise<SvgResult>;\n\tasPng(): Promise<PngResult>;\n}\n\n/** Default render options */\nexport const RENDER_DEFAULT_OPTIONS = {\n\twidth: 1200,\n\theight: 630,\n\tdebug: false,\n\tfonts: [],\n};\n\n/**\n * Renders {@link ReactNode} to image\n *\n * @param element The {@link ReactNode}\n * @param options The {@link RenderOptions}\n *\n * @returns An object containing methods for rendering the input element to image\n */\nexport function render(\n\telement: ReactNode | VNode,\n\toptions: RenderOptions = {},\n): RenderResult {\n\tconst promises: {\n\t\tsvg?: Promise<SvgResult>;\n\t\tpng?: Promise<PngResult>;\n\t\tfonts?: Promise<SatoriOptions['fonts']>;\n\t} = {};\n\n\tconst renderOptions = {\n\t\t...RENDER_DEFAULT_OPTIONS,\n\t\t...options,\n\t};\n\n\tconst loadAdditionalAsset = async (languageCode: string, segment: string) => {\n\t\tconst next = () =>\n\t\t\tloadDynamicAsset(languageCode, segment, renderOptions?.emoji);\n\n\t\tlet result: string | Font[] | undefined;\n\t\tif (options.loadAdditionalAsset) {\n\t\t\tresult = await options.loadAdditionalAsset(languageCode, segment, next);\n\t\t}\n\n\t\tresult ??= await next();\n\n\t\tif (Array.isArray(result)) {\n\t\t\treturn await Promise.all(\n\t\t\t\tresult.map(async (asset) => ({\n\t\t\t\t\t...asset,\n\t\t\t\t\tdata: await asset.data,\n\t\t\t\t})),\n\t\t\t);\n\t\t}\n\n\t\treturn result;\n\t};\n\n\tconst getFonts = async () => {\n\t\tconst fallback = async (): Promise<SatoriOptions['fonts']> => {\n\t\t\tconst defaultFonts: Font[] = [];\n\n\t\t\tconst fallbackFont = await (renderOptions.defaultFont?.data ??\n\t\t\t\tdefaultFont.get());\n\t\t\tif (fallbackFont) {\n\t\t\t\tdefaultFonts.push(\n\t\t\t\t\tnew CustomFont('sans serif', fallbackFont, {\n\t\t\t\t\t\tweight: 400,\n\t\t\t\t\t\tstyle: 'normal',\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t\"(@cf-wasm/og) [ WARN ] No default font specified. Using 'Noto Sans' from Google Fonts as the fallback.\",\n\t\t\t\t);\n\t\t\t\tdefaultFonts.push(\n\t\t\t\t\tnew GoogleFont('Noto Sans', {\n\t\t\t\t\t\tname: 'sans serif',\n\t\t\t\t\t\tweight: 400,\n\t\t\t\t\t\tstyle: 'normal',\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\treturn Promise.all(\n\t\t\t\t[...defaultFonts, ...renderOptions.fonts].map(async (font) => ({\n\t\t\t\t\t...font,\n\t\t\t\t\tdata: await font.data,\n\t\t\t\t})),\n\t\t\t).then((fonts) =>\n\t\t\t\t/**\n\t\t\t\t * An attempt to improve performance by passing cached satori font object\n\t\t\t\t *\n\t\t\t\t * @see https://github.com/vercel/satori/issues/590\n\t\t\t\t */\n\t\t\t\tfonts.map((font) => {\n\t\t\t\t\tconst key = JSON.stringify([\n\t\t\t\t\t\tfont.name,\n\t\t\t\t\t\tfont.style || '',\n\t\t\t\t\t\tfont.weight || '',\n\t\t\t\t\t\tfont.lang || '',\n\t\t\t\t\t\tfont.data.byteLength,\n\t\t\t\t\t]);\n\t\t\t\t\tconst fromMap = SATORI_FONT_CACHE_MAP.get(key);\n\t\t\t\t\tif (fromMap) {\n\t\t\t\t\t\treturn fromMap;\n\t\t\t\t\t}\n\t\t\t\t\tSATORI_FONT_CACHE_MAP.set(key, font);\n\t\t\t\t\treturn font;\n\t\t\t\t}),\n\t\t\t);\n\t\t};\n\n\t\tpromises.fonts = promises.fonts?.then(null, fallback) ?? fallback();\n\t\treturn promises.fonts;\n\t};\n\n\t/**\n\t * Method to render the element as svg image\n\t *\n\t * @returns A promise which resolves to rendered svg image as {@link SvgResult}\n\t */\n\tconst asSvg = async () => {\n\t\tconst fallback = async (): Promise<SvgResult> => {\n\t\t\tconst satoriFonts = await getFonts();\n\n\t\t\tconst satoriOptions = {\n\t\t\t\t...renderOptions.satoriOptions,\n\t\t\t\twidth: renderOptions.width,\n\t\t\t\theight: renderOptions.height,\n\t\t\t\tdebug: renderOptions.debug,\n\t\t\t\tfonts: satoriFonts,\n\t\t\t\tloadAdditionalAsset,\n\t\t\t};\n\t\t\tconst svg = await satori(element, satoriOptions);\n\n\t\t\treturn {\n\t\t\t\timage: svg,\n\t\t\t\theight: satoriOptions.height,\n\t\t\t\twidth: satoriOptions.width,\n\t\t\t\ttype: 'image/svg+xml',\n\t\t\t};\n\t\t};\n\n\t\tpromises.svg = promises.svg?.then(null, fallback) ?? fallback();\n\t\treturn promises.svg;\n\t};\n\n\t/**\n\t * Method to render the element as png image\n\t *\n\t * @returns A promise which resolves to rendered png image as {@link PngResult}\n\t */\n\tconst asPng = async () => {\n\t\tconst fallback = async (): Promise<PngResult> => {\n\t\t\tconst svg = await asSvg();\n\t\t\tconst resvg = await createResvg(svg.image, {\n\t\t\t\t...renderOptions.resvgOptions,\n\t\t\t\tfitTo: {\n\t\t\t\t\tmode: 'width',\n\t\t\t\t\tvalue: renderOptions.width,\n\t\t\t\t},\n\t\t\t});\n\t\t\tconst renderedImage = resvg.render();\n\n\t\t\tconst result: PngResult = {\n\t\t\t\tpixels: renderedImage.pixels as Uint8Array<ArrayBuffer>,\n\t\t\t\timage: renderedImage.asPng() as Uint8Array<ArrayBuffer>,\n\t\t\t\twidth: renderedImage.width,\n\t\t\t\theight: renderedImage.height,\n\t\t\t\ttype: 'image/png',\n\t\t\t};\n\n\t\t\t// Explicitly free rust memory\n\t\t\trenderedImage.free();\n\t\t\tresvg.free();\n\n\t\t\treturn result;\n\t\t};\n\n\t\tpromises.png = promises.png?.then(null, fallback) ?? fallback();\n\t\treturn promises.png;\n\t};\n\n\treturn { asSvg, asPng };\n}\n"],"mappings":"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"}