@vercel/og 0.8.3 → 0.8.4
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 +0 -1
- package/dist/index.edge.d.ts +9 -4
- package/dist/index.edge.js +20587 -33
- package/dist/index.edge.js.map +1 -1
- package/dist/index.node.d.ts +10 -6
- package/dist/index.node.js +20611 -73
- package/dist/index.node.js.map +1 -1
- package/dist/{types.d.ts → types-d38469ff.d.ts} +17 -7
- package/package.json +2 -3
- package/dist/emoji/index.d.ts +0 -15
- package/dist/emoji/index.js +0 -49
- package/dist/language/index.d.ts +0 -28
- package/dist/language/index.js +0 -103
- package/dist/og.d.ts +0 -1
- package/dist/og.js +0 -91
- package/dist/types.js +0 -1
|
@@ -1,7 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { SatoriOptions } from 'satori';
|
|
2
|
+
import { OutgoingHttpHeader } from 'http';
|
|
3
|
+
|
|
4
|
+
declare const apis: {
|
|
5
|
+
twemoji: (code: any) => string;
|
|
6
|
+
openmoji: string;
|
|
7
|
+
blobmoji: string;
|
|
8
|
+
noto: string;
|
|
9
|
+
fluent: (code: any) => string;
|
|
10
|
+
fluentFlat: (code: any) => string;
|
|
11
|
+
};
|
|
12
|
+
type EmojiType = keyof typeof apis;
|
|
13
|
+
|
|
5
14
|
type ImageOptions = {
|
|
6
15
|
/**
|
|
7
16
|
* The width of the image.
|
|
@@ -39,12 +48,12 @@ type ImageOptions = {
|
|
|
39
48
|
*/
|
|
40
49
|
emoji?: EmojiType;
|
|
41
50
|
};
|
|
42
|
-
|
|
51
|
+
type ImageResponseNodeOptions = ImageOptions & {
|
|
43
52
|
status?: number;
|
|
44
53
|
statusText?: string;
|
|
45
54
|
headers?: OutgoingHttpHeader[];
|
|
46
55
|
};
|
|
47
|
-
|
|
56
|
+
type ImageResponseOptions = ImageOptions & ConstructorParameters<typeof Response>[1];
|
|
48
57
|
declare module 'react' {
|
|
49
58
|
interface HTMLAttributes<T> {
|
|
50
59
|
/**
|
|
@@ -61,4 +70,5 @@ declare module 'react' {
|
|
|
61
70
|
tw?: string;
|
|
62
71
|
}
|
|
63
72
|
}
|
|
64
|
-
|
|
73
|
+
|
|
74
|
+
export { ImageResponseOptions as I, ImageResponseNodeOptions as a };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vercel/og",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.4",
|
|
4
4
|
"description": "Generate Open Graph Images dynamically from HTML/CSS without a browser",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.node.js",
|
|
@@ -44,8 +44,7 @@
|
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
46
|
"typecheck": "tsc --noEmit",
|
|
47
|
-
"build": "pnpm typecheck && tsup
|
|
48
|
-
"types": "tsc --project tsconfig.json",
|
|
47
|
+
"build": "pnpm typecheck && tsup --dts && pnpm copy",
|
|
49
48
|
"copy": "node scripts/copy-vendors.js"
|
|
50
49
|
},
|
|
51
50
|
"keywords": [
|
package/dist/emoji/index.d.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Modified version of https://unpkg.com/twemoji@13.1.0/dist/twemoji.esm.js.
|
|
3
|
-
*/
|
|
4
|
-
export declare function getIconCode(char: string): string;
|
|
5
|
-
declare const apis: {
|
|
6
|
-
twemoji: (code: any) => string;
|
|
7
|
-
openmoji: string;
|
|
8
|
-
blobmoji: string;
|
|
9
|
-
noto: string;
|
|
10
|
-
fluent: (code: any) => string;
|
|
11
|
-
fluentFlat: (code: any) => string;
|
|
12
|
-
};
|
|
13
|
-
export type EmojiType = keyof typeof apis;
|
|
14
|
-
export declare function loadEmoji(code: string, type?: EmojiType): Promise<Response>;
|
|
15
|
-
export {};
|
package/dist/emoji/index.js
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Modified version of https://unpkg.com/twemoji@13.1.0/dist/twemoji.esm.js.
|
|
3
|
-
*/
|
|
4
|
-
/*! Copyright Twitter Inc. and other contributors. Licensed under MIT */
|
|
5
|
-
const U200D = String.fromCharCode(8205);
|
|
6
|
-
const UFE0Fg = /\uFE0F/g;
|
|
7
|
-
export function getIconCode(char) {
|
|
8
|
-
return toCodePoint(char.indexOf(U200D) < 0 ? char.replace(UFE0Fg, '') : char);
|
|
9
|
-
}
|
|
10
|
-
function toCodePoint(unicodeSurrogates) {
|
|
11
|
-
var r = [], c = 0, p = 0, i = 0;
|
|
12
|
-
while (i < unicodeSurrogates.length) {
|
|
13
|
-
c = unicodeSurrogates.charCodeAt(i++);
|
|
14
|
-
if (p) {
|
|
15
|
-
r.push((65536 + ((p - 55296) << 10) + (c - 56320)).toString(16));
|
|
16
|
-
p = 0;
|
|
17
|
-
}
|
|
18
|
-
else if (55296 <= c && c <= 56319) {
|
|
19
|
-
p = c;
|
|
20
|
-
}
|
|
21
|
-
else {
|
|
22
|
-
r.push(c.toString(16));
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
return r.join('-');
|
|
26
|
-
}
|
|
27
|
-
const apis = {
|
|
28
|
-
twemoji: (code) => 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/svg/' + code.toLowerCase() + '.svg',
|
|
29
|
-
openmoji: 'https://cdn.jsdelivr.net/npm/@svgmoji/openmoji@2.0.0/svg/',
|
|
30
|
-
blobmoji: 'https://cdn.jsdelivr.net/npm/@svgmoji/blob@2.0.0/svg/',
|
|
31
|
-
noto: 'https://cdn.jsdelivr.net/gh/svgmoji/svgmoji/packages/svgmoji__noto/svg/',
|
|
32
|
-
fluent: (code) => 'https://cdn.jsdelivr.net/gh/shuding/fluentui-emoji-unicode/assets/' +
|
|
33
|
-
code.toLowerCase() +
|
|
34
|
-
'_color.svg',
|
|
35
|
-
fluentFlat: (code) => 'https://cdn.jsdelivr.net/gh/shuding/fluentui-emoji-unicode/assets/' +
|
|
36
|
-
code.toLowerCase() +
|
|
37
|
-
'_flat.svg',
|
|
38
|
-
};
|
|
39
|
-
export function loadEmoji(code, type) {
|
|
40
|
-
// https://github.com/svgmoji/svgmoji
|
|
41
|
-
if (!type || !apis[type]) {
|
|
42
|
-
type = 'twemoji';
|
|
43
|
-
}
|
|
44
|
-
const api = apis[type];
|
|
45
|
-
if (typeof api === 'function') {
|
|
46
|
-
return fetch(api(code));
|
|
47
|
-
}
|
|
48
|
-
return fetch(`${api}${code.toUpperCase()}.svg`);
|
|
49
|
-
}
|
package/dist/language/index.d.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
export declare class FontDetector {
|
|
2
|
-
private rangesByLang;
|
|
3
|
-
detect(text: string, fonts: string[]): Promise<{
|
|
4
|
-
[lang: string]: string;
|
|
5
|
-
}>;
|
|
6
|
-
private detectSegment;
|
|
7
|
-
private load;
|
|
8
|
-
private addDetectors;
|
|
9
|
-
}
|
|
10
|
-
export declare const languageFontMap: {
|
|
11
|
-
'ja-JP': string;
|
|
12
|
-
'ko-KR': string;
|
|
13
|
-
'zh-CN': string;
|
|
14
|
-
'zh-TW': string;
|
|
15
|
-
'zh-HK': string;
|
|
16
|
-
'th-TH': string;
|
|
17
|
-
'bn-IN': string;
|
|
18
|
-
'ar-AR': string;
|
|
19
|
-
'ta-IN': string;
|
|
20
|
-
'ml-IN': string;
|
|
21
|
-
'he-IL': string;
|
|
22
|
-
'te-IN': string;
|
|
23
|
-
devanagari: string;
|
|
24
|
-
kannada: string;
|
|
25
|
-
symbol: string[];
|
|
26
|
-
math: string;
|
|
27
|
-
unknown: string;
|
|
28
|
-
};
|
package/dist/language/index.js
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
export class FontDetector {
|
|
2
|
-
constructor() {
|
|
3
|
-
this.rangesByLang = {};
|
|
4
|
-
}
|
|
5
|
-
async detect(text, fonts) {
|
|
6
|
-
await this.load(fonts);
|
|
7
|
-
const result = {};
|
|
8
|
-
for (const segment of text) {
|
|
9
|
-
const lang = this.detectSegment(segment, fonts);
|
|
10
|
-
if (lang) {
|
|
11
|
-
result[lang] = result[lang] || '';
|
|
12
|
-
result[lang] += segment;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
return result;
|
|
16
|
-
}
|
|
17
|
-
detectSegment(segment, fonts) {
|
|
18
|
-
for (const font of fonts) {
|
|
19
|
-
const range = this.rangesByLang[font];
|
|
20
|
-
if (range && checkSegmentInRange(segment, range)) {
|
|
21
|
-
return font;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
return null;
|
|
25
|
-
}
|
|
26
|
-
async load(fonts) {
|
|
27
|
-
let params = '';
|
|
28
|
-
const existingLang = Object.keys(this.rangesByLang);
|
|
29
|
-
const langNeedsToLoad = fonts.filter((font) => !existingLang.includes(font));
|
|
30
|
-
if (langNeedsToLoad.length === 0) {
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
for (const font of langNeedsToLoad) {
|
|
34
|
-
params += `family=${font}&`;
|
|
35
|
-
}
|
|
36
|
-
params += 'display=swap';
|
|
37
|
-
const API = `https://fonts.googleapis.com/css2?${params}`;
|
|
38
|
-
const fontFace = await (await fetch(API, {
|
|
39
|
-
headers: {
|
|
40
|
-
// Make sure it returns TTF.
|
|
41
|
-
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36',
|
|
42
|
-
},
|
|
43
|
-
})).text();
|
|
44
|
-
this.addDetectors(fontFace);
|
|
45
|
-
}
|
|
46
|
-
addDetectors(input) {
|
|
47
|
-
const regex = /font-family:\s*'(.+?)';.+?unicode-range:\s*(.+?);/gms;
|
|
48
|
-
const matches = input.matchAll(regex);
|
|
49
|
-
for (const [, _lang, range] of matches) {
|
|
50
|
-
const lang = _lang.replaceAll(' ', '+');
|
|
51
|
-
if (!this.rangesByLang[lang]) {
|
|
52
|
-
this.rangesByLang[lang] = [];
|
|
53
|
-
}
|
|
54
|
-
this.rangesByLang[lang].push(...convert(range));
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
function convert(input) {
|
|
59
|
-
return input.split(', ').map((range) => {
|
|
60
|
-
range = range.replaceAll('U+', '');
|
|
61
|
-
const [start, end] = range.split('-').map((hex) => parseInt(hex, 16));
|
|
62
|
-
if (isNaN(end)) {
|
|
63
|
-
return start;
|
|
64
|
-
}
|
|
65
|
-
return [start, end];
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
function checkSegmentInRange(segment, range) {
|
|
69
|
-
const codePoint = segment.codePointAt(0);
|
|
70
|
-
if (!codePoint)
|
|
71
|
-
return false;
|
|
72
|
-
return range.some((val) => {
|
|
73
|
-
if (typeof val === 'number') {
|
|
74
|
-
return codePoint === val;
|
|
75
|
-
}
|
|
76
|
-
else {
|
|
77
|
-
const [start, end] = val;
|
|
78
|
-
return start <= codePoint && codePoint <= end;
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
// @TODO: Support font style and weights, and make this option extensible rather
|
|
83
|
-
// than built-in.
|
|
84
|
-
// @TODO: Cover most languages with Noto Sans.
|
|
85
|
-
export const languageFontMap = {
|
|
86
|
-
'ja-JP': 'Noto+Sans+JP',
|
|
87
|
-
'ko-KR': 'Noto+Sans+KR',
|
|
88
|
-
'zh-CN': 'Noto+Sans+SC',
|
|
89
|
-
'zh-TW': 'Noto+Sans+TC',
|
|
90
|
-
'zh-HK': 'Noto+Sans+HK',
|
|
91
|
-
'th-TH': 'Noto+Sans+Thai',
|
|
92
|
-
'bn-IN': 'Noto+Sans+Bengali',
|
|
93
|
-
'ar-AR': 'Noto+Sans+Arabic',
|
|
94
|
-
'ta-IN': 'Noto+Sans+Tamil',
|
|
95
|
-
'ml-IN': 'Noto+Sans+Malayalam',
|
|
96
|
-
'he-IL': 'Noto+Sans+Hebrew',
|
|
97
|
-
'te-IN': 'Noto+Sans+Telugu',
|
|
98
|
-
devanagari: 'Noto+Sans+Devanagari',
|
|
99
|
-
kannada: 'Noto+Sans+Kannada',
|
|
100
|
-
symbol: ['Noto+Sans+Symbols', 'Noto+Sans+Symbols+2'],
|
|
101
|
-
math: 'Noto+Sans+Math',
|
|
102
|
-
unknown: 'Noto+Sans',
|
|
103
|
-
};
|
package/dist/og.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export default function render(satori: any, resvg: typeof import('@resvg/resvg-wasm'), opts: any, defaultFonts: any, element: any): Promise<Uint8Array>;
|
package/dist/og.js
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import { loadEmoji, getIconCode } from './emoji';
|
|
2
|
-
import { FontDetector, languageFontMap } from './language';
|
|
3
|
-
async function loadGoogleFont(font, text) {
|
|
4
|
-
if (!font || !text)
|
|
5
|
-
return;
|
|
6
|
-
const API = `https://fonts.googleapis.com/css2?family=${font}&text=${encodeURIComponent(text)}`;
|
|
7
|
-
const css = await (await fetch(API, {
|
|
8
|
-
headers: {
|
|
9
|
-
// Make sure it returns TTF.
|
|
10
|
-
'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',
|
|
11
|
-
},
|
|
12
|
-
})).text();
|
|
13
|
-
const resource = css.match(/src: url\((.+)\) format\('(opentype|truetype)'\)/);
|
|
14
|
-
if (!resource)
|
|
15
|
-
throw new Error('Failed to download dynamic font');
|
|
16
|
-
const res = await fetch(resource[1]);
|
|
17
|
-
if (!res.ok) {
|
|
18
|
-
throw new Error('Failed to download dynamic font. Status: ' + res.status);
|
|
19
|
-
}
|
|
20
|
-
return res.arrayBuffer();
|
|
21
|
-
}
|
|
22
|
-
const detector = new FontDetector();
|
|
23
|
-
const assetCache = new Map();
|
|
24
|
-
const loadDynamicAsset = ({ emoji }) => {
|
|
25
|
-
const fn = async (code, text) => {
|
|
26
|
-
if (code === 'emoji') {
|
|
27
|
-
// It's an emoji, load the image.
|
|
28
|
-
return (`data:image/svg+xml;base64,` +
|
|
29
|
-
btoa(await (await loadEmoji(getIconCode(text), emoji)).text()));
|
|
30
|
-
}
|
|
31
|
-
const codes = code.split('|');
|
|
32
|
-
// Try to load from Google Fonts.
|
|
33
|
-
const names = codes
|
|
34
|
-
.map((code) => languageFontMap[code])
|
|
35
|
-
.filter(Boolean)
|
|
36
|
-
.flat();
|
|
37
|
-
if (names.length === 0)
|
|
38
|
-
return [];
|
|
39
|
-
try {
|
|
40
|
-
const textByFont = await detector.detect(text, names);
|
|
41
|
-
const fonts = Object.keys(textByFont);
|
|
42
|
-
const fontData = await Promise.all(fonts.map((font) => loadGoogleFont(font, textByFont[font])));
|
|
43
|
-
return fontData.map((data, index) => ({
|
|
44
|
-
name: `satori_${codes[index]}_fallback_${text}`,
|
|
45
|
-
data,
|
|
46
|
-
weight: 400,
|
|
47
|
-
style: 'normal',
|
|
48
|
-
lang: codes[index] === 'unknown' ? undefined : codes[index],
|
|
49
|
-
}));
|
|
50
|
-
}
|
|
51
|
-
catch (e) {
|
|
52
|
-
console.error('Failed to load dynamic font for', text, '. Error:', e);
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
return async (...args) => {
|
|
56
|
-
const key = JSON.stringify(args);
|
|
57
|
-
const cache = assetCache.get(key);
|
|
58
|
-
if (cache)
|
|
59
|
-
return cache;
|
|
60
|
-
const asset = await fn(...args);
|
|
61
|
-
assetCache.set(key, asset);
|
|
62
|
-
return asset;
|
|
63
|
-
};
|
|
64
|
-
};
|
|
65
|
-
export default async function render(satori, resvg, opts, defaultFonts, element) {
|
|
66
|
-
const options = Object.assign({
|
|
67
|
-
width: 1200,
|
|
68
|
-
height: 630,
|
|
69
|
-
debug: false,
|
|
70
|
-
}, opts);
|
|
71
|
-
const svg = await satori(element, {
|
|
72
|
-
width: options.width,
|
|
73
|
-
height: options.height,
|
|
74
|
-
debug: options.debug,
|
|
75
|
-
fonts: options.fonts || defaultFonts,
|
|
76
|
-
loadAdditionalAsset: loadDynamicAsset({
|
|
77
|
-
emoji: options.emoji,
|
|
78
|
-
}),
|
|
79
|
-
});
|
|
80
|
-
const resvgJS = new resvg.Resvg(svg, {
|
|
81
|
-
fitTo: {
|
|
82
|
-
mode: 'width',
|
|
83
|
-
value: options.width,
|
|
84
|
-
},
|
|
85
|
-
});
|
|
86
|
-
const pngData = resvgJS.render();
|
|
87
|
-
const pngBuffer = pngData.asPng();
|
|
88
|
-
pngData.free();
|
|
89
|
-
resvgJS.free();
|
|
90
|
-
return pngBuffer;
|
|
91
|
-
}
|
package/dist/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|