takumi-js 1.8.7 → 2.0.0-beta.0
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/index.cjs +3 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +2 -2
- package/dist/{render-BKqYuN_7.mjs → render-1w0Pei0G.mjs} +113 -30
- package/dist/{render-BkAfi_5a.cjs → render-BWrJI7_9.cjs} +124 -29
- package/dist/render-DJjKHz2l.d.cts +117 -0
- package/dist/render-DJjKHz2l.d.mts +117 -0
- package/dist/response.cjs +33 -73
- package/dist/response.d.cts +2 -24
- package/dist/response.d.mts +2 -24
- package/dist/response.mjs +34 -73
- package/package.json +4 -4
- package/dist/render-lU4_qVw3.d.cts +0 -63
- package/dist/render-lU4_qVw3.d.mts +0 -63
package/dist/index.cjs
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_render = require("./render-
|
|
2
|
+
const require_render = require("./render-BWrJI7_9.cjs");
|
|
3
3
|
exports.render = require_render.render;
|
|
4
|
+
exports.renderAnimation = require_render.renderAnimation;
|
|
5
|
+
exports.renderSvg = require_render.renderSvg;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as RenderSvgOptions, c as renderSvg, i as RenderOptions, n as RenderAnimationScene, o as render, r as RenderInput, s as renderAnimation, t as RenderAnimationOptions } from "./render-DJjKHz2l.cjs";
|
|
2
2
|
import { ContainerNode, FetchResourcesOptions, ImageNode, Node, NodeAttributes, NodeMetadata, ReactElementLike, TextNode } from "@takumi-rs/helpers";
|
|
3
|
-
import { AnimationFrameSource, AnimationOutputFormat, AnimationSceneSource,
|
|
3
|
+
import { AnimationFrameSource, AnimationOutputFormat, AnimationSceneSource, DitheringAlgorithm, EncodeFramesOptions, Font, FontDetails, FontLoader, ImageSource, Keyframes, KeyframesMap, KeyframesRuleList, MeasuredNode, MeasuredTextRun, OutputFormat } from "@takumi-rs/core";
|
|
4
4
|
|
|
5
5
|
//#region src/index.d.ts
|
|
6
6
|
declare module "react" {
|
|
@@ -9,4 +9,4 @@ declare module "react" {
|
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
11
|
//#endregion
|
|
12
|
-
export { type AnimationFrameSource, type AnimationOutputFormat, type AnimationSceneSource, type
|
|
12
|
+
export { type AnimationFrameSource, type AnimationOutputFormat, type AnimationSceneSource, type ContainerNode, type DitheringAlgorithm, type EncodeFramesOptions, type FetchResourcesOptions, type Font, type FontDetails, type FontLoader, type ImageNode, type ImageSource, type Keyframes, type KeyframesMap, type KeyframesRuleList, type MeasuredNode, type MeasuredTextRun, type Node, type NodeAttributes, type NodeMetadata, type OutputFormat, type ReactElementLike, type RenderAnimationOptions, type RenderAnimationScene, type RenderInput, type RenderOptions, type RenderSvgOptions, type TextNode, render, renderAnimation, renderSvg };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as RenderSvgOptions, c as renderSvg, i as RenderOptions, n as RenderAnimationScene, o as render, r as RenderInput, s as renderAnimation, t as RenderAnimationOptions } from "./render-DJjKHz2l.mjs";
|
|
2
2
|
import { ContainerNode, FetchResourcesOptions, ImageNode, Node, NodeAttributes, NodeMetadata, ReactElementLike, TextNode } from "@takumi-rs/helpers";
|
|
3
|
-
import { AnimationFrameSource, AnimationOutputFormat, AnimationSceneSource,
|
|
3
|
+
import { AnimationFrameSource, AnimationOutputFormat, AnimationSceneSource, DitheringAlgorithm, EncodeFramesOptions, Font, FontDetails, FontLoader, ImageSource, Keyframes, KeyframesMap, KeyframesRuleList, MeasuredNode, MeasuredTextRun, OutputFormat } from "@takumi-rs/core";
|
|
4
4
|
|
|
5
5
|
//#region src/index.d.ts
|
|
6
6
|
declare module "react" {
|
|
@@ -9,4 +9,4 @@ declare module "react" {
|
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
11
|
//#endregion
|
|
12
|
-
export { type AnimationFrameSource, type AnimationOutputFormat, type AnimationSceneSource, type
|
|
12
|
+
export { type AnimationFrameSource, type AnimationOutputFormat, type AnimationSceneSource, type ContainerNode, type DitheringAlgorithm, type EncodeFramesOptions, type FetchResourcesOptions, type Font, type FontDetails, type FontLoader, type ImageNode, type ImageSource, type Keyframes, type KeyframesMap, type KeyframesRuleList, type MeasuredNode, type MeasuredTextRun, type Node, type NodeAttributes, type NodeMetadata, type OutputFormat, type ReactElementLike, type RenderAnimationOptions, type RenderAnimationScene, type RenderInput, type RenderOptions, type RenderSvgOptions, type TextNode, render, renderAnimation, renderSvg };
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as render } from "./render-
|
|
2
|
-
export { render };
|
|
1
|
+
import { n as renderAnimation, r as renderSvg, t as render } from "./render-1w0Pei0G.mjs";
|
|
2
|
+
export { render, renderAnimation, renderSvg };
|
|
@@ -2,22 +2,6 @@ import { extractEmojis } from "@takumi-rs/helpers/emoji";
|
|
|
2
2
|
import { extractResourceUrls, fetchResources } from "@takumi-rs/helpers";
|
|
3
3
|
import { fromJsx } from "@takumi-rs/helpers/jsx";
|
|
4
4
|
import { fromHtml } from "@takumi-rs/helpers/html";
|
|
5
|
-
//#region src/renderer.ts
|
|
6
|
-
/**
|
|
7
|
-
* Mirrors the renderer constructor behavior: when custom fonts are provided,
|
|
8
|
-
* default fonts are disabled so they can't shadow user fonts through generic
|
|
9
|
-
* family resolution (e.g. `sans-serif` resolving to the embedded font).
|
|
10
|
-
*/
|
|
11
|
-
function shouldLoadDefaultFonts(options) {
|
|
12
|
-
return options?.loadDefaultFonts ?? (options?.fonts?.length ? false : void 0);
|
|
13
|
-
}
|
|
14
|
-
async function loadRendererResources(renderer, options) {
|
|
15
|
-
const tasks = [];
|
|
16
|
-
if (options?.fonts && options.fonts.length > 0) tasks.push(renderer.loadFonts(options.fonts));
|
|
17
|
-
if (options?.persistentImages && options.persistentImages.length > 0) tasks.push(...options.persistentImages.map((image) => Promise.resolve(renderer.putPersistentImage(image, options.signal))));
|
|
18
|
-
if (tasks.length > 0) await Promise.all(tasks);
|
|
19
|
-
}
|
|
20
|
-
//#endregion
|
|
21
5
|
//#region src/import.ts
|
|
22
6
|
let importPromise = null;
|
|
23
7
|
function getImports(module) {
|
|
@@ -106,6 +90,31 @@ async function transformElement(element, options) {
|
|
|
106
90
|
if (typeof element === "string") return fromHtml(element);
|
|
107
91
|
return fromJsx(element, options?.jsx);
|
|
108
92
|
}
|
|
93
|
+
/** Resolves the renderer to use: a caller-supplied one, or the shared global. */
|
|
94
|
+
async function resolveRenderer(options) {
|
|
95
|
+
if (options && "renderer" in options && options.renderer) return options.renderer;
|
|
96
|
+
const imports = await getImports(options?.module);
|
|
97
|
+
return globalRenderer ??= new imports.Renderer();
|
|
98
|
+
}
|
|
99
|
+
/** Transforms an input into a node tree and extracts its emojis. */
|
|
100
|
+
async function resolveContent(element, options) {
|
|
101
|
+
const { node: originalNode, stylesheets } = await transformElement(element, options);
|
|
102
|
+
const emojiType = options?.emoji ?? "twemoji";
|
|
103
|
+
return {
|
|
104
|
+
node: emojiType !== "from-font" ? extractEmojis(originalNode, emojiType) : originalNode,
|
|
105
|
+
stylesheets
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/** Merges caller-supplied images with the resources fetched from the nodes. */
|
|
109
|
+
async function collectImages(nodes, options) {
|
|
110
|
+
const providedImages = /* @__PURE__ */ new Map();
|
|
111
|
+
for (const image of options?.images ?? []) providedImages.set(image.src, image);
|
|
112
|
+
const fetched = await fetchResources([...new Set(nodes.flatMap((node) => extractResourceUrls(node)))].filter((url) => !providedImages.has(url)), options?.resourcesOptions);
|
|
113
|
+
return [...providedImages.values(), ...fetched];
|
|
114
|
+
}
|
|
115
|
+
function mergeStylesheets(options, extra) {
|
|
116
|
+
return [...options?.stylesheets ?? [], ...extra];
|
|
117
|
+
}
|
|
109
118
|
/**
|
|
110
119
|
* Renders a React element, HTML string, or Takumi node tree into an image.
|
|
111
120
|
*
|
|
@@ -128,21 +137,95 @@ async function transformElement(element, options) {
|
|
|
128
137
|
*/
|
|
129
138
|
async function render(element, options) {
|
|
130
139
|
options?.signal?.throwIfAborted();
|
|
131
|
-
const
|
|
132
|
-
const
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const emojiType = options?.emoji ?? "twemoji";
|
|
137
|
-
const node = emojiType !== "from-font" ? extractEmojis(originalNode, emojiType) : originalNode;
|
|
138
|
-
const fetchedResources = options?.fetchedResources ?? await fetchResources(extractResourceUrls(node), options?.resourcesOptions);
|
|
139
|
-
const renderOptions = {
|
|
140
|
+
const renderer = await resolveRenderer(options);
|
|
141
|
+
const { node, stylesheets } = await resolveContent(element, options);
|
|
142
|
+
const images = await collectImages([node], options);
|
|
143
|
+
options?.signal?.throwIfAborted();
|
|
144
|
+
return renderer.render(node, {
|
|
140
145
|
...options,
|
|
141
|
-
|
|
142
|
-
stylesheets:
|
|
143
|
-
};
|
|
146
|
+
images,
|
|
147
|
+
stylesheets: mergeStylesheets(options, stylesheets)
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Renders a React element, HTML string, or Takumi node tree into a vector SVG
|
|
152
|
+
* document string.
|
|
153
|
+
*
|
|
154
|
+
* Same input handling and resource pipeline as {@link render}, but emits real SVG
|
|
155
|
+
* (`<rect>`, `<path>`, gradients, glyph outlines, embedded images) instead of a
|
|
156
|
+
* raster bitmap.
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```tsx
|
|
160
|
+
* import { renderSvg } from "takumi-js";
|
|
161
|
+
*
|
|
162
|
+
* const svg = await renderSvg(
|
|
163
|
+
* <div tw="bg-blue-500 text-white p-4">Hello World</div>,
|
|
164
|
+
* { width: 1200, height: 630 }
|
|
165
|
+
* );
|
|
166
|
+
* ```
|
|
167
|
+
*
|
|
168
|
+
* @returns A promise that resolves to the SVG document string.
|
|
169
|
+
*/
|
|
170
|
+
async function renderSvg(element, options) {
|
|
171
|
+
options?.signal?.throwIfAborted();
|
|
172
|
+
const renderer = await resolveRenderer(options);
|
|
173
|
+
const { node, stylesheets } = await resolveContent(element, options);
|
|
174
|
+
const images = await collectImages([node], options);
|
|
144
175
|
options?.signal?.throwIfAborted();
|
|
145
|
-
return renderer.
|
|
176
|
+
return renderer.renderSvg(node, {
|
|
177
|
+
...options,
|
|
178
|
+
images,
|
|
179
|
+
stylesheets: mergeStylesheets(options, stylesheets)
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Renders a sequence of scenes into an animated image (WebP / APNG / GIF).
|
|
184
|
+
*
|
|
185
|
+
* Each scene's content goes through the same input handling and resource pipeline
|
|
186
|
+
* as {@link render}; resources are fetched once across all scenes.
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* ```tsx
|
|
190
|
+
* import { renderAnimation } from "takumi-js";
|
|
191
|
+
*
|
|
192
|
+
* const webp = await renderAnimation({
|
|
193
|
+
* width: 600,
|
|
194
|
+
* height: 400,
|
|
195
|
+
* fps: 30,
|
|
196
|
+
* format: "webp",
|
|
197
|
+
* scenes: [
|
|
198
|
+
* { node: <div tw="bg-red-500 w-full h-full" />, durationMs: 500 },
|
|
199
|
+
* { node: <div tw="bg-blue-500 w-full h-full" />, durationMs: 500 },
|
|
200
|
+
* ],
|
|
201
|
+
* });
|
|
202
|
+
* ```
|
|
203
|
+
*
|
|
204
|
+
* @returns A promise that resolves to the encoded animation (Buffer/Uint8Array).
|
|
205
|
+
*/
|
|
206
|
+
async function renderAnimation(options) {
|
|
207
|
+
options.signal?.throwIfAborted();
|
|
208
|
+
const renderer = await resolveRenderer(options);
|
|
209
|
+
const scenes = await Promise.all(options.scenes.map(async (scene) => {
|
|
210
|
+
const { node, stylesheets } = await resolveContent(scene.node, options);
|
|
211
|
+
return {
|
|
212
|
+
node,
|
|
213
|
+
durationMs: scene.durationMs,
|
|
214
|
+
stylesheets
|
|
215
|
+
};
|
|
216
|
+
}));
|
|
217
|
+
const images = await collectImages(scenes.map((scene) => scene.node), options);
|
|
218
|
+
const stylesheets = mergeStylesheets(options, scenes.flatMap((scene) => scene.stylesheets));
|
|
219
|
+
options.signal?.throwIfAborted();
|
|
220
|
+
return renderer.renderAnimation({
|
|
221
|
+
...options,
|
|
222
|
+
scenes: scenes.map(({ node, durationMs }) => ({
|
|
223
|
+
node,
|
|
224
|
+
durationMs
|
|
225
|
+
})),
|
|
226
|
+
images,
|
|
227
|
+
stylesheets
|
|
228
|
+
});
|
|
146
229
|
}
|
|
147
230
|
//#endregion
|
|
148
|
-
export { render as t };
|
|
231
|
+
export { renderAnimation as n, renderSvg as r, render as t };
|
|
@@ -2,22 +2,6 @@ let _takumi_rs_helpers_emoji = require("@takumi-rs/helpers/emoji");
|
|
|
2
2
|
let _takumi_rs_helpers = require("@takumi-rs/helpers");
|
|
3
3
|
let _takumi_rs_helpers_jsx = require("@takumi-rs/helpers/jsx");
|
|
4
4
|
let _takumi_rs_helpers_html = require("@takumi-rs/helpers/html");
|
|
5
|
-
//#region src/renderer.ts
|
|
6
|
-
/**
|
|
7
|
-
* Mirrors the renderer constructor behavior: when custom fonts are provided,
|
|
8
|
-
* default fonts are disabled so they can't shadow user fonts through generic
|
|
9
|
-
* family resolution (e.g. `sans-serif` resolving to the embedded font).
|
|
10
|
-
*/
|
|
11
|
-
function shouldLoadDefaultFonts(options) {
|
|
12
|
-
return options?.loadDefaultFonts ?? (options?.fonts?.length ? false : void 0);
|
|
13
|
-
}
|
|
14
|
-
async function loadRendererResources(renderer, options) {
|
|
15
|
-
const tasks = [];
|
|
16
|
-
if (options?.fonts && options.fonts.length > 0) tasks.push(renderer.loadFonts(options.fonts));
|
|
17
|
-
if (options?.persistentImages && options.persistentImages.length > 0) tasks.push(...options.persistentImages.map((image) => Promise.resolve(renderer.putPersistentImage(image, options.signal))));
|
|
18
|
-
if (tasks.length > 0) await Promise.all(tasks);
|
|
19
|
-
}
|
|
20
|
-
//#endregion
|
|
21
5
|
//#region src/import.ts
|
|
22
6
|
let importPromise = null;
|
|
23
7
|
function getImports(module) {
|
|
@@ -106,6 +90,31 @@ async function transformElement(element, options) {
|
|
|
106
90
|
if (typeof element === "string") return (0, _takumi_rs_helpers_html.fromHtml)(element);
|
|
107
91
|
return (0, _takumi_rs_helpers_jsx.fromJsx)(element, options?.jsx);
|
|
108
92
|
}
|
|
93
|
+
/** Resolves the renderer to use: a caller-supplied one, or the shared global. */
|
|
94
|
+
async function resolveRenderer(options) {
|
|
95
|
+
if (options && "renderer" in options && options.renderer) return options.renderer;
|
|
96
|
+
const imports = await getImports(options?.module);
|
|
97
|
+
return globalRenderer ??= new imports.Renderer();
|
|
98
|
+
}
|
|
99
|
+
/** Transforms an input into a node tree and extracts its emojis. */
|
|
100
|
+
async function resolveContent(element, options) {
|
|
101
|
+
const { node: originalNode, stylesheets } = await transformElement(element, options);
|
|
102
|
+
const emojiType = options?.emoji ?? "twemoji";
|
|
103
|
+
return {
|
|
104
|
+
node: emojiType !== "from-font" ? (0, _takumi_rs_helpers_emoji.extractEmojis)(originalNode, emojiType) : originalNode,
|
|
105
|
+
stylesheets
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/** Merges caller-supplied images with the resources fetched from the nodes. */
|
|
109
|
+
async function collectImages(nodes, options) {
|
|
110
|
+
const providedImages = /* @__PURE__ */ new Map();
|
|
111
|
+
for (const image of options?.images ?? []) providedImages.set(image.src, image);
|
|
112
|
+
const fetched = await (0, _takumi_rs_helpers.fetchResources)([...new Set(nodes.flatMap((node) => (0, _takumi_rs_helpers.extractResourceUrls)(node)))].filter((url) => !providedImages.has(url)), options?.resourcesOptions);
|
|
113
|
+
return [...providedImages.values(), ...fetched];
|
|
114
|
+
}
|
|
115
|
+
function mergeStylesheets(options, extra) {
|
|
116
|
+
return [...options?.stylesheets ?? [], ...extra];
|
|
117
|
+
}
|
|
109
118
|
/**
|
|
110
119
|
* Renders a React element, HTML string, or Takumi node tree into an image.
|
|
111
120
|
*
|
|
@@ -128,21 +137,95 @@ async function transformElement(element, options) {
|
|
|
128
137
|
*/
|
|
129
138
|
async function render(element, options) {
|
|
130
139
|
options?.signal?.throwIfAborted();
|
|
131
|
-
const
|
|
132
|
-
const
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const emojiType = options?.emoji ?? "twemoji";
|
|
137
|
-
const node = emojiType !== "from-font" ? (0, _takumi_rs_helpers_emoji.extractEmojis)(originalNode, emojiType) : originalNode;
|
|
138
|
-
const fetchedResources = options?.fetchedResources ?? await (0, _takumi_rs_helpers.fetchResources)((0, _takumi_rs_helpers.extractResourceUrls)(node), options?.resourcesOptions);
|
|
139
|
-
const renderOptions = {
|
|
140
|
+
const renderer = await resolveRenderer(options);
|
|
141
|
+
const { node, stylesheets } = await resolveContent(element, options);
|
|
142
|
+
const images = await collectImages([node], options);
|
|
143
|
+
options?.signal?.throwIfAborted();
|
|
144
|
+
return renderer.render(node, {
|
|
140
145
|
...options,
|
|
141
|
-
|
|
142
|
-
stylesheets:
|
|
143
|
-
};
|
|
146
|
+
images,
|
|
147
|
+
stylesheets: mergeStylesheets(options, stylesheets)
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Renders a React element, HTML string, or Takumi node tree into a vector SVG
|
|
152
|
+
* document string.
|
|
153
|
+
*
|
|
154
|
+
* Same input handling and resource pipeline as {@link render}, but emits real SVG
|
|
155
|
+
* (`<rect>`, `<path>`, gradients, glyph outlines, embedded images) instead of a
|
|
156
|
+
* raster bitmap.
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```tsx
|
|
160
|
+
* import { renderSvg } from "takumi-js";
|
|
161
|
+
*
|
|
162
|
+
* const svg = await renderSvg(
|
|
163
|
+
* <div tw="bg-blue-500 text-white p-4">Hello World</div>,
|
|
164
|
+
* { width: 1200, height: 630 }
|
|
165
|
+
* );
|
|
166
|
+
* ```
|
|
167
|
+
*
|
|
168
|
+
* @returns A promise that resolves to the SVG document string.
|
|
169
|
+
*/
|
|
170
|
+
async function renderSvg(element, options) {
|
|
144
171
|
options?.signal?.throwIfAborted();
|
|
145
|
-
|
|
172
|
+
const renderer = await resolveRenderer(options);
|
|
173
|
+
const { node, stylesheets } = await resolveContent(element, options);
|
|
174
|
+
const images = await collectImages([node], options);
|
|
175
|
+
options?.signal?.throwIfAborted();
|
|
176
|
+
return renderer.renderSvg(node, {
|
|
177
|
+
...options,
|
|
178
|
+
images,
|
|
179
|
+
stylesheets: mergeStylesheets(options, stylesheets)
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Renders a sequence of scenes into an animated image (WebP / APNG / GIF).
|
|
184
|
+
*
|
|
185
|
+
* Each scene's content goes through the same input handling and resource pipeline
|
|
186
|
+
* as {@link render}; resources are fetched once across all scenes.
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* ```tsx
|
|
190
|
+
* import { renderAnimation } from "takumi-js";
|
|
191
|
+
*
|
|
192
|
+
* const webp = await renderAnimation({
|
|
193
|
+
* width: 600,
|
|
194
|
+
* height: 400,
|
|
195
|
+
* fps: 30,
|
|
196
|
+
* format: "webp",
|
|
197
|
+
* scenes: [
|
|
198
|
+
* { node: <div tw="bg-red-500 w-full h-full" />, durationMs: 500 },
|
|
199
|
+
* { node: <div tw="bg-blue-500 w-full h-full" />, durationMs: 500 },
|
|
200
|
+
* ],
|
|
201
|
+
* });
|
|
202
|
+
* ```
|
|
203
|
+
*
|
|
204
|
+
* @returns A promise that resolves to the encoded animation (Buffer/Uint8Array).
|
|
205
|
+
*/
|
|
206
|
+
async function renderAnimation(options) {
|
|
207
|
+
options.signal?.throwIfAborted();
|
|
208
|
+
const renderer = await resolveRenderer(options);
|
|
209
|
+
const scenes = await Promise.all(options.scenes.map(async (scene) => {
|
|
210
|
+
const { node, stylesheets } = await resolveContent(scene.node, options);
|
|
211
|
+
return {
|
|
212
|
+
node,
|
|
213
|
+
durationMs: scene.durationMs,
|
|
214
|
+
stylesheets
|
|
215
|
+
};
|
|
216
|
+
}));
|
|
217
|
+
const images = await collectImages(scenes.map((scene) => scene.node), options);
|
|
218
|
+
const stylesheets = mergeStylesheets(options, scenes.flatMap((scene) => scene.stylesheets));
|
|
219
|
+
options.signal?.throwIfAborted();
|
|
220
|
+
return renderer.renderAnimation({
|
|
221
|
+
...options,
|
|
222
|
+
scenes: scenes.map(({ node, durationMs }) => ({
|
|
223
|
+
node,
|
|
224
|
+
durationMs
|
|
225
|
+
})),
|
|
226
|
+
images,
|
|
227
|
+
stylesheets
|
|
228
|
+
});
|
|
146
229
|
}
|
|
147
230
|
//#endregion
|
|
148
231
|
Object.defineProperty(exports, "render", {
|
|
@@ -151,3 +234,15 @@ Object.defineProperty(exports, "render", {
|
|
|
151
234
|
return render;
|
|
152
235
|
}
|
|
153
236
|
});
|
|
237
|
+
Object.defineProperty(exports, "renderAnimation", {
|
|
238
|
+
enumerable: true,
|
|
239
|
+
get: function() {
|
|
240
|
+
return renderAnimation;
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
Object.defineProperty(exports, "renderSvg", {
|
|
244
|
+
enumerable: true,
|
|
245
|
+
get: function() {
|
|
246
|
+
return renderSvg;
|
|
247
|
+
}
|
|
248
|
+
});
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { EmojiType } from "@takumi-rs/helpers/emoji";
|
|
2
|
+
import { FetchResourcesOptions, Node, ReactElementLike } from "@takumi-rs/helpers";
|
|
3
|
+
import { FromJsxOptions } from "@takumi-rs/helpers/jsx";
|
|
4
|
+
import * as napi from "@takumi-rs/core";
|
|
5
|
+
import * as wasm from "@takumi-rs/wasm";
|
|
6
|
+
import { ReactNode } from "react";
|
|
7
|
+
|
|
8
|
+
//#region src/render.d.ts
|
|
9
|
+
type Renderer = napi.Renderer | wasm.Renderer;
|
|
10
|
+
/** The managed-renderer plumbing shared by every entry point. */
|
|
11
|
+
type SharedRenderExtras = {
|
|
12
|
+
renderer: Renderer;
|
|
13
|
+
signal?: AbortSignal;
|
|
14
|
+
jsx?: FromJsxOptions;
|
|
15
|
+
resourcesOptions?: FetchResourcesOptions;
|
|
16
|
+
/**
|
|
17
|
+
* @description The emoji provider to use when rendering emojis. If set to `"from-font"`, the renderer will attempt to source emoji glyphs from the loaded fonts.
|
|
18
|
+
* @default "twemoji"
|
|
19
|
+
*/
|
|
20
|
+
emoji?: EmojiType | "from-font";
|
|
21
|
+
};
|
|
22
|
+
type ManagedRendererOptions = {
|
|
23
|
+
/**
|
|
24
|
+
* @description The WebAssembly module to use for the renderer. If not provided, the default resolving strategy will be used.
|
|
25
|
+
*/
|
|
26
|
+
module?: wasm.InitInput | Promise<wasm.InitInput> | {
|
|
27
|
+
default: wasm.InitInput;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Adds the managed-renderer plumbing to a set of inner options. Either bring your
|
|
32
|
+
* own `renderer`, or let Takumi resolve one (optionally pointing at a WASM
|
|
33
|
+
* `module`).
|
|
34
|
+
*/
|
|
35
|
+
type Managed<TInner> = (TInner & SharedRenderExtras) | (TInner & Omit<SharedRenderExtras, "renderer"> & ManagedRendererOptions);
|
|
36
|
+
type InnerRenderOptions = napi.RenderOptions | wasm.RenderOptions;
|
|
37
|
+
type InnerSvgRenderOptions = napi.SvgRenderOptions | wasm.SvgRenderOptions;
|
|
38
|
+
type InnerRenderAnimationOptions = napi.RenderAnimationOptions | wasm.RenderAnimationOptions;
|
|
39
|
+
type RenderOptions = Managed<InnerRenderOptions>;
|
|
40
|
+
type RenderSvgOptions = Managed<InnerSvgRenderOptions>;
|
|
41
|
+
/** A single animation scene whose content is any renderable input. */
|
|
42
|
+
type RenderAnimationScene = Omit<napi.AnimationSceneSource, "node"> & {
|
|
43
|
+
/** The content to render for this scene: JSX, an HTML string, or a node tree. */node: RenderInput;
|
|
44
|
+
};
|
|
45
|
+
type RenderAnimationOptions = Managed<Omit<InnerRenderAnimationOptions, "scenes"> & {
|
|
46
|
+
/** The scenes to render sequentially. */scenes: RenderAnimationScene[];
|
|
47
|
+
}>;
|
|
48
|
+
type RenderInput = ReactNode | ReactElementLike | Node | string;
|
|
49
|
+
/**
|
|
50
|
+
* Renders a React element, HTML string, or Takumi node tree into an image.
|
|
51
|
+
*
|
|
52
|
+
* This function automatically detects the best renderer for your environment (native Rust on Node.js,
|
|
53
|
+
* WASM on Edge/Workers) and handles resource fetching (fonts, images) and emoji extraction.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```tsx
|
|
57
|
+
* import { render } from "takumi-js";
|
|
58
|
+
*
|
|
59
|
+
* const buffer = await render(
|
|
60
|
+
* <div tw="bg-blue-500 text-white p-4">Hello World</div>,
|
|
61
|
+
* { width: 1200, height: 630 }
|
|
62
|
+
* );
|
|
63
|
+
* ```
|
|
64
|
+
*
|
|
65
|
+
* @param element - The content to render. Can be a JSX element (React-like), an HTML string, or a pre-constructed node tree.
|
|
66
|
+
* @param options - Configuration for rendering, including dimensions, format, fonts, and more.
|
|
67
|
+
* @returns A promise that resolves to the rendered image data (Buffer/Uint8Array).
|
|
68
|
+
*/
|
|
69
|
+
declare function render(element: RenderInput, options?: RenderOptions): Promise<Uint8Array<ArrayBufferLike> | Buffer<ArrayBufferLike>>;
|
|
70
|
+
/**
|
|
71
|
+
* Renders a React element, HTML string, or Takumi node tree into a vector SVG
|
|
72
|
+
* document string.
|
|
73
|
+
*
|
|
74
|
+
* Same input handling and resource pipeline as {@link render}, but emits real SVG
|
|
75
|
+
* (`<rect>`, `<path>`, gradients, glyph outlines, embedded images) instead of a
|
|
76
|
+
* raster bitmap.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```tsx
|
|
80
|
+
* import { renderSvg } from "takumi-js";
|
|
81
|
+
*
|
|
82
|
+
* const svg = await renderSvg(
|
|
83
|
+
* <div tw="bg-blue-500 text-white p-4">Hello World</div>,
|
|
84
|
+
* { width: 1200, height: 630 }
|
|
85
|
+
* );
|
|
86
|
+
* ```
|
|
87
|
+
*
|
|
88
|
+
* @returns A promise that resolves to the SVG document string.
|
|
89
|
+
*/
|
|
90
|
+
declare function renderSvg(element: RenderInput, options?: RenderSvgOptions): Promise<string>;
|
|
91
|
+
/**
|
|
92
|
+
* Renders a sequence of scenes into an animated image (WebP / APNG / GIF).
|
|
93
|
+
*
|
|
94
|
+
* Each scene's content goes through the same input handling and resource pipeline
|
|
95
|
+
* as {@link render}; resources are fetched once across all scenes.
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```tsx
|
|
99
|
+
* import { renderAnimation } from "takumi-js";
|
|
100
|
+
*
|
|
101
|
+
* const webp = await renderAnimation({
|
|
102
|
+
* width: 600,
|
|
103
|
+
* height: 400,
|
|
104
|
+
* fps: 30,
|
|
105
|
+
* format: "webp",
|
|
106
|
+
* scenes: [
|
|
107
|
+
* { node: <div tw="bg-red-500 w-full h-full" />, durationMs: 500 },
|
|
108
|
+
* { node: <div tw="bg-blue-500 w-full h-full" />, durationMs: 500 },
|
|
109
|
+
* ],
|
|
110
|
+
* });
|
|
111
|
+
* ```
|
|
112
|
+
*
|
|
113
|
+
* @returns A promise that resolves to the encoded animation (Buffer/Uint8Array).
|
|
114
|
+
*/
|
|
115
|
+
declare function renderAnimation(options: RenderAnimationOptions): Promise<Uint8Array<ArrayBufferLike> | Buffer<ArrayBufferLike>>;
|
|
116
|
+
//#endregion
|
|
117
|
+
export { RenderSvgOptions as a, renderSvg as c, RenderOptions as i, RenderAnimationScene as n, render as o, RenderInput as r, renderAnimation as s, RenderAnimationOptions as t };
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { EmojiType } from "@takumi-rs/helpers/emoji";
|
|
2
|
+
import { FetchResourcesOptions, Node, ReactElementLike } from "@takumi-rs/helpers";
|
|
3
|
+
import { FromJsxOptions } from "@takumi-rs/helpers/jsx";
|
|
4
|
+
import * as napi from "@takumi-rs/core";
|
|
5
|
+
import * as wasm from "@takumi-rs/wasm";
|
|
6
|
+
import { ReactNode } from "react";
|
|
7
|
+
|
|
8
|
+
//#region src/render.d.ts
|
|
9
|
+
type Renderer = napi.Renderer | wasm.Renderer;
|
|
10
|
+
/** The managed-renderer plumbing shared by every entry point. */
|
|
11
|
+
type SharedRenderExtras = {
|
|
12
|
+
renderer: Renderer;
|
|
13
|
+
signal?: AbortSignal;
|
|
14
|
+
jsx?: FromJsxOptions;
|
|
15
|
+
resourcesOptions?: FetchResourcesOptions;
|
|
16
|
+
/**
|
|
17
|
+
* @description The emoji provider to use when rendering emojis. If set to `"from-font"`, the renderer will attempt to source emoji glyphs from the loaded fonts.
|
|
18
|
+
* @default "twemoji"
|
|
19
|
+
*/
|
|
20
|
+
emoji?: EmojiType | "from-font";
|
|
21
|
+
};
|
|
22
|
+
type ManagedRendererOptions = {
|
|
23
|
+
/**
|
|
24
|
+
* @description The WebAssembly module to use for the renderer. If not provided, the default resolving strategy will be used.
|
|
25
|
+
*/
|
|
26
|
+
module?: wasm.InitInput | Promise<wasm.InitInput> | {
|
|
27
|
+
default: wasm.InitInput;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Adds the managed-renderer plumbing to a set of inner options. Either bring your
|
|
32
|
+
* own `renderer`, or let Takumi resolve one (optionally pointing at a WASM
|
|
33
|
+
* `module`).
|
|
34
|
+
*/
|
|
35
|
+
type Managed<TInner> = (TInner & SharedRenderExtras) | (TInner & Omit<SharedRenderExtras, "renderer"> & ManagedRendererOptions);
|
|
36
|
+
type InnerRenderOptions = napi.RenderOptions | wasm.RenderOptions;
|
|
37
|
+
type InnerSvgRenderOptions = napi.SvgRenderOptions | wasm.SvgRenderOptions;
|
|
38
|
+
type InnerRenderAnimationOptions = napi.RenderAnimationOptions | wasm.RenderAnimationOptions;
|
|
39
|
+
type RenderOptions = Managed<InnerRenderOptions>;
|
|
40
|
+
type RenderSvgOptions = Managed<InnerSvgRenderOptions>;
|
|
41
|
+
/** A single animation scene whose content is any renderable input. */
|
|
42
|
+
type RenderAnimationScene = Omit<napi.AnimationSceneSource, "node"> & {
|
|
43
|
+
/** The content to render for this scene: JSX, an HTML string, or a node tree. */node: RenderInput;
|
|
44
|
+
};
|
|
45
|
+
type RenderAnimationOptions = Managed<Omit<InnerRenderAnimationOptions, "scenes"> & {
|
|
46
|
+
/** The scenes to render sequentially. */scenes: RenderAnimationScene[];
|
|
47
|
+
}>;
|
|
48
|
+
type RenderInput = ReactNode | ReactElementLike | Node | string;
|
|
49
|
+
/**
|
|
50
|
+
* Renders a React element, HTML string, or Takumi node tree into an image.
|
|
51
|
+
*
|
|
52
|
+
* This function automatically detects the best renderer for your environment (native Rust on Node.js,
|
|
53
|
+
* WASM on Edge/Workers) and handles resource fetching (fonts, images) and emoji extraction.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```tsx
|
|
57
|
+
* import { render } from "takumi-js";
|
|
58
|
+
*
|
|
59
|
+
* const buffer = await render(
|
|
60
|
+
* <div tw="bg-blue-500 text-white p-4">Hello World</div>,
|
|
61
|
+
* { width: 1200, height: 630 }
|
|
62
|
+
* );
|
|
63
|
+
* ```
|
|
64
|
+
*
|
|
65
|
+
* @param element - The content to render. Can be a JSX element (React-like), an HTML string, or a pre-constructed node tree.
|
|
66
|
+
* @param options - Configuration for rendering, including dimensions, format, fonts, and more.
|
|
67
|
+
* @returns A promise that resolves to the rendered image data (Buffer/Uint8Array).
|
|
68
|
+
*/
|
|
69
|
+
declare function render(element: RenderInput, options?: RenderOptions): Promise<Uint8Array<ArrayBufferLike> | Buffer<ArrayBufferLike>>;
|
|
70
|
+
/**
|
|
71
|
+
* Renders a React element, HTML string, or Takumi node tree into a vector SVG
|
|
72
|
+
* document string.
|
|
73
|
+
*
|
|
74
|
+
* Same input handling and resource pipeline as {@link render}, but emits real SVG
|
|
75
|
+
* (`<rect>`, `<path>`, gradients, glyph outlines, embedded images) instead of a
|
|
76
|
+
* raster bitmap.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```tsx
|
|
80
|
+
* import { renderSvg } from "takumi-js";
|
|
81
|
+
*
|
|
82
|
+
* const svg = await renderSvg(
|
|
83
|
+
* <div tw="bg-blue-500 text-white p-4">Hello World</div>,
|
|
84
|
+
* { width: 1200, height: 630 }
|
|
85
|
+
* );
|
|
86
|
+
* ```
|
|
87
|
+
*
|
|
88
|
+
* @returns A promise that resolves to the SVG document string.
|
|
89
|
+
*/
|
|
90
|
+
declare function renderSvg(element: RenderInput, options?: RenderSvgOptions): Promise<string>;
|
|
91
|
+
/**
|
|
92
|
+
* Renders a sequence of scenes into an animated image (WebP / APNG / GIF).
|
|
93
|
+
*
|
|
94
|
+
* Each scene's content goes through the same input handling and resource pipeline
|
|
95
|
+
* as {@link render}; resources are fetched once across all scenes.
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```tsx
|
|
99
|
+
* import { renderAnimation } from "takumi-js";
|
|
100
|
+
*
|
|
101
|
+
* const webp = await renderAnimation({
|
|
102
|
+
* width: 600,
|
|
103
|
+
* height: 400,
|
|
104
|
+
* fps: 30,
|
|
105
|
+
* format: "webp",
|
|
106
|
+
* scenes: [
|
|
107
|
+
* { node: <div tw="bg-red-500 w-full h-full" />, durationMs: 500 },
|
|
108
|
+
* { node: <div tw="bg-blue-500 w-full h-full" />, durationMs: 500 },
|
|
109
|
+
* ],
|
|
110
|
+
* });
|
|
111
|
+
* ```
|
|
112
|
+
*
|
|
113
|
+
* @returns A promise that resolves to the encoded animation (Buffer/Uint8Array).
|
|
114
|
+
*/
|
|
115
|
+
declare function renderAnimation(options: RenderAnimationOptions): Promise<Uint8Array<ArrayBufferLike> | Buffer<ArrayBufferLike>>;
|
|
116
|
+
//#endregion
|
|
117
|
+
export { RenderSvgOptions as a, renderSvg as c, RenderOptions as i, RenderAnimationScene as n, render as o, RenderInput as r, renderAnimation as s, RenderAnimationOptions as t };
|
package/dist/response.cjs
CHANGED
|
@@ -2,22 +2,8 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_render = require("./render-
|
|
5
|
+
const require_render = require("./render-BWrJI7_9.cjs");
|
|
6
6
|
//#region src/response/index.ts
|
|
7
|
-
function mergeOptions(defaultOptions, options) {
|
|
8
|
-
if (!defaultOptions) return options;
|
|
9
|
-
if (!options) return defaultOptions;
|
|
10
|
-
const headers = new Headers(defaultOptions?.headers);
|
|
11
|
-
if (options?.headers) new Headers(options.headers).forEach((value, key) => {
|
|
12
|
-
headers.set(key, value);
|
|
13
|
-
});
|
|
14
|
-
return {
|
|
15
|
-
...defaultOptions,
|
|
16
|
-
...options,
|
|
17
|
-
headers,
|
|
18
|
-
stylesheets: [...defaultOptions?.stylesheets ?? [], ...options?.stylesheets ?? []]
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
7
|
const contentTypeMap = {
|
|
22
8
|
png: "image/png",
|
|
23
9
|
jpeg: "image/jpeg",
|
|
@@ -29,62 +15,38 @@ function defaultErrorHandler(error) {
|
|
|
29
15
|
console.error("Failed to render image.");
|
|
30
16
|
console.error(error);
|
|
31
17
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const image = await require_render.render(element, mergedOptions);
|
|
64
|
-
controller.enqueue(image);
|
|
65
|
-
controller.close();
|
|
66
|
-
resolveReady();
|
|
67
|
-
} catch (error) {
|
|
68
|
-
controller.error(error);
|
|
69
|
-
rejectReady(error);
|
|
70
|
-
await (mergedOptions?.onError ?? defaultErrorHandler)(error);
|
|
71
|
-
}
|
|
72
|
-
} });
|
|
73
|
-
const headers = new Headers(mergedOptions?.headers);
|
|
74
|
-
if (!headers.get("content-type")) headers.set("content-type", contentTypeMap[mergedOptions?.format ?? "png"]);
|
|
75
|
-
const response = new Response(stream, {
|
|
76
|
-
headers,
|
|
77
|
-
status: mergedOptions?.status,
|
|
78
|
-
statusText: mergedOptions?.statusText
|
|
79
|
-
});
|
|
80
|
-
return Object.defineProperty(response, "ready", {
|
|
81
|
-
enumerable: false,
|
|
82
|
-
value: ready,
|
|
83
|
-
writable: false
|
|
84
|
-
});
|
|
85
|
-
};
|
|
18
|
+
function buildImageResponse(element, options) {
|
|
19
|
+
let resolveReady;
|
|
20
|
+
let rejectReady;
|
|
21
|
+
const ready = new Promise((resolve, reject) => {
|
|
22
|
+
resolveReady = resolve;
|
|
23
|
+
rejectReady = reject;
|
|
24
|
+
});
|
|
25
|
+
const stream = new ReadableStream({ async start(controller) {
|
|
26
|
+
try {
|
|
27
|
+
const image = await require_render.render(element, options);
|
|
28
|
+
controller.enqueue(image);
|
|
29
|
+
controller.close();
|
|
30
|
+
resolveReady();
|
|
31
|
+
} catch (error) {
|
|
32
|
+
controller.error(error);
|
|
33
|
+
rejectReady(error);
|
|
34
|
+
await (options?.onError ?? defaultErrorHandler)(error);
|
|
35
|
+
}
|
|
36
|
+
} });
|
|
37
|
+
const headers = new Headers(options?.headers);
|
|
38
|
+
if (!headers.get("content-type")) headers.set("content-type", contentTypeMap[options?.format ?? "png"]);
|
|
39
|
+
const response = new Response(stream, {
|
|
40
|
+
headers,
|
|
41
|
+
status: options?.status,
|
|
42
|
+
statusText: options?.statusText
|
|
43
|
+
});
|
|
44
|
+
return Object.defineProperty(response, "ready", {
|
|
45
|
+
enumerable: false,
|
|
46
|
+
value: ready,
|
|
47
|
+
writable: false
|
|
48
|
+
});
|
|
86
49
|
}
|
|
87
|
-
let defaultImageResponse;
|
|
88
50
|
/**
|
|
89
51
|
* A universal ImageResponse class for generating images in API routes.
|
|
90
52
|
*
|
|
@@ -111,8 +73,7 @@ let defaultImageResponse;
|
|
|
111
73
|
var ImageResponse = class extends Response {
|
|
112
74
|
ready;
|
|
113
75
|
constructor(component, options) {
|
|
114
|
-
|
|
115
|
-
const response = defaultImageResponse(component, options);
|
|
76
|
+
const response = buildImageResponse(component, options);
|
|
116
77
|
super(response.body, response);
|
|
117
78
|
this.ready = response.ready;
|
|
118
79
|
}
|
|
@@ -120,4 +81,3 @@ var ImageResponse = class extends Response {
|
|
|
120
81
|
//#endregion
|
|
121
82
|
exports.ImageResponse = ImageResponse;
|
|
122
83
|
exports.default = ImageResponse;
|
|
123
|
-
exports.createImageResponse = createImageResponse;
|
package/dist/response.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as RenderOptions, r as RenderInput } from "./render-DJjKHz2l.cjs";
|
|
2
2
|
|
|
3
3
|
//#region src/response/index.d.ts
|
|
4
4
|
type ImageResponseResult = Response & {
|
|
@@ -7,28 +7,6 @@ type ImageResponseResult = Response & {
|
|
|
7
7
|
type ImageResponseOptions = RenderOptions & ResponseInit & {
|
|
8
8
|
onError?: (error: unknown) => void | Promise<void>;
|
|
9
9
|
};
|
|
10
|
-
type ImageResponseFactory = (component: RenderInput, options?: ImageResponseOptions) => ImageResponseResult;
|
|
11
|
-
/**
|
|
12
|
-
* Creates a reusable ImageResponse factory with shared configuration.
|
|
13
|
-
*
|
|
14
|
-
* Use this to avoid repeating configuration like fonts, stylesheets, or cache headers
|
|
15
|
-
* across multiple API routes.
|
|
16
|
-
*
|
|
17
|
-
* @example
|
|
18
|
-
* ```tsx
|
|
19
|
-
* const myImageResponse = createImageResponse({
|
|
20
|
-
* fonts: [{ name: "Inter", data: fontBuffer }],
|
|
21
|
-
* headers: { "Cache-Control": "public, max-age=31536000" }
|
|
22
|
-
* });
|
|
23
|
-
*
|
|
24
|
-
* export function GET() {
|
|
25
|
-
* return myImageResponse(<div>Custom Og Image</div>);
|
|
26
|
-
* }
|
|
27
|
-
* ```
|
|
28
|
-
*
|
|
29
|
-
* @param defaultOptions - Options that will be applied to every response created by this factory.
|
|
30
|
-
*/
|
|
31
|
-
declare function createImageResponse(defaultOptions?: ImageResponseOptions): ImageResponseFactory;
|
|
32
10
|
/**
|
|
33
11
|
* A universal ImageResponse class for generating images in API routes.
|
|
34
12
|
*
|
|
@@ -57,4 +35,4 @@ declare class ImageResponse extends Response {
|
|
|
57
35
|
constructor(component: RenderInput, options?: ImageResponseOptions);
|
|
58
36
|
}
|
|
59
37
|
//#endregion
|
|
60
|
-
export { ImageResponse, ImageResponse as default,
|
|
38
|
+
export { ImageResponse, ImageResponse as default, ImageResponseOptions, ImageResponseResult };
|
package/dist/response.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as RenderOptions, r as RenderInput } from "./render-DJjKHz2l.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/response/index.d.ts
|
|
4
4
|
type ImageResponseResult = Response & {
|
|
@@ -7,28 +7,6 @@ type ImageResponseResult = Response & {
|
|
|
7
7
|
type ImageResponseOptions = RenderOptions & ResponseInit & {
|
|
8
8
|
onError?: (error: unknown) => void | Promise<void>;
|
|
9
9
|
};
|
|
10
|
-
type ImageResponseFactory = (component: RenderInput, options?: ImageResponseOptions) => ImageResponseResult;
|
|
11
|
-
/**
|
|
12
|
-
* Creates a reusable ImageResponse factory with shared configuration.
|
|
13
|
-
*
|
|
14
|
-
* Use this to avoid repeating configuration like fonts, stylesheets, or cache headers
|
|
15
|
-
* across multiple API routes.
|
|
16
|
-
*
|
|
17
|
-
* @example
|
|
18
|
-
* ```tsx
|
|
19
|
-
* const myImageResponse = createImageResponse({
|
|
20
|
-
* fonts: [{ name: "Inter", data: fontBuffer }],
|
|
21
|
-
* headers: { "Cache-Control": "public, max-age=31536000" }
|
|
22
|
-
* });
|
|
23
|
-
*
|
|
24
|
-
* export function GET() {
|
|
25
|
-
* return myImageResponse(<div>Custom Og Image</div>);
|
|
26
|
-
* }
|
|
27
|
-
* ```
|
|
28
|
-
*
|
|
29
|
-
* @param defaultOptions - Options that will be applied to every response created by this factory.
|
|
30
|
-
*/
|
|
31
|
-
declare function createImageResponse(defaultOptions?: ImageResponseOptions): ImageResponseFactory;
|
|
32
10
|
/**
|
|
33
11
|
* A universal ImageResponse class for generating images in API routes.
|
|
34
12
|
*
|
|
@@ -57,4 +35,4 @@ declare class ImageResponse extends Response {
|
|
|
57
35
|
constructor(component: RenderInput, options?: ImageResponseOptions);
|
|
58
36
|
}
|
|
59
37
|
//#endregion
|
|
60
|
-
export { ImageResponse, ImageResponse as default,
|
|
38
|
+
export { ImageResponse, ImageResponse as default, ImageResponseOptions, ImageResponseResult };
|
package/dist/response.mjs
CHANGED
|
@@ -1,19 +1,5 @@
|
|
|
1
|
-
import { t as render } from "./render-
|
|
1
|
+
import { t as render } from "./render-1w0Pei0G.mjs";
|
|
2
2
|
//#region src/response/index.ts
|
|
3
|
-
function mergeOptions(defaultOptions, options) {
|
|
4
|
-
if (!defaultOptions) return options;
|
|
5
|
-
if (!options) return defaultOptions;
|
|
6
|
-
const headers = new Headers(defaultOptions?.headers);
|
|
7
|
-
if (options?.headers) new Headers(options.headers).forEach((value, key) => {
|
|
8
|
-
headers.set(key, value);
|
|
9
|
-
});
|
|
10
|
-
return {
|
|
11
|
-
...defaultOptions,
|
|
12
|
-
...options,
|
|
13
|
-
headers,
|
|
14
|
-
stylesheets: [...defaultOptions?.stylesheets ?? [], ...options?.stylesheets ?? []]
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
3
|
const contentTypeMap = {
|
|
18
4
|
png: "image/png",
|
|
19
5
|
jpeg: "image/jpeg",
|
|
@@ -25,62 +11,38 @@ function defaultErrorHandler(error) {
|
|
|
25
11
|
console.error("Failed to render image.");
|
|
26
12
|
console.error(error);
|
|
27
13
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const image = await render(element, mergedOptions);
|
|
60
|
-
controller.enqueue(image);
|
|
61
|
-
controller.close();
|
|
62
|
-
resolveReady();
|
|
63
|
-
} catch (error) {
|
|
64
|
-
controller.error(error);
|
|
65
|
-
rejectReady(error);
|
|
66
|
-
await (mergedOptions?.onError ?? defaultErrorHandler)(error);
|
|
67
|
-
}
|
|
68
|
-
} });
|
|
69
|
-
const headers = new Headers(mergedOptions?.headers);
|
|
70
|
-
if (!headers.get("content-type")) headers.set("content-type", contentTypeMap[mergedOptions?.format ?? "png"]);
|
|
71
|
-
const response = new Response(stream, {
|
|
72
|
-
headers,
|
|
73
|
-
status: mergedOptions?.status,
|
|
74
|
-
statusText: mergedOptions?.statusText
|
|
75
|
-
});
|
|
76
|
-
return Object.defineProperty(response, "ready", {
|
|
77
|
-
enumerable: false,
|
|
78
|
-
value: ready,
|
|
79
|
-
writable: false
|
|
80
|
-
});
|
|
81
|
-
};
|
|
14
|
+
function buildImageResponse(element, options) {
|
|
15
|
+
let resolveReady;
|
|
16
|
+
let rejectReady;
|
|
17
|
+
const ready = new Promise((resolve, reject) => {
|
|
18
|
+
resolveReady = resolve;
|
|
19
|
+
rejectReady = reject;
|
|
20
|
+
});
|
|
21
|
+
const stream = new ReadableStream({ async start(controller) {
|
|
22
|
+
try {
|
|
23
|
+
const image = await render(element, options);
|
|
24
|
+
controller.enqueue(image);
|
|
25
|
+
controller.close();
|
|
26
|
+
resolveReady();
|
|
27
|
+
} catch (error) {
|
|
28
|
+
controller.error(error);
|
|
29
|
+
rejectReady(error);
|
|
30
|
+
await (options?.onError ?? defaultErrorHandler)(error);
|
|
31
|
+
}
|
|
32
|
+
} });
|
|
33
|
+
const headers = new Headers(options?.headers);
|
|
34
|
+
if (!headers.get("content-type")) headers.set("content-type", contentTypeMap[options?.format ?? "png"]);
|
|
35
|
+
const response = new Response(stream, {
|
|
36
|
+
headers,
|
|
37
|
+
status: options?.status,
|
|
38
|
+
statusText: options?.statusText
|
|
39
|
+
});
|
|
40
|
+
return Object.defineProperty(response, "ready", {
|
|
41
|
+
enumerable: false,
|
|
42
|
+
value: ready,
|
|
43
|
+
writable: false
|
|
44
|
+
});
|
|
82
45
|
}
|
|
83
|
-
let defaultImageResponse;
|
|
84
46
|
/**
|
|
85
47
|
* A universal ImageResponse class for generating images in API routes.
|
|
86
48
|
*
|
|
@@ -107,11 +69,10 @@ let defaultImageResponse;
|
|
|
107
69
|
var ImageResponse = class extends Response {
|
|
108
70
|
ready;
|
|
109
71
|
constructor(component, options) {
|
|
110
|
-
|
|
111
|
-
const response = defaultImageResponse(component, options);
|
|
72
|
+
const response = buildImageResponse(component, options);
|
|
112
73
|
super(response.body, response);
|
|
113
74
|
this.ready = response.ready;
|
|
114
75
|
}
|
|
115
76
|
};
|
|
116
77
|
//#endregion
|
|
117
|
-
export { ImageResponse, ImageResponse as default
|
|
78
|
+
export { ImageResponse, ImageResponse as default };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "takumi-js",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0-beta.0",
|
|
4
4
|
"description": "All-in-one Takumi package for Node.js and WebAssembly runtimes.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"css",
|
|
@@ -129,9 +129,9 @@
|
|
|
129
129
|
"publish-lint": "attw --pack . && publint --strict ."
|
|
130
130
|
},
|
|
131
131
|
"dependencies": {
|
|
132
|
-
"@takumi-rs/core": "
|
|
133
|
-
"@takumi-rs/helpers": "
|
|
134
|
-
"@takumi-rs/wasm": "
|
|
132
|
+
"@takumi-rs/core": "2.0.0-beta.0",
|
|
133
|
+
"@takumi-rs/helpers": "2.0.0-beta.0",
|
|
134
|
+
"@takumi-rs/wasm": "2.0.0-beta.0"
|
|
135
135
|
},
|
|
136
136
|
"devDependencies": {
|
|
137
137
|
"@types/bun": "catalog:",
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { EmojiType } from "@takumi-rs/helpers/emoji";
|
|
2
|
-
import { FetchResourcesOptions, Node, ReactElementLike } from "@takumi-rs/helpers";
|
|
3
|
-
import { FromJsxOptions } from "@takumi-rs/helpers/jsx";
|
|
4
|
-
import * as napi from "@takumi-rs/core";
|
|
5
|
-
import * as wasm from "@takumi-rs/wasm";
|
|
6
|
-
import { ReactNode } from "react";
|
|
7
|
-
|
|
8
|
-
//#region src/renderer.d.ts
|
|
9
|
-
type ManagedRendererOptions = {
|
|
10
|
-
fonts?: napi.FontLoader[];
|
|
11
|
-
/**
|
|
12
|
-
* Whether to load the embedded default fonts.
|
|
13
|
-
* Defaults to `false` when `fonts` are provided.
|
|
14
|
-
*/
|
|
15
|
-
loadDefaultFonts?: boolean;
|
|
16
|
-
persistentImages?: napi.ImageSourceLoader[];
|
|
17
|
-
/**
|
|
18
|
-
* @description The WebAssembly module to use for the renderer. If not provided, the default resolving strategy will be used.
|
|
19
|
-
*/
|
|
20
|
-
module?: wasm.InitInput | Promise<wasm.InitInput> | {
|
|
21
|
-
default: wasm.InitInput;
|
|
22
|
-
};
|
|
23
|
-
};
|
|
24
|
-
//#endregion
|
|
25
|
-
//#region src/render.d.ts
|
|
26
|
-
type InnerRenderOptions = napi.RenderOptions | wasm.RenderOptions;
|
|
27
|
-
type RenderOptionsWithRenderer = InnerRenderOptions & {
|
|
28
|
-
renderer: napi.Renderer | wasm.Renderer;
|
|
29
|
-
signal?: AbortSignal;
|
|
30
|
-
jsx?: FromJsxOptions;
|
|
31
|
-
resourcesOptions?: FetchResourcesOptions;
|
|
32
|
-
/**
|
|
33
|
-
* @description The emoji provider to use when rendering emojis. If set to `"from-font"`, the renderer will attempt to source emoji glyphs from the loaded fonts.
|
|
34
|
-
* @default "twemoji"
|
|
35
|
-
*/
|
|
36
|
-
emoji?: EmojiType | "from-font";
|
|
37
|
-
};
|
|
38
|
-
type RenderOptionsWithoutRenderer = Omit<RenderOptionsWithRenderer, "renderer"> & ManagedRendererOptions;
|
|
39
|
-
type RenderOptions = RenderOptionsWithRenderer | RenderOptionsWithoutRenderer;
|
|
40
|
-
type RenderInput = ReactNode | ReactElementLike | Node | string;
|
|
41
|
-
/**
|
|
42
|
-
* Renders a React element, HTML string, or Takumi node tree into an image.
|
|
43
|
-
*
|
|
44
|
-
* This function automatically detects the best renderer for your environment (native Rust on Node.js,
|
|
45
|
-
* WASM on Edge/Workers) and handles resource fetching (fonts, images) and emoji extraction.
|
|
46
|
-
*
|
|
47
|
-
* @example
|
|
48
|
-
* ```tsx
|
|
49
|
-
* import { render } from "takumi-js";
|
|
50
|
-
*
|
|
51
|
-
* const buffer = await render(
|
|
52
|
-
* <div tw="bg-blue-500 text-white p-4">Hello World</div>,
|
|
53
|
-
* { width: 1200, height: 630 }
|
|
54
|
-
* );
|
|
55
|
-
* ```
|
|
56
|
-
*
|
|
57
|
-
* @param element - The content to render. Can be a JSX element (React-like), an HTML string, or a pre-constructed node tree.
|
|
58
|
-
* @param options - Configuration for rendering, including dimensions, format, fonts, and more.
|
|
59
|
-
* @returns A promise that resolves to the rendered image data (Buffer/Uint8Array).
|
|
60
|
-
*/
|
|
61
|
-
declare function render(element: RenderInput, options?: RenderOptions): Promise<Uint8Array<ArrayBufferLike> | Buffer<ArrayBufferLike>>;
|
|
62
|
-
//#endregion
|
|
63
|
-
export { RenderOptions as n, render as r, RenderInput as t };
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { EmojiType } from "@takumi-rs/helpers/emoji";
|
|
2
|
-
import { FetchResourcesOptions, Node, ReactElementLike } from "@takumi-rs/helpers";
|
|
3
|
-
import { FromJsxOptions } from "@takumi-rs/helpers/jsx";
|
|
4
|
-
import * as napi from "@takumi-rs/core";
|
|
5
|
-
import * as wasm from "@takumi-rs/wasm";
|
|
6
|
-
import { ReactNode } from "react";
|
|
7
|
-
|
|
8
|
-
//#region src/renderer.d.ts
|
|
9
|
-
type ManagedRendererOptions = {
|
|
10
|
-
fonts?: napi.FontLoader[];
|
|
11
|
-
/**
|
|
12
|
-
* Whether to load the embedded default fonts.
|
|
13
|
-
* Defaults to `false` when `fonts` are provided.
|
|
14
|
-
*/
|
|
15
|
-
loadDefaultFonts?: boolean;
|
|
16
|
-
persistentImages?: napi.ImageSourceLoader[];
|
|
17
|
-
/**
|
|
18
|
-
* @description The WebAssembly module to use for the renderer. If not provided, the default resolving strategy will be used.
|
|
19
|
-
*/
|
|
20
|
-
module?: wasm.InitInput | Promise<wasm.InitInput> | {
|
|
21
|
-
default: wasm.InitInput;
|
|
22
|
-
};
|
|
23
|
-
};
|
|
24
|
-
//#endregion
|
|
25
|
-
//#region src/render.d.ts
|
|
26
|
-
type InnerRenderOptions = napi.RenderOptions | wasm.RenderOptions;
|
|
27
|
-
type RenderOptionsWithRenderer = InnerRenderOptions & {
|
|
28
|
-
renderer: napi.Renderer | wasm.Renderer;
|
|
29
|
-
signal?: AbortSignal;
|
|
30
|
-
jsx?: FromJsxOptions;
|
|
31
|
-
resourcesOptions?: FetchResourcesOptions;
|
|
32
|
-
/**
|
|
33
|
-
* @description The emoji provider to use when rendering emojis. If set to `"from-font"`, the renderer will attempt to source emoji glyphs from the loaded fonts.
|
|
34
|
-
* @default "twemoji"
|
|
35
|
-
*/
|
|
36
|
-
emoji?: EmojiType | "from-font";
|
|
37
|
-
};
|
|
38
|
-
type RenderOptionsWithoutRenderer = Omit<RenderOptionsWithRenderer, "renderer"> & ManagedRendererOptions;
|
|
39
|
-
type RenderOptions = RenderOptionsWithRenderer | RenderOptionsWithoutRenderer;
|
|
40
|
-
type RenderInput = ReactNode | ReactElementLike | Node | string;
|
|
41
|
-
/**
|
|
42
|
-
* Renders a React element, HTML string, or Takumi node tree into an image.
|
|
43
|
-
*
|
|
44
|
-
* This function automatically detects the best renderer for your environment (native Rust on Node.js,
|
|
45
|
-
* WASM on Edge/Workers) and handles resource fetching (fonts, images) and emoji extraction.
|
|
46
|
-
*
|
|
47
|
-
* @example
|
|
48
|
-
* ```tsx
|
|
49
|
-
* import { render } from "takumi-js";
|
|
50
|
-
*
|
|
51
|
-
* const buffer = await render(
|
|
52
|
-
* <div tw="bg-blue-500 text-white p-4">Hello World</div>,
|
|
53
|
-
* { width: 1200, height: 630 }
|
|
54
|
-
* );
|
|
55
|
-
* ```
|
|
56
|
-
*
|
|
57
|
-
* @param element - The content to render. Can be a JSX element (React-like), an HTML string, or a pre-constructed node tree.
|
|
58
|
-
* @param options - Configuration for rendering, including dimensions, format, fonts, and more.
|
|
59
|
-
* @returns A promise that resolves to the rendered image data (Buffer/Uint8Array).
|
|
60
|
-
*/
|
|
61
|
-
declare function render(element: RenderInput, options?: RenderOptions): Promise<Uint8Array<ArrayBufferLike> | Buffer<ArrayBufferLike>>;
|
|
62
|
-
//#endregion
|
|
63
|
-
export { RenderOptions as n, render as r, RenderInput as t };
|