koishi-plugin-vercel-satori-png-service 0.0.5 → 0.1.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/lib/Satori.d.ts +5 -0
- package/lib/emoji.d.ts +15 -0
- package/lib/index.d.ts +2 -4
- package/lib/index.js +284 -5
- package/lib/language.d.ts +28 -0
- package/lib/og.d.ts +42 -0
- package/package.json +6 -5
- package/readme.md +2 -2
package/lib/Satori.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Readable } from "node:stream";
|
|
2
|
+
import { ReactElement } from "react";
|
|
3
|
+
import { ImageOptions } from "./og";
|
|
4
|
+
export declare const initSatori: () => Promise<void>;
|
|
5
|
+
export declare const createNodejsStream: (element: ReactElement<any, any>, options: ImageOptions) => Promise<Readable>;
|
package/lib/emoji.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
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/lib/index.d.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { Context, Schema, Service } from "koishi";
|
|
2
2
|
import { ReactElement } from "react";
|
|
3
3
|
import { Readable } from "stream";
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
export type ImageOptions = Parameters<typeof unstable_createNodejsStream>[1];
|
|
4
|
+
import { Font, ImageOptions } from "./og";
|
|
5
|
+
export { Font, ImageOptions } from "./og";
|
|
7
6
|
declare module "koishi" {
|
|
8
7
|
interface Context {
|
|
9
8
|
vercelSatoriPngService: VercelSatoriPngService;
|
|
@@ -12,7 +11,6 @@ declare module "koishi" {
|
|
|
12
11
|
declare class VercelSatoriPngService extends Service {
|
|
13
12
|
private _ctx;
|
|
14
13
|
private _config;
|
|
15
|
-
private createNodejsStream;
|
|
16
14
|
private fonts;
|
|
17
15
|
constructor(ctx: Context, config: VercelSatoriPngService.Config);
|
|
18
16
|
start(): Promise<void>;
|
package/lib/index.js
CHANGED
|
@@ -37,6 +37,285 @@ var import_koishi = require("koishi");
|
|
|
37
37
|
var import_react = __toESM(require("react"));
|
|
38
38
|
var import_sucrase = require("sucrase");
|
|
39
39
|
var import_html_react_parser = __toESM(require("html-react-parser"));
|
|
40
|
+
|
|
41
|
+
// src/Satori.ts
|
|
42
|
+
var import_node_path = __toESM(require("node:path"));
|
|
43
|
+
var import_promises = __toESM(require("node:fs/promises"));
|
|
44
|
+
var import_node_module = require("node:module");
|
|
45
|
+
var import_node_stream = require("node:stream");
|
|
46
|
+
var resvg = __toESM(require("@resvg/resvg-wasm"));
|
|
47
|
+
|
|
48
|
+
// src/emoji.ts
|
|
49
|
+
var U200D = String.fromCharCode(8205);
|
|
50
|
+
var UFE0Fg = /\uFE0F/g;
|
|
51
|
+
function getIconCode(char) {
|
|
52
|
+
return toCodePoint(char.indexOf(U200D) < 0 ? char.replace(UFE0Fg, "") : char);
|
|
53
|
+
}
|
|
54
|
+
__name(getIconCode, "getIconCode");
|
|
55
|
+
function toCodePoint(unicodeSurrogates) {
|
|
56
|
+
var r = [], c = 0, p = 0, i = 0;
|
|
57
|
+
while (i < unicodeSurrogates.length) {
|
|
58
|
+
c = unicodeSurrogates.charCodeAt(i++);
|
|
59
|
+
if (p) {
|
|
60
|
+
r.push((65536 + (p - 55296 << 10) + (c - 56320)).toString(16));
|
|
61
|
+
p = 0;
|
|
62
|
+
} else if (55296 <= c && c <= 56319) {
|
|
63
|
+
p = c;
|
|
64
|
+
} else {
|
|
65
|
+
r.push(c.toString(16));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return r.join("-");
|
|
69
|
+
}
|
|
70
|
+
__name(toCodePoint, "toCodePoint");
|
|
71
|
+
var apis = {
|
|
72
|
+
twemoji: /* @__PURE__ */ __name((code) => "https://gcore.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/svg/" + code.toLowerCase() + ".svg", "twemoji"),
|
|
73
|
+
openmoji: "https://gcore.jsdelivr.net/npm/@svgmoji/openmoji@2.0.0/svg/",
|
|
74
|
+
blobmoji: "https://gcore.jsdelivr.net/npm/@svgmoji/blob@2.0.0/svg/",
|
|
75
|
+
noto: "https://gcore.jsdelivr.net/gh/svgmoji/svgmoji/packages/svgmoji__noto/svg/",
|
|
76
|
+
fluent: /* @__PURE__ */ __name((code) => "https://gcore.jsdelivr.net/gh/shuding/fluentui-emoji-unicode/assets/" + code.toLowerCase() + "_color.svg", "fluent"),
|
|
77
|
+
fluentFlat: /* @__PURE__ */ __name((code) => "https://gcore.jsdelivr.net/gh/shuding/fluentui-emoji-unicode/assets/" + code.toLowerCase() + "_flat.svg", "fluentFlat")
|
|
78
|
+
};
|
|
79
|
+
function loadEmoji(code, type) {
|
|
80
|
+
if (!type || !apis[type]) {
|
|
81
|
+
type = "twemoji";
|
|
82
|
+
}
|
|
83
|
+
const api = apis[type];
|
|
84
|
+
if (typeof api === "function") {
|
|
85
|
+
return fetch(api(code));
|
|
86
|
+
}
|
|
87
|
+
return fetch(`${api}${code.toUpperCase()}.svg`);
|
|
88
|
+
}
|
|
89
|
+
__name(loadEmoji, "loadEmoji");
|
|
90
|
+
|
|
91
|
+
// src/language.ts
|
|
92
|
+
var FontDetector = class {
|
|
93
|
+
static {
|
|
94
|
+
__name(this, "FontDetector");
|
|
95
|
+
}
|
|
96
|
+
rangesByLang = {};
|
|
97
|
+
async detect(text, fonts) {
|
|
98
|
+
await this.load(fonts);
|
|
99
|
+
const result = {};
|
|
100
|
+
for (const segment of text) {
|
|
101
|
+
const lang = this.detectSegment(segment, fonts);
|
|
102
|
+
if (lang) {
|
|
103
|
+
result[lang] = result[lang] || "";
|
|
104
|
+
result[lang] += segment;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
detectSegment(segment, fonts) {
|
|
110
|
+
for (const font of fonts) {
|
|
111
|
+
const range = this.rangesByLang[font];
|
|
112
|
+
if (range && checkSegmentInRange(segment, range)) {
|
|
113
|
+
return font;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
async load(fonts) {
|
|
119
|
+
let params = "";
|
|
120
|
+
const existingLang = Object.keys(this.rangesByLang);
|
|
121
|
+
const langNeedsToLoad = fonts.filter((font) => !existingLang.includes(font));
|
|
122
|
+
if (langNeedsToLoad.length === 0) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
for (const font of langNeedsToLoad) {
|
|
126
|
+
params += `family=${font}&`;
|
|
127
|
+
}
|
|
128
|
+
params += "display=swap";
|
|
129
|
+
const API = `https://fonts.googleapis.com/css2?${params}`;
|
|
130
|
+
const fontFace = await (await fetch(API, {
|
|
131
|
+
headers: {
|
|
132
|
+
// Make sure it returns TTF.
|
|
133
|
+
"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"
|
|
134
|
+
}
|
|
135
|
+
})).text();
|
|
136
|
+
this.addDetectors(fontFace);
|
|
137
|
+
}
|
|
138
|
+
addDetectors(input) {
|
|
139
|
+
const regex = /font-family:\s*'(.+?)';.+?unicode-range:\s*(.+?);/gms;
|
|
140
|
+
const matches = input.matchAll(regex);
|
|
141
|
+
for (const [, _lang, range] of matches) {
|
|
142
|
+
const lang = _lang.replaceAll(" ", "+");
|
|
143
|
+
if (!this.rangesByLang[lang]) {
|
|
144
|
+
this.rangesByLang[lang] = [];
|
|
145
|
+
}
|
|
146
|
+
this.rangesByLang[lang].push(...convert(range));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
function convert(input) {
|
|
151
|
+
return input.split(", ").map((range) => {
|
|
152
|
+
range = range.replaceAll("U+", "");
|
|
153
|
+
const [start, end] = range.split("-").map((hex) => parseInt(hex, 16));
|
|
154
|
+
if (isNaN(end)) {
|
|
155
|
+
return start;
|
|
156
|
+
}
|
|
157
|
+
return [start, end];
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
__name(convert, "convert");
|
|
161
|
+
function checkSegmentInRange(segment, range) {
|
|
162
|
+
const codePoint = segment.codePointAt(0);
|
|
163
|
+
if (!codePoint) return false;
|
|
164
|
+
return range.some((val) => {
|
|
165
|
+
if (typeof val === "number") {
|
|
166
|
+
return codePoint === val;
|
|
167
|
+
} else {
|
|
168
|
+
const [start, end] = val;
|
|
169
|
+
return start <= codePoint && codePoint <= end;
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
__name(checkSegmentInRange, "checkSegmentInRange");
|
|
174
|
+
var languageFontMap = {
|
|
175
|
+
"ja-JP": "Noto+Sans+JP",
|
|
176
|
+
"ko-KR": "Noto+Sans+KR",
|
|
177
|
+
"zh-CN": "Noto+Sans+SC",
|
|
178
|
+
"zh-TW": "Noto+Sans+TC",
|
|
179
|
+
"zh-HK": "Noto+Sans+HK",
|
|
180
|
+
"th-TH": "Noto+Sans+Thai",
|
|
181
|
+
"bn-IN": "Noto+Sans+Bengali",
|
|
182
|
+
"ar-AR": "Noto+Sans+Arabic",
|
|
183
|
+
"ta-IN": "Noto+Sans+Tamil",
|
|
184
|
+
"ml-IN": "Noto+Sans+Malayalam",
|
|
185
|
+
"he-IL": "Noto+Sans+Hebrew",
|
|
186
|
+
"te-IN": "Noto+Sans+Telugu",
|
|
187
|
+
devanagari: "Noto+Sans+Devanagari",
|
|
188
|
+
kannada: "Noto+Sans+Kannada",
|
|
189
|
+
symbol: ["Noto+Sans+Symbols", "Noto+Sans+Symbols+2"],
|
|
190
|
+
math: "Noto+Sans+Math",
|
|
191
|
+
unknown: "Noto+Sans"
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// src/og.ts
|
|
195
|
+
async function loadGoogleFont(font, text) {
|
|
196
|
+
if (!font || !text) return;
|
|
197
|
+
const API = `https://fonts.googleapis.com/css2?family=${font}&text=${encodeURIComponent(
|
|
198
|
+
text
|
|
199
|
+
)}`;
|
|
200
|
+
const css = await (await fetch(API, {
|
|
201
|
+
headers: {
|
|
202
|
+
// Make sure it returns TTF.
|
|
203
|
+
"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"
|
|
204
|
+
}
|
|
205
|
+
})).text();
|
|
206
|
+
const resource = css.match(
|
|
207
|
+
/src: url\((.+)\) format\('(opentype|truetype)'\)/
|
|
208
|
+
);
|
|
209
|
+
if (!resource) throw new Error("Failed to download dynamic font");
|
|
210
|
+
const res = await fetch(resource[1]);
|
|
211
|
+
if (!res.ok) {
|
|
212
|
+
throw new Error("Failed to download dynamic font. Status: " + res.status);
|
|
213
|
+
}
|
|
214
|
+
return res.arrayBuffer();
|
|
215
|
+
}
|
|
216
|
+
__name(loadGoogleFont, "loadGoogleFont");
|
|
217
|
+
var detector = new FontDetector();
|
|
218
|
+
var assetCache = /* @__PURE__ */ new Map();
|
|
219
|
+
var loadDynamicAsset = /* @__PURE__ */ __name(({ emoji }) => {
|
|
220
|
+
const fn = /* @__PURE__ */ __name(async (code, text) => {
|
|
221
|
+
if (code === "emoji") {
|
|
222
|
+
return `data:image/svg+xml;base64,` + btoa(await (await loadEmoji(getIconCode(text), emoji)).text());
|
|
223
|
+
}
|
|
224
|
+
const codes = code.split("|");
|
|
225
|
+
const names = codes.map((code2) => languageFontMap[code2]).filter(Boolean).flat();
|
|
226
|
+
if (names.length === 0) return [];
|
|
227
|
+
try {
|
|
228
|
+
const textByFont = await detector.detect(text, names);
|
|
229
|
+
const fonts = Object.keys(textByFont);
|
|
230
|
+
const fontData2 = await Promise.all(
|
|
231
|
+
fonts.map((font) => loadGoogleFont(font, textByFont[font]))
|
|
232
|
+
);
|
|
233
|
+
return fontData2.map((data, index) => ({
|
|
234
|
+
name: `satori_${codes[index]}_fallback_${text}`,
|
|
235
|
+
data,
|
|
236
|
+
weight: 400,
|
|
237
|
+
style: "normal",
|
|
238
|
+
lang: codes[index] === "unknown" ? void 0 : codes[index]
|
|
239
|
+
}));
|
|
240
|
+
} catch (e) {
|
|
241
|
+
console.error("Failed to load dynamic font for", text, ". Error:", e);
|
|
242
|
+
}
|
|
243
|
+
}, "fn");
|
|
244
|
+
return async (...args) => {
|
|
245
|
+
const key = JSON.stringify(args);
|
|
246
|
+
const cache = assetCache.get(key);
|
|
247
|
+
if (cache) return cache;
|
|
248
|
+
const asset = await fn(...args);
|
|
249
|
+
assetCache.set(key, asset);
|
|
250
|
+
return asset;
|
|
251
|
+
};
|
|
252
|
+
}, "loadDynamicAsset");
|
|
253
|
+
async function render(satori2, resvg2, opts, defaultFonts, element) {
|
|
254
|
+
const options = Object.assign(
|
|
255
|
+
{
|
|
256
|
+
width: 1200,
|
|
257
|
+
height: 630,
|
|
258
|
+
debug: false
|
|
259
|
+
},
|
|
260
|
+
opts
|
|
261
|
+
);
|
|
262
|
+
const svg = await satori2(element, {
|
|
263
|
+
width: options.width,
|
|
264
|
+
height: options.height,
|
|
265
|
+
debug: options.debug,
|
|
266
|
+
fonts: options.fonts || defaultFonts,
|
|
267
|
+
loadAdditionalAsset: loadDynamicAsset({
|
|
268
|
+
emoji: options.emoji
|
|
269
|
+
})
|
|
270
|
+
});
|
|
271
|
+
const resvgJS = new resvg2.Resvg(svg, {
|
|
272
|
+
fitTo: {
|
|
273
|
+
mode: "width",
|
|
274
|
+
value: options.width
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
const pngData = resvgJS.render();
|
|
278
|
+
const pngBuffer = pngData.asPng();
|
|
279
|
+
pngData.free();
|
|
280
|
+
resvgJS.free();
|
|
281
|
+
return pngBuffer;
|
|
282
|
+
}
|
|
283
|
+
__name(render, "render");
|
|
284
|
+
|
|
285
|
+
// src/Satori.ts
|
|
286
|
+
var initialized = false;
|
|
287
|
+
var fontData;
|
|
288
|
+
var satori;
|
|
289
|
+
var initSatori = /* @__PURE__ */ __name(async () => {
|
|
290
|
+
if (initialized) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
satori = (await import("satori")).default;
|
|
294
|
+
const require2 = (0, import_node_module.createRequire)("file:///" + __filename);
|
|
295
|
+
const reSvgWasm = import_node_path.default.join(
|
|
296
|
+
import_node_path.default.dirname(require2.resolve("@resvg/resvg-wasm")),
|
|
297
|
+
"index_bg.wasm"
|
|
298
|
+
);
|
|
299
|
+
await resvg.initWasm(await import_promises.default.readFile(reSvgWasm));
|
|
300
|
+
fontData = await import_promises.default.readFile(
|
|
301
|
+
require2.resolve("./noto-sans-v27-latin-regular.ttf")
|
|
302
|
+
);
|
|
303
|
+
initialized = true;
|
|
304
|
+
}, "initSatori");
|
|
305
|
+
var createNodejsStream = /* @__PURE__ */ __name(async (element, options) => {
|
|
306
|
+
const fonts = [
|
|
307
|
+
{
|
|
308
|
+
name: "sans serif",
|
|
309
|
+
data: fontData,
|
|
310
|
+
weight: 700,
|
|
311
|
+
style: "normal"
|
|
312
|
+
}
|
|
313
|
+
];
|
|
314
|
+
const result = await render(satori, resvg, options, fonts, element);
|
|
315
|
+
return import_node_stream.Readable.from(Buffer.from(result));
|
|
316
|
+
}, "createNodejsStream");
|
|
317
|
+
|
|
318
|
+
// src/index.ts
|
|
40
319
|
var serviceName = "vercelSatoriPngService";
|
|
41
320
|
var AsyncFunction = (async () => 0).constructor;
|
|
42
321
|
var VercelSatoriPngService = class extends import_koishi.Service {
|
|
@@ -45,7 +324,6 @@ var VercelSatoriPngService = class extends import_koishi.Service {
|
|
|
45
324
|
}
|
|
46
325
|
_ctx;
|
|
47
326
|
_config;
|
|
48
|
-
createNodejsStream;
|
|
49
327
|
fonts = [];
|
|
50
328
|
constructor(ctx, config) {
|
|
51
329
|
super(ctx, serviceName);
|
|
@@ -53,7 +331,7 @@ var VercelSatoriPngService = class extends import_koishi.Service {
|
|
|
53
331
|
this._config = config;
|
|
54
332
|
}
|
|
55
333
|
async start() {
|
|
56
|
-
|
|
334
|
+
await initSatori();
|
|
57
335
|
}
|
|
58
336
|
async jsxToReactElement(jsxCode, data) {
|
|
59
337
|
const hCode = (0, import_sucrase.transform)(jsxCode, {
|
|
@@ -107,14 +385,14 @@ var VercelSatoriPngService = class extends import_koishi.Service {
|
|
|
107
385
|
}
|
|
108
386
|
async jsxToPng(jsxCode, options, data) {
|
|
109
387
|
const reactElement = await this.jsxToReactElement(jsxCode, data);
|
|
110
|
-
return
|
|
388
|
+
return createNodejsStream(reactElement, this.buildOptions(options));
|
|
111
389
|
}
|
|
112
390
|
htmlToPng(htmlCode, options) {
|
|
113
391
|
const reactElement = this.htmlToReactElement(htmlCode);
|
|
114
|
-
return
|
|
392
|
+
return createNodejsStream(reactElement, this.buildOptions(options));
|
|
115
393
|
}
|
|
116
394
|
async reactElementToPng(reactElement, options) {
|
|
117
|
-
return
|
|
395
|
+
return createNodejsStream(reactElement, this.buildOptions(options));
|
|
118
396
|
}
|
|
119
397
|
};
|
|
120
398
|
((VercelSatoriPngService2) => {
|
|
@@ -122,3 +400,4 @@ var VercelSatoriPngService = class extends import_koishi.Service {
|
|
|
122
400
|
VercelSatoriPngService2.Config = import_koishi.Schema.object({});
|
|
123
401
|
})(VercelSatoriPngService || (VercelSatoriPngService = {}));
|
|
124
402
|
var src_default = VercelSatoriPngService;
|
|
403
|
+
/*! Copyright Twitter Inc. and other contributors. Licensed under MIT */
|
|
@@ -0,0 +1,28 @@
|
|
|
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/lib/og.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { EmojiType } from "./emoji";
|
|
2
|
+
import type { SatoriOptions } from "satori";
|
|
3
|
+
import type { ReactElement } from "react";
|
|
4
|
+
export type ImageOptions = {
|
|
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
|
+
* @type {EmojiType}
|
|
37
|
+
* @default 'twemoji'
|
|
38
|
+
*/
|
|
39
|
+
emoji?: EmojiType;
|
|
40
|
+
};
|
|
41
|
+
export type Font = ImageOptions["fonts"][number];
|
|
42
|
+
export default function render(satori: any, resvg: any, opts: ImageOptions, defaultFonts: Font[], element: ReactElement): Promise<any>;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-vercel-satori-png-service",
|
|
3
3
|
"description": "Use Vercel Satori and Resvg.js to convert html to png",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.1.1",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
@@ -35,12 +35,13 @@
|
|
|
35
35
|
"jsx to png"
|
|
36
36
|
],
|
|
37
37
|
"peerDependencies": {
|
|
38
|
-
"koishi": "^4.18.
|
|
38
|
+
"koishi": "^4.18.8"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@
|
|
42
|
-
"html-react-parser": "^5.2.
|
|
43
|
-
"react": "^19.
|
|
41
|
+
"@resvg/resvg-wasm": "^2.6.2",
|
|
42
|
+
"html-react-parser": "^5.2.5",
|
|
43
|
+
"react": "^19.2.0-canary-f9ae0a4c-20250527",
|
|
44
|
+
"satori": "^0.16.2",
|
|
44
45
|
"sucrase": "^3.35.0"
|
|
45
46
|
},
|
|
46
47
|
"koishi": {
|
package/readme.md
CHANGED
|
@@ -10,8 +10,8 @@ html to ReactElement [html-react-parser](https://www.npmjs.com/package/html-reac
|
|
|
10
10
|
|
|
11
11
|
jsx to ReactElement [sucrase](https://www.npmjs.com/package/sucrase)
|
|
12
12
|
|
|
13
|
-
ReactElement to
|
|
13
|
+
ReactElement to svg [vercel/satori](https://github.com/vercel/satori#overview)
|
|
14
14
|
|
|
15
15
|
[og-playground](https://og-playground.vercel.app/)
|
|
16
16
|
|
|
17
|
-
[
|
|
17
|
+
svg to png [@resvg/resvg-wasm](https://www.npmjs.com/package/@resvg/resvg-wasm)
|