@vercel/og 0.3.0 → 0.4.1
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/dist/chunk-GIRBQXLD.js +135 -0
- package/dist/chunk-GIRBQXLD.js.map +1 -0
- package/dist/index.edge.d.ts +5 -0
- package/dist/index.edge.js +50 -0
- package/dist/index.edge.js.map +1 -0
- package/dist/index.node.d.ts +25 -0
- package/dist/index.node.js +71 -0
- package/dist/index.node.js.map +1 -0
- package/dist/og.d.ts +1 -44
- package/dist/types.d.ts +65 -0
- package/package.json +40 -6
- package/dist/index.d.ts +0 -17
- package/dist/index.js +0 -3
- package/dist/index.js.map +0 -1
- /package/{vendor → dist}/noto-sans-v27-latin-regular.ttf +0 -0
- /package/{vendor → dist}/resvg.wasm +0 -0
- /package/{vendor → dist}/yoga.wasm +0 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// src/emoji/index.ts
|
|
2
|
+
var U200D = String.fromCharCode(8205);
|
|
3
|
+
var UFE0Fg = /\uFE0F/g;
|
|
4
|
+
function getIconCode(char) {
|
|
5
|
+
return toCodePoint(char.indexOf(U200D) < 0 ? char.replace(UFE0Fg, "") : char);
|
|
6
|
+
}
|
|
7
|
+
function toCodePoint(unicodeSurrogates) {
|
|
8
|
+
var r = [], c = 0, p = 0, i = 0;
|
|
9
|
+
while (i < unicodeSurrogates.length) {
|
|
10
|
+
c = unicodeSurrogates.charCodeAt(i++);
|
|
11
|
+
if (p) {
|
|
12
|
+
r.push((65536 + (p - 55296 << 10) + (c - 56320)).toString(16));
|
|
13
|
+
p = 0;
|
|
14
|
+
} else if (55296 <= c && c <= 56319) {
|
|
15
|
+
p = c;
|
|
16
|
+
} else {
|
|
17
|
+
r.push(c.toString(16));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return r.join("-");
|
|
21
|
+
}
|
|
22
|
+
var apis = {
|
|
23
|
+
twemoji: (code) => "https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/svg/" + code.toLowerCase() + ".svg",
|
|
24
|
+
openmoji: "https://cdn.jsdelivr.net/npm/@svgmoji/openmoji@2.0.0/svg/",
|
|
25
|
+
blobmoji: "https://cdn.jsdelivr.net/npm/@svgmoji/blob@2.0.0/svg/",
|
|
26
|
+
noto: "https://cdn.jsdelivr.net/gh/svgmoji/svgmoji/packages/svgmoji__noto/svg/",
|
|
27
|
+
fluent: (code) => "https://cdn.jsdelivr.net/gh/shuding/fluentui-emoji-unicode/assets/" + code.toLowerCase() + "_color.svg",
|
|
28
|
+
fluentFlat: (code) => "https://cdn.jsdelivr.net/gh/shuding/fluentui-emoji-unicode/assets/" + code.toLowerCase() + "_flat.svg"
|
|
29
|
+
};
|
|
30
|
+
function loadEmoji(code, type) {
|
|
31
|
+
if (!type || !apis[type]) {
|
|
32
|
+
type = "twemoji";
|
|
33
|
+
}
|
|
34
|
+
const api = apis[type];
|
|
35
|
+
if (typeof api === "function") {
|
|
36
|
+
return fetch(api(code));
|
|
37
|
+
}
|
|
38
|
+
return fetch(`${api}${code.toUpperCase()}.svg`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/og.ts
|
|
42
|
+
var languageFontMap = {
|
|
43
|
+
"ja-JP": "Noto+Sans+JP",
|
|
44
|
+
"ko-KR": "Noto+Sans+KR",
|
|
45
|
+
"zh-CN": "Noto+Sans+SC",
|
|
46
|
+
"zh-TW": "Noto+Sans+TC",
|
|
47
|
+
"zh-HK": "Noto+Sans+HK",
|
|
48
|
+
"th-TH": "Noto+Sans+Thai",
|
|
49
|
+
"bn-IN": "Noto+Sans+Bengali",
|
|
50
|
+
"ar-AR": "Noto+Sans+Arabic",
|
|
51
|
+
"ta-IN": "Noto+Sans+Tamil",
|
|
52
|
+
"ml-IN": "Noto+Sans+Malayalam",
|
|
53
|
+
"he-IL": "Noto+Sans+Hebrew",
|
|
54
|
+
"te-IN": "Noto+Sans+Telugu",
|
|
55
|
+
devanagari: "Noto+Sans+Devanagari",
|
|
56
|
+
kannada: "Noto+Sans+Kannada",
|
|
57
|
+
symbol: ["Noto+Sans+Symbols", "Noto+Sans+Symbols+2"],
|
|
58
|
+
math: "Noto+Sans+Math",
|
|
59
|
+
unknown: "Noto+Sans"
|
|
60
|
+
};
|
|
61
|
+
async function loadGoogleFont(font, text) {
|
|
62
|
+
if (!font || !text)
|
|
63
|
+
return;
|
|
64
|
+
const API = `https://fonts.googleapis.com/css2?family=${font}&text=${encodeURIComponent(text)}`;
|
|
65
|
+
const css = await (await fetch(API, {
|
|
66
|
+
headers: {
|
|
67
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; de-at) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1"
|
|
68
|
+
}
|
|
69
|
+
})).text();
|
|
70
|
+
const resource = css.match(/src: url\((.+)\) format\('(opentype|truetype)'\)/);
|
|
71
|
+
if (!resource)
|
|
72
|
+
throw new Error("Failed to load font");
|
|
73
|
+
return fetch(resource[1]).then((res) => res.arrayBuffer());
|
|
74
|
+
}
|
|
75
|
+
var assetCache = /* @__PURE__ */ new Map();
|
|
76
|
+
var loadDynamicAsset = ({ emoji }) => {
|
|
77
|
+
const fn = async (code, text) => {
|
|
78
|
+
if (code === "emoji") {
|
|
79
|
+
return `data:image/svg+xml;base64,` + btoa(await (await loadEmoji(getIconCode(text), emoji)).text());
|
|
80
|
+
}
|
|
81
|
+
if (!languageFontMap[code])
|
|
82
|
+
code = "unknown";
|
|
83
|
+
try {
|
|
84
|
+
const data = await loadGoogleFont(languageFontMap[code], text);
|
|
85
|
+
if (data) {
|
|
86
|
+
return {
|
|
87
|
+
name: `satori_${code}_fallback_${text}`,
|
|
88
|
+
data,
|
|
89
|
+
weight: 400,
|
|
90
|
+
style: "normal"
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
} catch (e) {
|
|
94
|
+
console.error("Failed to load dynamic font for", text, ". Error:", e);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
return async (...args) => {
|
|
98
|
+
const key = JSON.stringify(args);
|
|
99
|
+
const cache = assetCache.get(key);
|
|
100
|
+
if (cache)
|
|
101
|
+
return cache;
|
|
102
|
+
const asset = await fn(...args);
|
|
103
|
+
assetCache.set(key, asset);
|
|
104
|
+
return asset;
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
async function render(satori, resvg, opts, defaultFonts, element) {
|
|
108
|
+
const options = Object.assign({
|
|
109
|
+
width: 1200,
|
|
110
|
+
height: 630,
|
|
111
|
+
debug: false
|
|
112
|
+
}, opts);
|
|
113
|
+
const svg = await satori(element, {
|
|
114
|
+
width: options.width,
|
|
115
|
+
height: options.height,
|
|
116
|
+
debug: options.debug,
|
|
117
|
+
fonts: options.fonts || defaultFonts,
|
|
118
|
+
loadAdditionalAsset: loadDynamicAsset({
|
|
119
|
+
emoji: options.emoji
|
|
120
|
+
})
|
|
121
|
+
});
|
|
122
|
+
const resvgJS = new resvg.Resvg(svg, {
|
|
123
|
+
fitTo: {
|
|
124
|
+
mode: "width",
|
|
125
|
+
value: options.width
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
return resvgJS.render().asPng();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export {
|
|
132
|
+
render
|
|
133
|
+
};
|
|
134
|
+
/*! Copyright Twitter Inc. and other contributors. Licensed under MIT */
|
|
135
|
+
//# sourceMappingURL=chunk-GIRBQXLD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/emoji/index.ts","../src/og.ts"],"sourcesContent":["/**\n * Modified version of https://unpkg.com/twemoji@13.1.0/dist/twemoji.esm.js.\n */\n\n/*! Copyright Twitter Inc. and other contributors. Licensed under MIT */\n\nconst U200D = String.fromCharCode(8205)\nconst UFE0Fg = /\\uFE0F/g\n\nexport function getIconCode(char: string) {\n return toCodePoint(char.indexOf(U200D) < 0 ? char.replace(UFE0Fg, '') : char)\n}\n\nfunction toCodePoint(unicodeSurrogates: string) {\n var r: string[] = [],\n c = 0,\n p = 0,\n i = 0\n while (i < unicodeSurrogates.length) {\n c = unicodeSurrogates.charCodeAt(i++)\n if (p) {\n r.push((65536 + ((p - 55296) << 10) + (c - 56320)).toString(16))\n p = 0\n } else if (55296 <= c && c <= 56319) {\n p = c\n } else {\n r.push(c.toString(16))\n }\n }\n return r.join('-')\n}\n\nconst apis = {\n twemoji: (code) =>\n 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/svg/' + code.toLowerCase() + '.svg',\n openmoji: 'https://cdn.jsdelivr.net/npm/@svgmoji/openmoji@2.0.0/svg/',\n blobmoji: 'https://cdn.jsdelivr.net/npm/@svgmoji/blob@2.0.0/svg/',\n noto: 'https://cdn.jsdelivr.net/gh/svgmoji/svgmoji/packages/svgmoji__noto/svg/',\n fluent: (code) =>\n 'https://cdn.jsdelivr.net/gh/shuding/fluentui-emoji-unicode/assets/' +\n code.toLowerCase() +\n '_color.svg',\n fluentFlat: (code) =>\n 'https://cdn.jsdelivr.net/gh/shuding/fluentui-emoji-unicode/assets/' +\n code.toLowerCase() +\n '_flat.svg',\n}\n\nexport type EmojiType = keyof typeof apis\n\nexport function loadEmoji(code: string, type?: EmojiType) {\n // https://github.com/svgmoji/svgmoji\n if (!type || !apis[type]) {\n type = 'twemoji'\n }\n const api = apis[type]\n if (typeof api === 'function') {\n return fetch(api(code))\n }\n return fetch(`${api}${code.toUpperCase()}.svg`)\n}\n","import { loadEmoji, getIconCode, EmojiType } from './emoji'\n\n// @TODO: Support font style and weights, and make this option extensible rather\n// than built-in.\n// @TODO: Cover most languages with Noto Sans.\nconst languageFontMap = {\n 'ja-JP': 'Noto+Sans+JP',\n 'ko-KR': 'Noto+Sans+KR',\n 'zh-CN': 'Noto+Sans+SC',\n 'zh-TW': 'Noto+Sans+TC',\n 'zh-HK': 'Noto+Sans+HK',\n 'th-TH': 'Noto+Sans+Thai',\n 'bn-IN': 'Noto+Sans+Bengali',\n 'ar-AR': 'Noto+Sans+Arabic',\n 'ta-IN': 'Noto+Sans+Tamil',\n 'ml-IN': 'Noto+Sans+Malayalam',\n 'he-IL': 'Noto+Sans+Hebrew',\n 'te-IN': 'Noto+Sans+Telugu',\n devanagari: 'Noto+Sans+Devanagari',\n kannada: 'Noto+Sans+Kannada',\n symbol: ['Noto+Sans+Symbols', 'Noto+Sans+Symbols+2'],\n math: 'Noto+Sans+Math',\n unknown: 'Noto+Sans',\n}\n\nasync function loadGoogleFont(font: string, text: string) {\n if (!font || !text) return\n\n const API = `https://fonts.googleapis.com/css2?family=${font}&text=${encodeURIComponent(\n text\n )}`\n\n const css = await (\n await fetch(API, {\n headers: {\n // Make sure it returns TTF.\n 'User-Agent':\n 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; de-at) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1',\n },\n })\n ).text()\n\n const resource = css.match(/src: url\\((.+)\\) format\\('(opentype|truetype)'\\)/)\n if (!resource) throw new Error('Failed to load font')\n\n return fetch(resource[1]).then((res) => res.arrayBuffer())\n}\n\nconst assetCache = new Map<string, any>()\nconst loadDynamicAsset = ({ emoji }: { emoji?: EmojiType }) => {\n const fn = async (code, text) => {\n if (code === 'emoji') {\n // It's an emoji, load the image.\n return (\n `data:image/svg+xml;base64,` +\n btoa(await (await loadEmoji(getIconCode(text), emoji)).text())\n )\n }\n\n // Try to load from Google Fonts.\n if (!languageFontMap[code]) code = 'unknown'\n\n try {\n const data = await loadGoogleFont(languageFontMap[code], text)\n\n if (data) {\n return {\n name: `satori_${code}_fallback_${text}`,\n data,\n weight: 400,\n style: 'normal',\n }\n }\n } catch (e) {\n console.error('Failed to load dynamic font for', text, '. Error:', e)\n }\n }\n\n return async (...args: Parameters<typeof fn>) => {\n const key = JSON.stringify(args)\n const cache = assetCache.get(key)\n if (cache) return cache\n\n const asset = await fn(...args)\n assetCache.set(key, asset)\n return asset\n }\n}\n\nexport default async function render(\n satori,\n resvg,\n opts,\n defaultFonts,\n element\n) {\n const options = Object.assign(\n {\n width: 1200,\n height: 630,\n debug: false,\n },\n opts\n )\n\n const svg = await satori(element, {\n width: options.width,\n height: options.height,\n debug: options.debug,\n fonts: options.fonts || defaultFonts,\n loadAdditionalAsset: loadDynamicAsset({\n emoji: options.emoji,\n }),\n })\n\n const resvgJS = new resvg.Resvg(svg, {\n fitTo: {\n mode: 'width',\n value: options.width,\n },\n })\n\n return resvgJS.render().asPng()\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {
|
|
2
|
+
render
|
|
3
|
+
} from "./chunk-GIRBQXLD.js";
|
|
4
|
+
|
|
5
|
+
// src/index.edge.ts
|
|
6
|
+
import satori, { init as initSatori } from "satori/wasm";
|
|
7
|
+
import initYoga from "yoga-wasm-web";
|
|
8
|
+
import * as resvg from "@resvg/resvg-wasm";
|
|
9
|
+
import resvg_wasm from "./resvg.wasm?module";
|
|
10
|
+
import yoga_wasm from "./yoga.wasm?module";
|
|
11
|
+
var initializedResvg = resvg.initWasm(resvg_wasm);
|
|
12
|
+
var initializedYoga = initYoga(yoga_wasm).then((yoga) => initSatori(yoga));
|
|
13
|
+
var fallbackFont = fetch(new URL("./noto-sans-v27-latin-regular.ttf", import.meta.url)).then((res) => res.arrayBuffer());
|
|
14
|
+
var _a, _b;
|
|
15
|
+
var isDev = ((_b = (_a = globalThis == null ? void 0 : globalThis.process) == null ? void 0 : _a.env) == null ? void 0 : _b.NODE_ENV) === "development";
|
|
16
|
+
var ImageResponse = class {
|
|
17
|
+
constructor(element, options = {}) {
|
|
18
|
+
const result = new ReadableStream({
|
|
19
|
+
async start(controller) {
|
|
20
|
+
await initializedYoga;
|
|
21
|
+
await initializedResvg;
|
|
22
|
+
const fontData = await fallbackFont;
|
|
23
|
+
const fonts = [
|
|
24
|
+
{
|
|
25
|
+
name: "sans serif",
|
|
26
|
+
data: fontData,
|
|
27
|
+
weight: 700,
|
|
28
|
+
style: "normal"
|
|
29
|
+
}
|
|
30
|
+
];
|
|
31
|
+
const result2 = await render(satori, resvg, options, fonts, element);
|
|
32
|
+
controller.enqueue(result2);
|
|
33
|
+
controller.close();
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
return new Response(result, {
|
|
37
|
+
headers: {
|
|
38
|
+
"content-type": "image/png",
|
|
39
|
+
"cache-control": isDev ? "no-cache, no-store" : "public, immutable, no-transform, max-age=31536000",
|
|
40
|
+
...options.headers
|
|
41
|
+
},
|
|
42
|
+
status: options.status,
|
|
43
|
+
statusText: options.statusText
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
export {
|
|
48
|
+
ImageResponse
|
|
49
|
+
};
|
|
50
|
+
//# sourceMappingURL=index.edge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.edge.ts"],"sourcesContent":["import type { ReactElement } from 'react'\nimport type { ImageResponseOptions } from './types'\n\n// @ts-ignore\nimport satori, { init as initSatori } from 'satori/wasm'\nimport initYoga from 'yoga-wasm-web'\nimport * as resvg from '@resvg/resvg-wasm'\n\n// @ts-ignore\nimport resvg_wasm from './resvg.wasm?module'\n// @ts-ignore\nimport yoga_wasm from './yoga.wasm?module'\n\nimport render from './og'\n\nconst initializedResvg = resvg.initWasm(resvg_wasm)\nconst initializedYoga = initYoga(yoga_wasm).then((yoga) => initSatori(yoga))\nconst fallbackFont = fetch(\n new URL('./noto-sans-v27-latin-regular.ttf', import.meta.url)\n).then((res) => res.arrayBuffer())\n\nconst isDev = globalThis?.process?.env?.NODE_ENV === 'development'\n\nexport class ImageResponse {\n constructor(element: ReactElement, options: ImageResponseOptions = {}) {\n const result = new ReadableStream({\n async start(controller) {\n await initializedYoga\n await initializedResvg\n const fontData = await fallbackFont\n const fonts = [\n {\n name: 'sans serif',\n data: fontData,\n weight: 700,\n style: 'normal',\n },\n ]\n\n const result = await render(satori, resvg, options, fonts, element)\n\n controller.enqueue(result)\n controller.close()\n },\n })\n\n return new Response(result, {\n headers: {\n 'content-type': 'image/png',\n 'cache-control': isDev\n ? 'no-cache, no-store'\n : 'public, immutable, no-transform, max-age=31536000',\n ...options.headers,\n },\n status: options.status,\n statusText: options.statusText,\n })\n }\n}\n"],"mappings":";;;;;AAIA;AACA;AACA;AAGA;AAEA;AAIA,IAAM,mBAAmB,AAAM,eAAS,UAAU;AAClD,IAAM,kBAAkB,SAAS,SAAS,EAAE,KAAK,CAAC,SAAS,WAAW,IAAI,CAAC;AAC3E,IAAM,eAAe,MACnB,IAAI,IAAI,qCAAqC,YAAY,GAAG,CAC9D,EAAE,KAAK,CAAC,QAAQ,IAAI,YAAY,CAAC;AAnBjC;AAqBA,IAAM,QAAQ,sDAAY,YAAZ,mBAAqB,QAArB,mBAA0B,cAAa;AAE9C,IAAM,gBAAN,MAAoB;AAAA,EACzB,YAAY,SAAuB,UAAgC,CAAC,GAAG;AACrE,UAAM,SAAS,IAAI,eAAe;AAAA,MAChC,MAAM,MAAM,YAAY;AACtB,cAAM;AACN,cAAM;AACN,cAAM,WAAW,MAAM;AACvB,cAAM,QAAQ;AAAA,UACZ;AAAA,YACE,MAAM;AAAA,YACN,MAAM;AAAA,YACN,QAAQ;AAAA,YACR,OAAO;AAAA,UACT;AAAA,QACF;AAEA,cAAM,UAAS,MAAM,OAAO,QAAQ,OAAO,SAAS,OAAO,OAAO;AAElE,mBAAW,QAAQ,OAAM;AACzB,mBAAW,MAAM;AAAA,MACnB;AAAA,IACF,CAAC;AAED,WAAO,IAAI,SAAS,QAAQ;AAAA,MAC1B,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,iBAAiB,QACb,uBACA;AAAA,QACJ,GAAG,QAAQ;AAAA,MACb;AAAA,MACA,QAAQ,QAAQ;AAAA,MAChB,YAAY,QAAQ;AAAA,IACtB,CAAC;AAAA,EACH;AACF;","names":[]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import type { ReactElement } from 'react';
|
|
3
|
+
import type { ImageResponseNodeOptions, ImageResponseOptions } from './types';
|
|
4
|
+
import { Readable } from 'stream';
|
|
5
|
+
export declare class ImageResponse {
|
|
6
|
+
constructor(element: ReactElement, options?: ImageResponseOptions);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Creates a pipeable stream of the rendered image in a lambda function.
|
|
10
|
+
* All parameters are the same as `ImageResponse`.
|
|
11
|
+
* @example
|
|
12
|
+
* ```js
|
|
13
|
+
* import { unstable_createNodejsStream } from '@vercel/og'
|
|
14
|
+
*
|
|
15
|
+
* export default async (req, res) => {
|
|
16
|
+
* const stream = await unstable_createNodejsStream(<div>Hello World</div>, { ... })
|
|
17
|
+
* res.setHeader('Content-Type', 'image/png')
|
|
18
|
+
* res.setHeader('Cache-Control', 'public, max-age=31536000, immutable')
|
|
19
|
+
* res.statusCode = 200
|
|
20
|
+
* res.statusMessage = 'OK'
|
|
21
|
+
* stream.pipe(res)
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare function unstable_createNodejsStream(element: ReactElement, options?: Omit<ImageResponseNodeOptions, 'status' | 'statusText' | 'headers'>): Promise<Readable>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import {
|
|
2
|
+
render
|
|
3
|
+
} from "./chunk-GIRBQXLD.js";
|
|
4
|
+
|
|
5
|
+
// src/index.node.ts
|
|
6
|
+
import satoriMod, { init as initSatori } from "satori/wasm";
|
|
7
|
+
import initYoga from "yoga-wasm-web";
|
|
8
|
+
import * as resvg from "@resvg/resvg-wasm";
|
|
9
|
+
import { Readable } from "stream";
|
|
10
|
+
import fs from "fs";
|
|
11
|
+
import { fileURLToPath } from "url";
|
|
12
|
+
var satori = satoriMod.default || satoriMod;
|
|
13
|
+
var fontData = fs.readFileSync(fileURLToPath(`${import.meta.url}/../noto-sans-v27-latin-regular.ttf`));
|
|
14
|
+
var yoga_wasm = fs.readFileSync(fileURLToPath(`${import.meta.url}/../yoga.wasm`));
|
|
15
|
+
var resvg_wasm = fs.readFileSync(fileURLToPath(`${import.meta.url}/../resvg.wasm`));
|
|
16
|
+
var initializedResvg = resvg.initWasm(resvg_wasm);
|
|
17
|
+
var initializedYoga = initYoga(yoga_wasm).then((yoga) => initSatori(yoga));
|
|
18
|
+
var _a, _b;
|
|
19
|
+
var isDev = ((_b = (_a = globalThis == null ? void 0 : globalThis.process) == null ? void 0 : _a.env) == null ? void 0 : _b.NODE_ENV) === "development";
|
|
20
|
+
var ImageResponse = class {
|
|
21
|
+
constructor(element, options = {}) {
|
|
22
|
+
if (typeof Response === "undefined" || typeof ReadableStream === "undefined") {
|
|
23
|
+
throw new Error("The `ImageResponse` API is not supported in this runtime, use the `unstable_createNodejsStream` API instead or switch to the Vercel Edge Runtime.");
|
|
24
|
+
}
|
|
25
|
+
const result = new ReadableStream({
|
|
26
|
+
async start(controller) {
|
|
27
|
+
await initializedYoga;
|
|
28
|
+
await initializedResvg;
|
|
29
|
+
const fonts = [
|
|
30
|
+
{
|
|
31
|
+
name: "sans serif",
|
|
32
|
+
data: fontData,
|
|
33
|
+
weight: 700,
|
|
34
|
+
style: "normal"
|
|
35
|
+
}
|
|
36
|
+
];
|
|
37
|
+
const result2 = await render(satori, resvg, options, fonts, element);
|
|
38
|
+
controller.enqueue(result2);
|
|
39
|
+
controller.close();
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
return new Response(result, {
|
|
43
|
+
headers: {
|
|
44
|
+
"content-type": "image/png",
|
|
45
|
+
"cache-control": isDev ? "no-cache, no-store" : "public, immutable, no-transform, max-age=31536000",
|
|
46
|
+
...options.headers
|
|
47
|
+
},
|
|
48
|
+
status: options.status,
|
|
49
|
+
statusText: options.statusText
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
async function unstable_createNodejsStream(element, options = {}) {
|
|
54
|
+
await initializedYoga;
|
|
55
|
+
await initializedResvg;
|
|
56
|
+
const fonts = [
|
|
57
|
+
{
|
|
58
|
+
name: "sans serif",
|
|
59
|
+
data: fontData,
|
|
60
|
+
weight: 700,
|
|
61
|
+
style: "normal"
|
|
62
|
+
}
|
|
63
|
+
];
|
|
64
|
+
const result = await render(satori, resvg, options, fonts, element);
|
|
65
|
+
return Readable.from(Buffer.from(result));
|
|
66
|
+
}
|
|
67
|
+
export {
|
|
68
|
+
ImageResponse,
|
|
69
|
+
unstable_createNodejsStream
|
|
70
|
+
};
|
|
71
|
+
//# sourceMappingURL=index.node.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.node.ts"],"sourcesContent":["import type { ReactElement } from 'react'\nimport type { ImageResponseNodeOptions, ImageResponseOptions } from './types'\n\n// @ts-ignore\nimport satoriMod, { init as initSatori } from 'satori/wasm'\nimport initYoga from 'yoga-wasm-web'\nimport * as resvg from '@resvg/resvg-wasm'\n\nimport { Readable } from 'stream'\nimport fs from 'fs'\nimport { fileURLToPath } from 'url'\n\nimport render from './og'\n\nconst satori = satoriMod.default || satoriMod\n\nconst fontData = fs.readFileSync(\n fileURLToPath(`${import.meta.url}/../noto-sans-v27-latin-regular.ttf`)\n)\nconst yoga_wasm = fs.readFileSync(\n fileURLToPath(`${import.meta.url}/../yoga.wasm`)\n)\nconst resvg_wasm = fs.readFileSync(\n fileURLToPath(`${import.meta.url}/../resvg.wasm`)\n)\n\nconst initializedResvg = resvg.initWasm(resvg_wasm)\nconst initializedYoga = initYoga(yoga_wasm).then((yoga) => initSatori(yoga))\n\nconst isDev = globalThis?.process?.env?.NODE_ENV === 'development'\n\nexport class ImageResponse {\n constructor(element: ReactElement, options: ImageResponseOptions = {}) {\n // This is a workaround for Next.js to detect if the API is used in\n // pages/api or app API routes. When used in pages/api, we can't return\n // a Response instance and need another interface instead.\n if (\n typeof Response === 'undefined' ||\n typeof ReadableStream === 'undefined'\n ) {\n throw new Error(\n 'The `ImageResponse` API is not supported in this runtime, use the `unstable_createNodejsStream` API instead or switch to the Vercel Edge Runtime.'\n )\n }\n\n const result = new ReadableStream({\n async start(controller) {\n await initializedYoga\n await initializedResvg\n const fonts = [\n {\n name: 'sans serif',\n data: fontData,\n weight: 700,\n style: 'normal',\n },\n ]\n\n const result = await render(satori, resvg, options, fonts, element)\n\n controller.enqueue(result)\n controller.close()\n },\n })\n\n return new Response(result, {\n headers: {\n 'content-type': 'image/png',\n 'cache-control': isDev\n ? 'no-cache, no-store'\n : 'public, immutable, no-transform, max-age=31536000',\n ...options.headers,\n },\n status: options.status,\n statusText: options.statusText,\n })\n }\n}\n\n/**\n * Creates a pipeable stream of the rendered image in a lambda function.\n * All parameters are the same as `ImageResponse`.\n * @example\n * ```js\n * import { unstable_createNodejsStream } from '@vercel/og'\n *\n * export default async (req, res) => {\n * const stream = await unstable_createNodejsStream(<div>Hello World</div>, { ... })\n * res.setHeader('Content-Type', 'image/png')\n * res.setHeader('Cache-Control', 'public, max-age=31536000, immutable')\n * res.statusCode = 200\n * res.statusMessage = 'OK'\n * stream.pipe(res)\n * }\n * ```\n */\nexport async function unstable_createNodejsStream(\n element: ReactElement,\n options: Omit<\n ImageResponseNodeOptions,\n 'status' | 'statusText' | 'headers'\n > = {}\n) {\n await initializedYoga\n await initializedResvg\n const fonts = [\n {\n name: 'sans serif',\n data: fontData,\n weight: 700,\n style: 'normal',\n },\n ]\n\n const result = await render(satori, resvg, options, fonts, element)\n return Readable.from(Buffer.from(result))\n}\n"],"mappings":";;;;;AAIA;AACA;AACA;AAEA;AACA;AACA;AAIA,IAAM,SAAS,UAAU,WAAW;AAEpC,IAAM,WAAW,GAAG,aAClB,cAAc,GAAG,YAAY,wCAAwC,CACvE;AACA,IAAM,YAAY,GAAG,aACnB,cAAc,GAAG,YAAY,kBAAkB,CACjD;AACA,IAAM,aAAa,GAAG,aACpB,cAAc,GAAG,YAAY,mBAAmB,CAClD;AAEA,IAAM,mBAAmB,AAAM,eAAS,UAAU;AAClD,IAAM,kBAAkB,SAAS,SAAS,EAAE,KAAK,CAAC,SAAS,WAAW,IAAI,CAAC;AA3B3E;AA6BA,IAAM,QAAQ,sDAAY,YAAZ,mBAAqB,QAArB,mBAA0B,cAAa;AAE9C,IAAM,gBAAN,MAAoB;AAAA,EACzB,YAAY,SAAuB,UAAgC,CAAC,GAAG;AAIrE,QACE,OAAO,aAAa,eACpB,OAAO,mBAAmB,aAC1B;AACA,YAAM,IAAI,MACR,mJACF;AAAA,IACF;AAEA,UAAM,SAAS,IAAI,eAAe;AAAA,MAChC,MAAM,MAAM,YAAY;AACtB,cAAM;AACN,cAAM;AACN,cAAM,QAAQ;AAAA,UACZ;AAAA,YACE,MAAM;AAAA,YACN,MAAM;AAAA,YACN,QAAQ;AAAA,YACR,OAAO;AAAA,UACT;AAAA,QACF;AAEA,cAAM,UAAS,MAAM,OAAO,QAAQ,OAAO,SAAS,OAAO,OAAO;AAElE,mBAAW,QAAQ,OAAM;AACzB,mBAAW,MAAM;AAAA,MACnB;AAAA,IACF,CAAC;AAED,WAAO,IAAI,SAAS,QAAQ;AAAA,MAC1B,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,iBAAiB,QACb,uBACA;AAAA,QACJ,GAAG,QAAQ;AAAA,MACb;AAAA,MACA,QAAQ,QAAQ;AAAA,MAChB,YAAY,QAAQ;AAAA,IACtB,CAAC;AAAA,EACH;AACF;AAmBA,2CACE,SACA,UAGI,CAAC,GACL;AACA,QAAM;AACN,QAAM;AACN,QAAM,QAAQ;AAAA,IACZ;AAAA,MACE,MAAM;AAAA,MACN,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,OAAO;AAAA,IACT;AAAA,EACF;AAEA,QAAM,SAAS,MAAM,OAAO,QAAQ,OAAO,SAAS,OAAO,OAAO;AAClE,SAAO,SAAS,KAAK,OAAO,KAAK,MAAM,CAAC;AAC1C;","names":[]}
|
package/dist/og.d.ts
CHANGED
|
@@ -1,44 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import type { SatoriOptions } from 'satori';
|
|
3
|
-
import { EmojiType } from './emoji';
|
|
4
|
-
export declare type ImageResponseOptions = ConstructorParameters<typeof Response>[1] & {
|
|
5
|
-
/**
|
|
6
|
-
* The width of the image.
|
|
7
|
-
*
|
|
8
|
-
* @type {number}
|
|
9
|
-
* @default 1200
|
|
10
|
-
*/
|
|
11
|
-
width?: number;
|
|
12
|
-
/**
|
|
13
|
-
* The height of the image.
|
|
14
|
-
*
|
|
15
|
-
* @type {number}
|
|
16
|
-
* @default 630
|
|
17
|
-
*/
|
|
18
|
-
height?: number;
|
|
19
|
-
/**
|
|
20
|
-
* Display debug information on the image.
|
|
21
|
-
*
|
|
22
|
-
* @type {boolean}
|
|
23
|
-
* @default false
|
|
24
|
-
*/
|
|
25
|
-
debug?: boolean;
|
|
26
|
-
/**
|
|
27
|
-
* A list of fonts to use.
|
|
28
|
-
*
|
|
29
|
-
* @type {{ data: ArrayBuffer; name: string; weight?: 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900; style?: 'normal' | 'italic' }[]}
|
|
30
|
-
* @default Noto Sans Latin Regular.
|
|
31
|
-
*/
|
|
32
|
-
fonts?: SatoriOptions['fonts'];
|
|
33
|
-
/**
|
|
34
|
-
* Using a specific Emoji style. Defaults to `twemoji`.
|
|
35
|
-
*
|
|
36
|
-
* @link https://github.com/vercel/og#emoji
|
|
37
|
-
* @type {EmojiType}
|
|
38
|
-
* @default 'twemoji'
|
|
39
|
-
*/
|
|
40
|
-
emoji?: EmojiType;
|
|
41
|
-
};
|
|
42
|
-
export declare class ImageResponse {
|
|
43
|
-
constructor(element: ReactElement, options?: ImageResponseOptions);
|
|
44
|
-
}
|
|
1
|
+
export default function render(satori: any, resvg: any, opts: any, defaultFonts: any, element: any): Promise<any>;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import type { SatoriOptions } from 'satori';
|
|
3
|
+
import type { EmojiType } from './emoji';
|
|
4
|
+
import type { OutgoingHttpHeader } from 'http';
|
|
5
|
+
declare type ImageOptions = {
|
|
6
|
+
/**
|
|
7
|
+
* The width of the image.
|
|
8
|
+
*
|
|
9
|
+
* @type {number}
|
|
10
|
+
* @default 1200
|
|
11
|
+
*/
|
|
12
|
+
width?: number;
|
|
13
|
+
/**
|
|
14
|
+
* The height of the image.
|
|
15
|
+
*
|
|
16
|
+
* @type {number}
|
|
17
|
+
* @default 630
|
|
18
|
+
*/
|
|
19
|
+
height?: number;
|
|
20
|
+
/**
|
|
21
|
+
* Display debug information on the image.
|
|
22
|
+
*
|
|
23
|
+
* @type {boolean}
|
|
24
|
+
* @default false
|
|
25
|
+
*/
|
|
26
|
+
debug?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* A list of fonts to use.
|
|
29
|
+
*
|
|
30
|
+
* @type {{ data: ArrayBuffer; name: string; weight?: 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900; style?: 'normal' | 'italic' }[]}
|
|
31
|
+
* @default Noto Sans Latin Regular.
|
|
32
|
+
*/
|
|
33
|
+
fonts?: SatoriOptions['fonts'];
|
|
34
|
+
/**
|
|
35
|
+
* Using a specific Emoji style. Defaults to `twemoji`.
|
|
36
|
+
*
|
|
37
|
+
* @link https://github.com/vercel/og#emoji
|
|
38
|
+
* @type {EmojiType}
|
|
39
|
+
* @default 'twemoji'
|
|
40
|
+
*/
|
|
41
|
+
emoji?: EmojiType;
|
|
42
|
+
};
|
|
43
|
+
export declare type ImageResponseNodeOptions = ImageOptions & {
|
|
44
|
+
status?: number;
|
|
45
|
+
statusText?: string;
|
|
46
|
+
headers?: OutgoingHttpHeader[];
|
|
47
|
+
};
|
|
48
|
+
export declare type ImageResponseOptions = ImageOptions & ConstructorParameters<typeof Response>[1];
|
|
49
|
+
declare module 'react' {
|
|
50
|
+
interface HTMLAttributes<T> {
|
|
51
|
+
/**
|
|
52
|
+
* Specify styles using Tailwind CSS classes. This feature is currently experimental.
|
|
53
|
+
* If `style` prop is also specified, styles generated with `tw` prop will be overridden.
|
|
54
|
+
*
|
|
55
|
+
* Example:
|
|
56
|
+
* - `tw='w-full h-full bg-blue-200'`
|
|
57
|
+
* - `tw='text-9xl'`
|
|
58
|
+
* - `tw='text-[80px]'`
|
|
59
|
+
*
|
|
60
|
+
* @type {string}
|
|
61
|
+
*/
|
|
62
|
+
tw?: string;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,17 +1,51 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vercel/og",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Generate Open Graph Images dynamically from HTML/CSS without a browser",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
|
-
"types": "dist/index.d.ts",
|
|
7
|
+
"types": "./dist/index.node.d.ts",
|
|
8
8
|
"files": [
|
|
9
|
-
"dist/**/*"
|
|
10
|
-
"vendor/**/*"
|
|
9
|
+
"dist/**/*"
|
|
11
10
|
],
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"edge": {
|
|
14
|
+
"types": "./dist/index.edge.d.ts",
|
|
15
|
+
"default": "./dist/index.edge.js"
|
|
16
|
+
},
|
|
17
|
+
"edge-light": {
|
|
18
|
+
"types": "./dist/index.edge.d.ts",
|
|
19
|
+
"default": "./dist/index.edge.js"
|
|
20
|
+
},
|
|
21
|
+
"browser": {
|
|
22
|
+
"types": "./dist/index.edge.d.ts",
|
|
23
|
+
"default": "./dist/index.edge.js"
|
|
24
|
+
},
|
|
25
|
+
"worker": {
|
|
26
|
+
"types": "./dist/index.edge.d.ts",
|
|
27
|
+
"default": "./dist/index.edge.js"
|
|
28
|
+
},
|
|
29
|
+
"workerd": {
|
|
30
|
+
"types": "./dist/index.edge.d.ts",
|
|
31
|
+
"default": "./dist/index.edge.js"
|
|
32
|
+
},
|
|
33
|
+
"import": {
|
|
34
|
+
"types": "./dist/index.node.d.ts",
|
|
35
|
+
"default": "./dist/index.node.js"
|
|
36
|
+
},
|
|
37
|
+
"node": {
|
|
38
|
+
"types": "./dist/index.node.d.ts",
|
|
39
|
+
"default": "./dist/index.node.js"
|
|
40
|
+
},
|
|
41
|
+
"default": "./dist/index.node.js"
|
|
42
|
+
},
|
|
43
|
+
"./package.json": "./package.json"
|
|
44
|
+
},
|
|
12
45
|
"scripts": {
|
|
13
|
-
"build": "tsup
|
|
14
|
-
"types": "tsc --project tsconfig.json"
|
|
46
|
+
"build": "tsup && pnpm types && pnpm copy",
|
|
47
|
+
"types": "tsc --project tsconfig.json",
|
|
48
|
+
"copy": "node scripts/copy-vendors.js"
|
|
15
49
|
},
|
|
16
50
|
"keywords": [
|
|
17
51
|
"open graph image",
|
package/dist/index.d.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export * from './og';
|
|
2
|
-
declare module 'react' {
|
|
3
|
-
interface HTMLAttributes<T> {
|
|
4
|
-
/**
|
|
5
|
-
* Specify styles using Tailwind CSS classes. This feature is currently experimental.
|
|
6
|
-
* If `style` prop is also specified, styles generated with `tw` prop will be overridden.
|
|
7
|
-
*
|
|
8
|
-
* Example:
|
|
9
|
-
* - `tw='w-full h-full bg-blue-200'`
|
|
10
|
-
* - `tw='text-9xl'`
|
|
11
|
-
* - `tw='text-[80px]'`
|
|
12
|
-
*
|
|
13
|
-
* @type {string}
|
|
14
|
-
*/
|
|
15
|
-
tw?: string;
|
|
16
|
-
}
|
|
17
|
-
}
|
package/dist/index.js
DELETED
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import S,{init as b}from"satori/wasm";import N from"yoga-wasm-web";import*as i from"@resvg/resvg-wasm";import T from"../vendor/resvg.wasm?module";import C from"../vendor/yoga.wasm?module";var v=String.fromCharCode(8205),j=/\uFE0F/g;function c(t){return y(t.indexOf(v)<0?t.replace(j,""):t)}function y(t){for(var n=[],o=0,e=0,s=0;s<t.length;)o=t.charCodeAt(s++),e?(n.push((65536+(e-55296<<10)+(o-56320)).toString(16)),e=0):55296<=o&&o<=56319?e=o:n.push(o.toString(16));return n.join("-")}var r={twemoji:t=>"https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/svg/"+t.toLowerCase()+".svg",openmoji:"https://cdn.jsdelivr.net/npm/@svgmoji/openmoji@2.0.0/svg/",blobmoji:"https://cdn.jsdelivr.net/npm/@svgmoji/blob@2.0.0/svg/",noto:"https://cdn.jsdelivr.net/gh/svgmoji/svgmoji/packages/svgmoji__noto/svg/",fluent:t=>"https://cdn.jsdelivr.net/gh/shuding/fluentui-emoji-unicode/assets/"+t.toLowerCase()+"_color.svg",fluentFlat:t=>"https://cdn.jsdelivr.net/gh/shuding/fluentui-emoji-unicode/assets/"+t.toLowerCase()+"_flat.svg"};function m(t,n){(!n||!r[n])&&(n="twemoji");let o=r[n];return fetch(typeof o=="function"?o(t):`${o}${t.toUpperCase()}.svg`)}var E=i.initWasm(T),R=N(C).then(t=>b(t)),_=fetch(new URL("../vendor/noto-sans-v27-latin-regular.ttf",import.meta.url)).then(t=>t.arrayBuffer()),f,d,k=((d=(f=globalThis==null?void 0:globalThis.process)==null?void 0:f.env)==null?void 0:d.NODE_ENV)==="development",l={"ja-JP":"Noto+Sans+JP","ko-KR":"Noto+Sans+KR","zh-CN":"Noto+Sans+SC","zh-TW":"Noto+Sans+TC","zh-HK":"Noto+Sans+HK","th-TH":"Noto+Sans+Thai","bn-IN":"Noto+Sans+Bengali","ar-AR":"Noto+Sans+Arabic","ta-IN":"Noto+Sans+Tamil","ml-IN":"Noto+Sans+Malayalam","he-IL":"Noto+Sans+Hebrew","te-IN":"Noto+Sans+Telugu",devanagari:"Noto+Sans+Devanagari",kannada:"Noto+Sans+Kannada",symbol:["Noto+Sans+Symbols","Noto+Sans+Symbols+2"],math:"Noto+Sans+Math",unknown:"Noto+Sans"};async function x(t,n){if(!t||!n)return;let o=`https://fonts.googleapis.com/css2?family=${t}&text=${encodeURIComponent(n)}`,s=(await(await fetch(o,{headers:{"User-Agent":"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; de-at) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1"}})).text()).match(/src: url\((.+)\) format\('(opentype|truetype)'\)/);if(!s)throw new Error("Failed to load font");return fetch(s[1]).then(a=>a.arrayBuffer())}var g=new Map,I=({emoji:t})=>{let n=async(o,e)=>{if(o==="emoji")return"data:image/svg+xml;base64,"+btoa(await(await m(c(e),t)).text());l[o]||(o="unknown");try{let s=await x(l[o],e);if(s)return{name:`satori_${o}_fallback_${e}`,data:s,weight:400,style:"normal"}}catch(s){console.error("Failed to load dynamic font for",e,". Error:",s)}};return async(...o)=>{let e=JSON.stringify(o),s=g.get(e);if(s)return s;let a=await n(...o);return g.set(e,a),a}},p=class{constructor(n,o={}){let e=Object.assign({width:1200,height:630,debug:!1},o),s=new ReadableStream({async start(a){await R,await E;let h=await _,u=await S(n,{width:e.width,height:e.height,debug:e.debug,fonts:e.fonts||[{name:"sans serif",data:h,weight:700,style:"normal"}],loadAdditionalAsset:I({emoji:e.emoji})}),w=new i.Resvg(u,{fitTo:{mode:"width",value:e.width}}).render().asPng();a.enqueue(w),a.close()}});return new Response(s,{headers:{"content-type":"image/png","cache-control":k?"no-cache, no-store":"public, immutable, no-transform, max-age=31536000",...e.headers},status:e.status,statusText:e.statusText})}};export{p as ImageResponse};
|
|
2
|
-
/*! Copyright Twitter Inc. and other contributors. Licensed under MIT */
|
|
3
|
-
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/og.ts","../src/emoji/index.ts"],"sourcesContent":["import type { ReactElement } from 'react'\nimport type { SatoriOptions } from 'satori'\n\n// @ts-ignore\nimport satori, { init as initSatori } from 'satori/wasm'\nimport initYoga from 'yoga-wasm-web'\nimport * as resvg from '@resvg/resvg-wasm'\n\n// @ts-ignore\nimport resvg_wasm from '../vendor/resvg.wasm?module'\n// @ts-ignore\nimport yoga_wasm from '../vendor/yoga.wasm?module'\n\nimport { loadEmoji, getIconCode, EmojiType } from './emoji'\n\nconst initializedResvg = resvg.initWasm(resvg_wasm)\nconst initializedYoga = initYoga(yoga_wasm).then((yoga) => initSatori(yoga))\nconst fallbackFont = fetch(\n new URL('../vendor/noto-sans-v27-latin-regular.ttf', import.meta.url)\n).then((res) => res.arrayBuffer())\nconst isDev = globalThis?.process?.env?.NODE_ENV === 'development'\n\nexport type ImageResponseOptions = ConstructorParameters<typeof Response>[1] & {\n /**\n * The width of the image.\n *\n * @type {number}\n * @default 1200\n */\n width?: number\n /**\n * The height of the image.\n *\n * @type {number}\n * @default 630\n */\n height?: number\n /**\n * Display debug information on the image.\n *\n * @type {boolean}\n * @default false\n */\n debug?: boolean\n /**\n * A list of fonts to use.\n *\n * @type {{ data: ArrayBuffer; name: string; weight?: 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900; style?: 'normal' | 'italic' }[]}\n * @default Noto Sans Latin Regular.\n */\n fonts?: SatoriOptions['fonts']\n /**\n * Using a specific Emoji style. Defaults to `twemoji`.\n *\n * @link https://github.com/vercel/og#emoji\n * @type {EmojiType}\n * @default 'twemoji'\n */\n emoji?: EmojiType\n}\n\n// @TODO: Support font style and weights, and make this option extensible rather\n// than built-in.\n// @TODO: Cover most languages with Noto Sans.\nconst languageFontMap = {\n 'ja-JP': 'Noto+Sans+JP',\n 'ko-KR': 'Noto+Sans+KR',\n 'zh-CN': 'Noto+Sans+SC',\n 'zh-TW': 'Noto+Sans+TC',\n 'zh-HK': 'Noto+Sans+HK',\n 'th-TH': 'Noto+Sans+Thai',\n 'bn-IN': 'Noto+Sans+Bengali',\n 'ar-AR': 'Noto+Sans+Arabic',\n 'ta-IN': 'Noto+Sans+Tamil',\n 'ml-IN': 'Noto+Sans+Malayalam',\n 'he-IL': 'Noto+Sans+Hebrew',\n 'te-IN': 'Noto+Sans+Telugu',\n devanagari: 'Noto+Sans+Devanagari',\n kannada: 'Noto+Sans+Kannada',\n symbol: ['Noto+Sans+Symbols', 'Noto+Sans+Symbols+2'],\n math: 'Noto+Sans+Math',\n unknown: 'Noto+Sans',\n}\n\nasync function loadGoogleFont(font: string, text: string) {\n if (!font || !text) return\n\n const API = `https://fonts.googleapis.com/css2?family=${font}&text=${encodeURIComponent(\n text\n )}`\n\n const css = await (\n await fetch(API, {\n headers: {\n // Make sure it returns TTF.\n 'User-Agent':\n 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; de-at) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1',\n },\n })\n ).text()\n\n const resource = css.match(/src: url\\((.+)\\) format\\('(opentype|truetype)'\\)/)\n if (!resource) throw new Error('Failed to load font')\n\n return fetch(resource[1]).then((res) => res.arrayBuffer())\n}\n\nconst assetCache = new Map<string, any>()\nconst loadDynamicAsset = ({ emoji }: { emoji?: EmojiType }) => {\n const fn = async (code, text) => {\n if (code === 'emoji') {\n // It's an emoji, load the image.\n return (\n `data:image/svg+xml;base64,` +\n btoa(await (await loadEmoji(getIconCode(text), emoji)).text())\n )\n }\n\n // Try to load from Google Fonts.\n if (!languageFontMap[code]) code = 'unknown'\n\n try {\n const data = await loadGoogleFont(languageFontMap[code], text)\n\n if (data) {\n return {\n name: `satori_${code}_fallback_${text}`,\n data,\n weight: 400,\n style: 'normal',\n }\n }\n } catch (e) {\n console.error('Failed to load dynamic font for', text, '. Error:', e)\n }\n }\n\n return async (...args: Parameters<typeof fn>) => {\n const key = JSON.stringify(args)\n const cache = assetCache.get(key)\n if (cache) return cache\n\n const asset = await fn(...args)\n assetCache.set(key, asset)\n return asset\n }\n}\n\nexport class ImageResponse {\n constructor(element: ReactElement, options: ImageResponseOptions = {}) {\n const extendedOptions = Object.assign(\n {\n width: 1200,\n height: 630,\n debug: false,\n },\n options\n )\n\n const result = new ReadableStream({\n async start(controller) {\n await initializedYoga\n await initializedResvg\n const fontData = await fallbackFont\n\n const svg = await satori(element, {\n width: extendedOptions.width,\n height: extendedOptions.height,\n debug: extendedOptions.debug,\n fonts: extendedOptions.fonts || [\n {\n name: 'sans serif',\n data: fontData,\n weight: 700,\n style: 'normal',\n },\n ],\n loadAdditionalAsset: loadDynamicAsset({\n emoji: extendedOptions.emoji,\n }),\n } as SatoriOptions)\n\n const resvgJS = new resvg.Resvg(svg, {\n fitTo: {\n mode: 'width',\n value: extendedOptions.width,\n },\n })\n const data = resvgJS.render().asPng()\n controller.enqueue(data)\n controller.close()\n },\n })\n\n return new Response(result, {\n headers: {\n 'content-type': 'image/png',\n 'cache-control': isDev\n ? 'no-cache, no-store'\n : 'public, immutable, no-transform, max-age=31536000',\n ...extendedOptions.headers,\n },\n status: extendedOptions.status,\n statusText: extendedOptions.statusText,\n })\n }\n}\n","/**\n * Modified version of https://unpkg.com/twemoji@13.1.0/dist/twemoji.esm.js.\n */\n\n/*! Copyright Twitter Inc. and other contributors. Licensed under MIT */\n\nconst U200D = String.fromCharCode(8205)\nconst UFE0Fg = /\\uFE0F/g\n\nexport function getIconCode(char: string) {\n return toCodePoint(char.indexOf(U200D) < 0 ? char.replace(UFE0Fg, '') : char)\n}\n\nfunction toCodePoint(unicodeSurrogates: string) {\n var r: string[] = [],\n c = 0,\n p = 0,\n i = 0\n while (i < unicodeSurrogates.length) {\n c = unicodeSurrogates.charCodeAt(i++)\n if (p) {\n r.push((65536 + ((p - 55296) << 10) + (c - 56320)).toString(16))\n p = 0\n } else if (55296 <= c && c <= 56319) {\n p = c\n } else {\n r.push(c.toString(16))\n }\n }\n return r.join('-')\n}\n\nconst apis = {\n twemoji: (code) =>\n 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/svg/' + code.toLowerCase() + '.svg',\n openmoji: 'https://cdn.jsdelivr.net/npm/@svgmoji/openmoji@2.0.0/svg/',\n blobmoji: 'https://cdn.jsdelivr.net/npm/@svgmoji/blob@2.0.0/svg/',\n noto: 'https://cdn.jsdelivr.net/gh/svgmoji/svgmoji/packages/svgmoji__noto/svg/',\n fluent: (code) =>\n 'https://cdn.jsdelivr.net/gh/shuding/fluentui-emoji-unicode/assets/' +\n code.toLowerCase() +\n '_color.svg',\n fluentFlat: (code) =>\n 'https://cdn.jsdelivr.net/gh/shuding/fluentui-emoji-unicode/assets/' +\n code.toLowerCase() +\n '_flat.svg',\n}\n\nexport type EmojiType = keyof typeof apis\n\nexport function loadEmoji(code: string, type?: EmojiType) {\n // https://github.com/svgmoji/svgmoji\n if (!type || !apis[type]) {\n type = 'twemoji'\n }\n const api = apis[type]\n if (typeof api === 'function') {\n return fetch(api(code))\n }\n return fetch(`${api}${code.toUpperCase()}.svg`)\n}\n"],"mappings":"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","names":[]}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|