@fraqjs/plugin-takumi 0.2.1 → 0.4.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.d.mts +14 -10
- package/dist/index.mjs +106 -57
- package/package.json +4 -4
package/dist/index.d.mts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { Context, Disposable } from "@fraqjs/fraq";
|
|
2
|
-
import {
|
|
2
|
+
import { FontLoader, RenderOptions, Renderer } from "@takumi-rs/core";
|
|
3
3
|
import { ReactElementLike } from "@takumi-rs/helpers";
|
|
4
4
|
import { EmojiType } from "@takumi-rs/helpers/emoji";
|
|
5
5
|
import { ReactNode } from "react";
|
|
6
|
-
|
|
7
6
|
//#region src/service.d.ts
|
|
8
7
|
interface TakumiServiceOptions {
|
|
9
|
-
renderer?: ConstructRendererOptions;
|
|
10
8
|
renderDefaults?: RenderOptions;
|
|
11
9
|
onFontRegisterConflict?: 'error' | 'warn-and-ignore' | 'warn-and-replace';
|
|
12
10
|
}
|
|
11
|
+
type RenderCallOptions = RenderOptions & {
|
|
12
|
+
emojiType?: EmojiType;
|
|
13
|
+
};
|
|
13
14
|
interface PathBasedFontDetails {
|
|
14
15
|
name?: string;
|
|
15
16
|
path: string;
|
|
@@ -21,16 +22,19 @@ declare class TakumiService implements Disposable {
|
|
|
21
22
|
private readonly ctx?;
|
|
22
23
|
readonly renderer: Renderer;
|
|
23
24
|
private readonly abortController;
|
|
25
|
+
private readonly imageFetchCache;
|
|
24
26
|
private readonly registeredFontFamilies;
|
|
25
27
|
private onFontRegisterConflict;
|
|
26
28
|
constructor(options?: TakumiServiceOptions | undefined, ctx?: Context | undefined);
|
|
27
|
-
registerFontFamily(family: string, fonts: (string | PathBasedFontDetails |
|
|
28
|
-
renderJsx(jsx: ReactNode | ReactElementLike,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
renderHtmlWithEmoji(html: string, renderOptions?: RenderOptions, signal?: AbortSignal, emojiType?: EmojiType): Promise<Buffer>;
|
|
29
|
+
registerFontFamily(family: string, fonts: (string | PathBasedFontDetails | FontLoader)[], signal?: AbortSignal): Promise<void>;
|
|
30
|
+
renderJsx(jsx: ReactNode | ReactElementLike, options?: RenderCallOptions): Promise<Buffer>;
|
|
31
|
+
renderHtml(html: string, options?: RenderCallOptions): Promise<Buffer>;
|
|
32
|
+
private renderNode;
|
|
32
33
|
private mergeRenderOptions;
|
|
33
|
-
private
|
|
34
|
+
private mergeFonts;
|
|
35
|
+
private mergeImages;
|
|
36
|
+
private toImagesInput;
|
|
37
|
+
private toFontLoader;
|
|
34
38
|
dispose(): void;
|
|
35
39
|
}
|
|
36
40
|
//#endregion
|
|
@@ -41,4 +45,4 @@ interface TakumiPluginOptions extends TakumiServiceOptions {
|
|
|
41
45
|
declare function loadBuiltinFontsForService(service: TakumiService): Promise<void>;
|
|
42
46
|
declare const TakumiPlugin: import("@fraqjs/fraq").Plugin<[options?: TakumiPluginOptions | undefined], import("@fraqjs/fraq").Injection | undefined, import("@fraqjs/fraq").Injection | undefined>;
|
|
43
47
|
//#endregion
|
|
44
|
-
export { PathBasedFontDetails, TakumiPlugin, TakumiPlugin as default, TakumiPluginOptions, TakumiService, TakumiServiceOptions, loadBuiltinFontsForService };
|
|
48
|
+
export { PathBasedFontDetails, RenderCallOptions, TakumiPlugin, TakumiPlugin as default, TakumiPluginOptions, TakumiService, TakumiServiceOptions, loadBuiltinFontsForService };
|
package/dist/index.mjs
CHANGED
|
@@ -1,26 +1,39 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import { definePlugin } from "@fraqjs/fraq";
|
|
3
|
-
import { Renderer
|
|
4
|
-
import {
|
|
3
|
+
import { Renderer } from "@takumi-rs/core";
|
|
4
|
+
import { fontFromUrl, prepareImages } from "@takumi-rs/helpers";
|
|
5
5
|
import { extractEmojis } from "@takumi-rs/helpers/emoji";
|
|
6
6
|
import { fromHtml } from "@takumi-rs/helpers/html";
|
|
7
7
|
import { fromJsx } from "@takumi-rs/helpers/jsx";
|
|
8
8
|
import fs from "node:fs/promises";
|
|
9
9
|
//#region src/service.ts
|
|
10
10
|
function combineAbortSignals(...signals) {
|
|
11
|
-
|
|
11
|
+
const filteredSignals = signals.filter((signal) => signal !== void 0);
|
|
12
|
+
if (filteredSignals.length === 0) return;
|
|
13
|
+
if (filteredSignals.length === 1) return filteredSignals[0];
|
|
14
|
+
return AbortSignal.any(filteredSignals);
|
|
15
|
+
}
|
|
16
|
+
function getImageSources(images) {
|
|
17
|
+
if (!images) return [];
|
|
18
|
+
if (Array.isArray(images)) return images;
|
|
19
|
+
return images.sources ?? [];
|
|
20
|
+
}
|
|
21
|
+
function getImageCache(images) {
|
|
22
|
+
if (!images || Array.isArray(images)) return;
|
|
23
|
+
return images.cache;
|
|
12
24
|
}
|
|
13
25
|
var TakumiService = class {
|
|
14
26
|
options;
|
|
15
27
|
ctx;
|
|
16
28
|
renderer;
|
|
17
29
|
abortController = new AbortController();
|
|
18
|
-
|
|
30
|
+
imageFetchCache = /* @__PURE__ */ new Map();
|
|
31
|
+
registeredFontFamilies = /* @__PURE__ */ new Map();
|
|
19
32
|
onFontRegisterConflict;
|
|
20
33
|
constructor(options, ctx) {
|
|
21
34
|
this.options = options;
|
|
22
35
|
this.ctx = ctx;
|
|
23
|
-
this.renderer = new Renderer(
|
|
36
|
+
this.renderer = new Renderer();
|
|
24
37
|
this.onFontRegisterConflict = options?.onFontRegisterConflict ?? "warn-and-ignore";
|
|
25
38
|
}
|
|
26
39
|
async registerFontFamily(family, fonts, signal) {
|
|
@@ -34,56 +47,49 @@ var TakumiService = class {
|
|
|
34
47
|
} else if (this.onFontRegisterConflict === "warn-and-replace") if (this.ctx) this.ctx.logger.warn(`${message} Replacing previous registration.`);
|
|
35
48
|
else console.warn(`${message} Replacing previous registration.`);
|
|
36
49
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
name: family,
|
|
40
|
-
data: await fs.readFile(font)
|
|
41
|
-
};
|
|
42
|
-
else if ("path" in font) {
|
|
43
|
-
const { path, name = family, weight, style } = font;
|
|
44
|
-
return {
|
|
45
|
-
name,
|
|
46
|
-
data: await fs.readFile(path),
|
|
47
|
-
weight,
|
|
48
|
-
style
|
|
49
|
-
};
|
|
50
|
-
} else return font;
|
|
51
|
-
})), combineAbortSignals(signal, this.abortController.signal));
|
|
52
|
-
this.registeredFontFamilies.add(family);
|
|
53
|
-
}
|
|
54
|
-
async renderJsx(jsx, renderOptions, signal) {
|
|
55
|
-
const { node, stylesheets } = await fromJsx(jsx);
|
|
56
|
-
return this.renderer.render(node, this.mergeRenderOptions({
|
|
57
|
-
stylesheets,
|
|
58
|
-
userOptions: renderOptions
|
|
59
|
-
}), combineAbortSignals(signal, this.abortController.signal));
|
|
50
|
+
signal?.throwIfAborted();
|
|
51
|
+
this.registeredFontFamilies.set(family, fonts.map((font) => this.toFontLoader(family, font)));
|
|
60
52
|
}
|
|
61
|
-
async
|
|
53
|
+
async renderJsx(jsx, options) {
|
|
54
|
+
const { emojiType, ...renderOptions } = options ?? {};
|
|
62
55
|
const { node, stylesheets } = await fromJsx(jsx);
|
|
63
|
-
|
|
64
|
-
|
|
56
|
+
return await this.renderNode({
|
|
57
|
+
node,
|
|
65
58
|
stylesheets,
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
})
|
|
59
|
+
renderOptions,
|
|
60
|
+
emojiType
|
|
61
|
+
});
|
|
69
62
|
}
|
|
70
|
-
async renderHtml(html,
|
|
63
|
+
async renderHtml(html, options) {
|
|
64
|
+
const { emojiType, ...renderOptions } = options ?? {};
|
|
71
65
|
const { node, stylesheets } = fromHtml(html);
|
|
72
|
-
return
|
|
66
|
+
return await this.renderNode({
|
|
67
|
+
node,
|
|
73
68
|
stylesheets,
|
|
74
|
-
|
|
75
|
-
|
|
69
|
+
renderOptions,
|
|
70
|
+
emojiType
|
|
71
|
+
});
|
|
76
72
|
}
|
|
77
|
-
async
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
stylesheets,
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
})
|
|
73
|
+
async renderNode(components) {
|
|
74
|
+
const node = components.emojiType === void 0 ? components.node : extractEmojis(components.node, components.emojiType);
|
|
75
|
+
return await this.renderer.render(node, await this.mergeRenderOptions({
|
|
76
|
+
node,
|
|
77
|
+
stylesheets: components.stylesheets,
|
|
78
|
+
userOptions: components.renderOptions,
|
|
79
|
+
images: components.emojiType !== void 0
|
|
80
|
+
}));
|
|
85
81
|
}
|
|
86
|
-
mergeRenderOptions(components) {
|
|
82
|
+
async mergeRenderOptions(components) {
|
|
83
|
+
const renderDefaults = this.options?.renderDefaults;
|
|
84
|
+
const signal = combineAbortSignals(components.userOptions?.signal, this.abortController.signal);
|
|
85
|
+
const images = await this.mergeImages({
|
|
86
|
+
node: components.node,
|
|
87
|
+
prepareImages: components.images ?? false,
|
|
88
|
+
signal,
|
|
89
|
+
defaultImages: renderDefaults?.images,
|
|
90
|
+
userImages: components.userOptions?.images
|
|
91
|
+
});
|
|
92
|
+
const fonts = this.mergeFonts(renderDefaults?.fonts, components.userOptions?.fonts);
|
|
87
93
|
return {
|
|
88
94
|
...this.options?.renderDefaults,
|
|
89
95
|
...components.userOptions,
|
|
@@ -92,20 +98,63 @@ var TakumiService = class {
|
|
|
92
98
|
...components.stylesheets ?? [],
|
|
93
99
|
...components.userOptions?.stylesheets ?? []
|
|
94
100
|
],
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
...components.userOptions?.fetchedResources ?? []
|
|
99
|
-
]
|
|
101
|
+
...fonts ? { fonts } : {},
|
|
102
|
+
...images ? { images } : {},
|
|
103
|
+
...signal ? { signal } : {}
|
|
100
104
|
};
|
|
101
105
|
}
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
106
|
+
mergeFonts(defaultFonts, userFonts) {
|
|
107
|
+
const fonts = [...this.registeredFontFamilies.values()].flat();
|
|
108
|
+
if (defaultFonts) fonts.push(...defaultFonts);
|
|
109
|
+
if (userFonts) fonts.push(...userFonts);
|
|
110
|
+
return fonts.length > 0 ? fonts : void 0;
|
|
111
|
+
}
|
|
112
|
+
async mergeImages(components) {
|
|
113
|
+
const sources = [...getImageSources(components.defaultImages), ...getImageSources(components.userImages)];
|
|
114
|
+
const cache = getImageCache(components.userImages) ?? getImageCache(components.defaultImages);
|
|
115
|
+
if (components.prepareImages) {
|
|
116
|
+
const preparedSources = await prepareImages({
|
|
117
|
+
node: components.node,
|
|
118
|
+
sources,
|
|
119
|
+
fetchCache: this.imageFetchCache,
|
|
120
|
+
signal: components.signal,
|
|
121
|
+
throwOnError: false
|
|
122
|
+
});
|
|
123
|
+
return this.toImagesInput(preparedSources, cache);
|
|
124
|
+
}
|
|
125
|
+
return this.toImagesInput(sources, cache);
|
|
126
|
+
}
|
|
127
|
+
toImagesInput(sources, cache) {
|
|
128
|
+
if (sources.length === 0) return cache === void 0 ? void 0 : { cache };
|
|
129
|
+
return cache === void 0 ? sources : {
|
|
130
|
+
sources,
|
|
131
|
+
cache
|
|
107
132
|
};
|
|
108
133
|
}
|
|
134
|
+
toFontLoader(family, font) {
|
|
135
|
+
if (typeof font === "string") {
|
|
136
|
+
if (font.startsWith("https://") || font.startsWith("http://")) return {
|
|
137
|
+
...fontFromUrl(font),
|
|
138
|
+
name: family
|
|
139
|
+
};
|
|
140
|
+
return {
|
|
141
|
+
name: family,
|
|
142
|
+
key: `${family}:${font}`,
|
|
143
|
+
data: () => fs.readFile(font)
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
if ("path" in font) {
|
|
147
|
+
const { path, name = family, weight, style } = font;
|
|
148
|
+
return {
|
|
149
|
+
name,
|
|
150
|
+
key: `${name}:${path}:${weight ?? ""}:${style ?? ""}`,
|
|
151
|
+
weight,
|
|
152
|
+
style,
|
|
153
|
+
data: () => fs.readFile(path)
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
return font;
|
|
157
|
+
}
|
|
109
158
|
dispose() {
|
|
110
159
|
this.abortController.abort();
|
|
111
160
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fraqjs/plugin-takumi",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.4.0",
|
|
5
5
|
"description": "Takumi integration plugin for Fraq",
|
|
6
6
|
"files": [
|
|
7
7
|
"dist",
|
|
@@ -19,12 +19,12 @@
|
|
|
19
19
|
"license": "MIT",
|
|
20
20
|
"peerDependencies": {
|
|
21
21
|
"react": "^19.0.0",
|
|
22
|
-
"@fraqjs/fraq": "^0.
|
|
22
|
+
"@fraqjs/fraq": "^0.13.2"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@fraqjs/takumi-builtin-fonts": "^1.0.0",
|
|
26
|
-
"@takumi-rs/core": "^
|
|
27
|
-
"@takumi-rs/helpers": "^
|
|
26
|
+
"@takumi-rs/core": "^2.0.1",
|
|
27
|
+
"@takumi-rs/helpers": "^2.0.1"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/react": "^19.2.15",
|