@takumi-rs/wasm 2.0.0-rc.1 → 2.0.0-rc.11

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  # @takumi-rs/wasm
2
2
 
3
- Takumi is a **image rendering engine** written in Rust and this package provides WebAssembly bindings.
3
+ WebAssembly bindings for [Takumi](https://github.com/kane50613/takumi), a Rust image rendering engine.
4
4
 
5
- Please refer to the [Integration](https://takumi.kane.tw/docs/integration) page in our official documentation for more details.
5
+ See the [Integration guide](https://takumi.kane.tw/docs/integration) for setup.
6
6
 
7
- If you are looking for Node.js bindings, take a look at the [@takumi-rs/core](https://npmjs.com/package/@takumi-rs/core) package.
7
+ For Node.js, use [@takumi-rs/core](https://npmjs.com/package/@takumi-rs/core).
package/dist/export.cjs CHANGED
@@ -3,6 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  let _takumi_rs_helpers = require("@takumi-rs/helpers");
6
+ let _takumi_rs_helpers_renderer = require("@takumi-rs/helpers/renderer");
6
7
  //#region pkg/takumi_wasm.js
7
8
  /**
8
9
  * The main renderer for Takumi image rendering engine.
@@ -517,148 +518,84 @@ async function __wbg_init(module_or_path) {
517
518
  }
518
519
  //#endregion
519
520
  //#region src/export.ts
520
- async function resolveImageLoaders(images) {
521
- const bySrc = /* @__PURE__ */ new Map();
522
- for (const image of images) bySrc.set(image.src, image);
523
- return Promise.all([...bySrc.values()].map(async ({ src, data, cache }) => ({
524
- src,
525
- data: typeof data === "function" ? await data() : data,
526
- cache
527
- })));
528
- }
529
521
  var Renderer = class {
530
- fontsByKey = /* @__PURE__ */ new Map();
531
- fontsByData = /* @__PURE__ */ new WeakMap();
532
522
  inner = new Renderer$1();
533
- getFont(key) {
534
- return typeof key === "string" ? this.fontsByKey.get(key) : this.fontsByData.get(key);
535
- }
536
- setFont(key, family) {
537
- if (typeof key === "string") this.fontsByKey.set(key, family);
538
- else this.fontsByData.set(key, family);
539
- }
540
- deleteFont(key) {
541
- if (typeof key === "string") this.fontsByKey.delete(key);
542
- else this.fontsByData.delete(key);
543
- }
544
- async prepareFonts(fonts) {
545
- if (!fonts) return;
546
- const families = await Promise.all(fonts.map(this.registerFont.bind(this)));
547
- return [...new Set(families.flat().map((f) => f.name))];
548
- }
523
+ fonts = new _takumi_rs_helpers_renderer.FontRegistry((font) => this.inner.registerFont(font));
549
524
  async render(node, options) {
550
- const { fonts, fontFamilies, images, subset, ...rest } = options ?? {};
551
- const registeredFamilies = await this.prepareFonts((0, _takumi_rs_helpers.pickFonts)({
525
+ const { fonts, fontFamilies, signal, images, ...rest } = options ?? {};
526
+ signal?.throwIfAborted();
527
+ const resolved = await this.fonts.resolveResources(fonts && (0, _takumi_rs_helpers.subsetFonts)({
552
528
  fonts,
553
- source: node,
554
- subset
555
- }));
556
- const resolvedImages = images ? await resolveImageLoaders(images) : void 0;
529
+ source: node
530
+ }), images, fontFamilies);
531
+ signal?.throwIfAborted();
557
532
  return this.inner.render(node, {
558
533
  ...rest,
559
- images: resolvedImages,
560
- fontFamilies: fontFamilies ?? registeredFamilies
534
+ ...resolved
561
535
  });
562
536
  }
563
537
  async renderAsDataUrl(node, options) {
564
- const { fonts, fontFamilies, images, subset, ...rest } = options ?? {};
565
- const registeredFamilies = await this.prepareFonts((0, _takumi_rs_helpers.pickFonts)({
538
+ const { fonts, fontFamilies, signal, images, ...rest } = options ?? {};
539
+ signal?.throwIfAborted();
540
+ const resolved = await this.fonts.resolveResources(fonts && (0, _takumi_rs_helpers.subsetFonts)({
566
541
  fonts,
567
- source: node,
568
- subset
569
- }));
570
- const resolvedImages = images ? await resolveImageLoaders(images) : void 0;
542
+ source: node
543
+ }), images, fontFamilies);
544
+ signal?.throwIfAborted();
571
545
  return this.inner.renderAsDataUrl(node, {
572
546
  ...rest,
573
- images: resolvedImages,
574
- fontFamilies: fontFamilies ?? registeredFamilies
547
+ ...resolved
575
548
  });
576
549
  }
577
550
  async renderSvg(node, options) {
578
- const { fonts, fontFamilies, images, subset, ...rest } = options ?? {};
579
- const registeredFamilies = await this.prepareFonts((0, _takumi_rs_helpers.pickFonts)({
551
+ const { fonts, fontFamilies, signal, images, ...rest } = options ?? {};
552
+ signal?.throwIfAborted();
553
+ const resolved = await this.fonts.resolveResources(fonts && (0, _takumi_rs_helpers.subsetFonts)({
580
554
  fonts,
581
- source: node,
582
- subset
583
- }));
584
- const resolvedImages = images ? await resolveImageLoaders(images) : void 0;
555
+ source: node
556
+ }), images, fontFamilies);
557
+ signal?.throwIfAborted();
585
558
  return this.inner.renderSvg(node, {
586
559
  ...rest,
587
- images: resolvedImages,
588
- fontFamilies: fontFamilies ?? registeredFamilies
560
+ ...resolved
589
561
  });
590
562
  }
591
563
  async measure(node, options) {
592
- const { fonts, fontFamilies, images, subset, ...rest } = options ?? {};
593
- const registeredFamilies = await this.prepareFonts((0, _takumi_rs_helpers.pickFonts)({
564
+ const { fonts, fontFamilies, signal, images, ...rest } = options ?? {};
565
+ signal?.throwIfAborted();
566
+ const resolved = await this.fonts.resolveResources(fonts && (0, _takumi_rs_helpers.subsetFonts)({
594
567
  fonts,
595
- source: node,
596
- subset
597
- }));
598
- const resolvedImages = images ? await resolveImageLoaders(images) : void 0;
568
+ source: node
569
+ }), images, fontFamilies);
570
+ signal?.throwIfAborted();
599
571
  return this.inner.measure(node, {
600
572
  ...rest,
601
- images: resolvedImages,
602
- fontFamilies: fontFamilies ?? registeredFamilies
573
+ ...resolved
603
574
  });
604
575
  }
605
576
  async renderAnimation(options) {
606
- const { fonts, fontFamilies, images, subset, ...rest } = options;
577
+ const { fonts, fontFamilies, signal, images, ...rest } = options;
578
+ signal?.throwIfAborted();
607
579
  const nodes = options.scenes.map((scene) => scene.node);
608
- const registeredFamilies = await this.prepareFonts((0, _takumi_rs_helpers.pickFonts)({
580
+ const resolved = await this.fonts.resolveResources(fonts && (0, _takumi_rs_helpers.subsetFonts)({
609
581
  fonts,
610
- source: nodes,
611
- subset
612
- }));
613
- const resolvedImages = images ? await resolveImageLoaders(images) : void 0;
582
+ source: nodes
583
+ }), images, fontFamilies);
584
+ signal?.throwIfAborted();
614
585
  return this.inner.renderAnimation({
615
586
  ...rest,
616
- images: resolvedImages,
617
- fontFamilies: fontFamilies ?? registeredFamilies
587
+ ...resolved
618
588
  });
619
589
  }
590
+ registerFont(font) {
591
+ return this.fonts.register(font);
592
+ }
593
+ /** Releases the underlying wasm renderer's memory. */
620
594
  free() {
621
595
  this.inner.free();
622
596
  }
623
- async registerFont(font) {
624
- const loader = typeof font === "string" ? (0, _takumi_rs_helpers.fontFromUrl)(font) : font;
625
- const key = createFontKey(loader);
626
- const cached = this.getFont(key);
627
- if (cached) return cached;
628
- const extracted = extractFontBuffer(loader);
629
- const register = (data) => this.inner.registerFont(isBuffer(loader) ? data : {
630
- ...loader,
631
- data
632
- });
633
- if (isBuffer(extracted)) {
634
- const binded = register(extracted);
635
- this.setFont(key, Promise.resolve(binded));
636
- return binded;
637
- }
638
- const promise = extracted.then(register).catch((error) => {
639
- this.deleteFont(key);
640
- throw error;
641
- });
642
- this.setFont(key, promise);
643
- return promise;
644
- }
645
597
  };
646
- function extractFontBuffer(font) {
647
- if (isBuffer(font)) return font;
648
- if (typeof font.data !== "function") return font.data;
649
- return font.data();
650
- }
651
- function createFontKey(font) {
652
- if (isBuffer(font)) return font;
653
- if ("key" in font && font.key) return font.key;
654
- if (typeof font.data === "function") return `${font.name ?? ""}:${font.weight ?? ""}:${font.style ?? ""}`;
655
- return font.data;
656
- }
657
- function isBuffer(data) {
658
- return data instanceof Uint8Array || data instanceof ArrayBuffer || typeof Buffer !== "undefined" && Buffer.isBuffer(data);
659
- }
660
598
  //#endregion
661
599
  exports.Renderer = Renderer;
662
600
  exports.default = __wbg_init;
663
- exports.extractResourceUrls = _takumi_rs_helpers.extractResourceUrls;
664
601
  exports.initSync = initSync;
package/dist/export.d.cts CHANGED
@@ -1,5 +1,6 @@
1
- import { ContainerNode, ImageNode, Node, Node as Node$1, NodeMetadata, TextNode, extractResourceUrls } from "@takumi-rs/helpers";
1
+ import { ContainerNode, ImageNode, Node, Node as Node$1, NodeMetadata, TextNode } from "@takumi-rs/helpers";
2
2
  import { Properties } from "csstype";
3
+ import { AnimationOutputFormatOptions, AnimationOutputFormatOptions as AnimationOutputFormatOptions$1, FontLoader, FontLoader as FontLoader$1, ImageLoader, ImagesInput, ImagesInput as ImagesInput$1, OutputFormatOptions, OutputFormatOptions as OutputFormatOptions$1 } from "@takumi-rs/helpers/renderer";
3
4
 
4
5
  //#region pkg/takumi_wasm.d.ts
5
6
  type ByteBuf = Uint8Array | ArrayBuffer | Buffer;
@@ -12,6 +13,14 @@ type KeyframesRuleList = {
12
13
  }[];
13
14
  }[];
14
15
  type Keyframes = KeyframesMap | KeyframesRuleList;
16
+ /** Output format for static images. */
17
+ type OutputFormat = "png" | "jpeg" | "webp" | "ico" | "raw";
18
+ /** Output format for animated images. */
19
+ type AnimationOutputFormat = "webp" | "apng" | "gif";
20
+ /** The output dithering algorithm. */
21
+ type DitheringAlgorithm = "none" | "ordered-bayer" | "floyd-steinberg";
22
+ /** Cache policy for a decoded image. Defaults to `"auto"`. */
23
+ type ImageCacheMode = "auto" | "none";
15
24
  type RenderOptions$1 = {
16
25
  /**
17
26
  * The width of the image. If not provided, the width will be automatically calculated based on the content.
@@ -25,11 +34,17 @@ type RenderOptions$1 = {
25
34
  * The format of the image.
26
35
  * @default "png"
27
36
  */
28
- format?: "png" | "jpeg" | "webp" | "ico" | "raw";
37
+ format?: OutputFormat;
29
38
  /**
30
- * The quality of JPEG format (0-100).
39
+ * The quality of lossy formats (0-100). For JPEG; on wasm, WebP is always
40
+ * lossless so this is ignored for WebP.
31
41
  */
32
42
  quality?: number;
43
+ /**
44
+ * Encode WebP losslessly. On wasm, WebP is always lossless, so this is
45
+ * accepted for parity with the native backend but has no effect.
46
+ */
47
+ lossless?: boolean;
33
48
  /**
34
49
  * Images keyed by `src`, each carrying raw bytes. Provided up front and used
35
50
  * in place of fetching external `src` URLs during rendering.
@@ -60,26 +75,33 @@ type RenderOptions$1 = {
60
75
  * The output dithering algorithm.
61
76
  * @default "none"
62
77
  */
63
- dithering?: "none" | "ordered-bayer" | "floyd-steinberg";
78
+ dithering?: DitheringAlgorithm;
64
79
  /**
65
80
  * Per-render font stack: ordered family names used as the fallback chain.
66
81
  * Defaults to all registered families in registration order.
67
82
  */
68
- fontFamilies?: string[];
83
+ fontFamilies?: string[]; /** Default BCP-47 language applied to the root, inherited by nodes without their own lang. */
84
+ lang?: string;
69
85
  };
70
86
  /**
71
87
  * SVG is a vector format, so the raster-only knobs do not apply.
72
88
  */
73
- type SvgRenderOptions$1 = Omit<RenderOptions$1, "format" | "quality" | "drawDebugBorder" | "devicePixelRatio" | "dithering">;
89
+ type SvgRenderOptions$1 = Omit<RenderOptions$1, "format" | "quality" | "lossless" | "drawDebugBorder" | "devicePixelRatio" | "dithering">;
74
90
  type RenderAnimationOptions$1 = {
75
- scenes: AnimationSceneSource[];
91
+ scenes: AnimationScene[];
76
92
  width: number;
77
93
  height: number;
78
- format?: "webp" | "apng" | "gif";
94
+ format?: AnimationOutputFormat;
79
95
  /**
80
- * The quality of WebP format (0-100). Ignored for APNG and GIF.
96
+ * The quality of lossy WebP (0-100). Ignored for APNG and GIF; on wasm, WebP
97
+ * is always lossless so this is ignored for WebP too.
81
98
  */
82
99
  quality?: number;
100
+ /**
101
+ * Encode WebP losslessly. On wasm, animated WebP is always lossless, so this
102
+ * is accepted for parity with the native backend but has no effect.
103
+ */
104
+ lossless?: boolean;
83
105
  /**
84
106
  * Images keyed by `src`, each carrying raw bytes. Provided up front and used
85
107
  * in place of fetching external `src` URLs during rendering.
@@ -90,6 +112,10 @@ type RenderAnimationOptions$1 = {
90
112
  * CSS stylesheets to apply before rendering.
91
113
  */
92
114
  stylesheets?: string[];
115
+ /**
116
+ * Structured keyframes to register alongside stylesheets.
117
+ */
118
+ keyframes?: Keyframes;
93
119
  /**
94
120
  * Defines the ratio resolution of the image to the physical pixels.
95
121
  * @default 1.0
@@ -103,7 +129,8 @@ type RenderAnimationOptions$1 = {
103
129
  * Per-render font stack: ordered family names used as the fallback chain.
104
130
  * Defaults to all registered families in registration order.
105
131
  */
106
- fontFamilies?: string[];
132
+ fontFamilies?: string[]; /** Default BCP-47 language applied to the root, inherited by nodes without their own lang. */
133
+ lang?: string;
107
134
  };
108
135
  type FontDetails = {
109
136
  name?: string;
@@ -120,7 +147,7 @@ type FontDetails = {
120
147
  type ImageSource = {
121
148
  src: string;
122
149
  data: ByteBuf; /** Cache policy for the decoded image. Defaults to `"auto"`. */
123
- cache?: "auto" | "none";
150
+ cache?: ImageCacheMode;
124
151
  };
125
152
  type KeyframeRule = {
126
153
  offsets: number[];
@@ -155,7 +182,7 @@ type MeasuredNode = {
155
182
  children: MeasuredNode[];
156
183
  runs: MeasuredTextRun[];
157
184
  };
158
- type AnimationSceneSource = {
185
+ type AnimationScene = {
159
186
  node: Node$1;
160
187
  durationMs: number;
161
188
  };
@@ -204,80 +231,32 @@ declare function __wbg_init(module_or_path?: {
204
231
  } | InitInput | Promise<InitInput>): Promise<InitOutput>;
205
232
  //#endregion
206
233
  //#region src/export.d.ts
207
- /**
208
- * A font to register. Either a URL string (fetched on demand, with name/weight/style read from the
209
- * file), raw bytes, or a descriptor with a lazy `data()` loader.
210
- */
211
- type FontLoader = string | Font | (Omit<FontDetails, "data"> & {
212
- data: () => Promise<FontDetails["data"]> | FontDetails["data"]; /** Inclusive codepoint ranges this face covers; lets `render` skip it when unused. */
213
- ranges?: [number, number][];
214
- } & ({
215
- key: string;
216
- } | {
217
- name: string;
218
- }));
219
- type ImageLoaderData = ImageSource["data"];
220
- type ImageLoader = Omit<ImageSource, "data"> & {
221
- data: ImageLoaderData | (() => ImageLoaderData | Promise<ImageLoaderData>);
222
- };
223
- /**
224
- * Output format. Format-specific options live on the variant that supports them,
225
- * so `quality` cannot be paired with a lossless format. On wasm, WebP is always
226
- * lossless (lossy WebP is native-only).
227
- */
228
- type OutputFormatOptions = {
229
- format?: "png";
230
- } | {
231
- format: "jpeg";
232
- quality?: number;
233
- } | {
234
- format: "webp";
235
- } | {
236
- format: "ico";
237
- } | {
238
- format: "raw";
239
- };
240
- type RenderOptions = Omit<RenderOptions$1, "images" | "format" | "quality"> & OutputFormatOptions & {
241
- fonts?: FontLoader[];
242
- images?: ImageLoader[]; /** Register only the `fonts` subsets the content renders. @default true */
243
- subset?: boolean;
244
- };
245
- /**
246
- * Animation output format. On wasm, WebP animation is always lossless (lossy
247
- * WebP is native-only).
248
- */
249
- type AnimationOutputFormatOptions = {
250
- format?: "webp";
251
- } | {
252
- format: "apng";
253
- } | {
254
- format: "gif";
234
+ type RenderOptions = Omit<RenderOptions$1, "images" | "format" | "quality" | "lossless"> & OutputFormatOptions$1 & {
235
+ fonts?: FontLoader$1[];
236
+ signal?: AbortSignal;
237
+ images?: ImagesInput$1;
255
238
  };
256
- type RenderAnimationOptions = Omit<RenderAnimationOptions$1, "images" | "format"> & AnimationOutputFormatOptions & {
257
- fonts?: FontLoader[];
258
- images?: ImageLoader[]; /** Register only the `fonts` subsets the content renders. @default true */
259
- subset?: boolean;
239
+ type RenderAnimationOptions = Omit<RenderAnimationOptions$1, "images" | "format" | "quality" | "lossless"> & AnimationOutputFormatOptions$1 & {
240
+ fonts?: FontLoader$1[];
241
+ signal?: AbortSignal;
242
+ images?: ImagesInput$1;
260
243
  };
261
244
  type SvgRenderOptions = Omit<SvgRenderOptions$1, "images"> & {
262
- fonts?: FontLoader[];
263
- images?: ImageLoader[]; /** Register only the `fonts` subsets the content renders. @default true */
264
- subset?: boolean;
245
+ fonts?: FontLoader$1[];
246
+ signal?: AbortSignal;
247
+ images?: ImagesInput$1;
265
248
  };
266
249
  declare class Renderer {
267
- private fontsByKey;
268
- private fontsByData;
269
250
  private inner;
270
- private getFont;
271
- private setFont;
272
- private deleteFont;
273
- private prepareFonts;
251
+ private fonts;
274
252
  render(node: Node, options?: RenderOptions): Promise<Uint8Array<ArrayBufferLike>>;
275
253
  renderAsDataUrl(node: Node, options?: RenderOptions): Promise<string>;
276
254
  renderSvg(node: Node, options?: SvgRenderOptions): Promise<string>;
277
255
  measure(node: Node, options?: RenderOptions): Promise<MeasuredNode>;
278
256
  renderAnimation(options: RenderAnimationOptions): Promise<Uint8Array<ArrayBufferLike>>;
257
+ registerFont(font: FontLoader$1): any;
258
+ /** Releases the underlying wasm renderer's memory. */
279
259
  free(): void;
280
- registerFont(font: FontLoader): Promise<RegisteredFamily[]>;
281
260
  }
282
261
  //#endregion
283
- export { AnimationOutputFormatOptions, AnimationSceneSource, ByteBuf, ContainerNode, Font, FontDetails, FontLoader, ImageLoader, ImageNode, ImageSource, InitInput, InitOutput, KeyframeRule, Keyframes, KeyframesMap, KeyframesRule, KeyframesRuleList, MeasuredNode, MeasuredTextRun, type Node, NodeMetadata, OutputFormatOptions, RegisteredFace, RegisteredFamily, RenderAnimationOptions, RenderOptions, Renderer, SvgRenderOptions, SyncInitInput, TextNode, __wbg_init as default, extractResourceUrls, initSync };
262
+ export { AnimationOutputFormat, type AnimationOutputFormatOptions, AnimationScene, ByteBuf, ContainerNode, DitheringAlgorithm, Font, FontDetails, type FontLoader, ImageCacheMode, type ImageLoader, ImageNode, ImageSource, type ImagesInput, InitInput, InitOutput, KeyframeRule, Keyframes, KeyframesMap, KeyframesRule, KeyframesRuleList, MeasuredNode, MeasuredTextRun, type Node, NodeMetadata, OutputFormat, type OutputFormatOptions, RegisteredFace, RegisteredFamily, RenderAnimationOptions, RenderOptions, Renderer, SvgRenderOptions, SyncInitInput, TextNode, __wbg_init as default, initSync };
package/dist/export.d.mts CHANGED
@@ -1,4 +1,5 @@
1
- import { ContainerNode, ImageNode, Node, Node as Node$1, NodeMetadata, TextNode, extractResourceUrls } from "@takumi-rs/helpers";
1
+ import { ContainerNode, ImageNode, Node, Node as Node$1, NodeMetadata, TextNode } from "@takumi-rs/helpers";
2
+ import { AnimationOutputFormatOptions, AnimationOutputFormatOptions as AnimationOutputFormatOptions$1, FontLoader, FontLoader as FontLoader$1, ImageLoader, ImagesInput, ImagesInput as ImagesInput$1, OutputFormatOptions, OutputFormatOptions as OutputFormatOptions$1 } from "@takumi-rs/helpers/renderer";
2
3
  import { Properties } from "csstype";
3
4
 
4
5
  //#region pkg/takumi_wasm.d.ts
@@ -12,6 +13,14 @@ type KeyframesRuleList = {
12
13
  }[];
13
14
  }[];
14
15
  type Keyframes = KeyframesMap | KeyframesRuleList;
16
+ /** Output format for static images. */
17
+ type OutputFormat = "png" | "jpeg" | "webp" | "ico" | "raw";
18
+ /** Output format for animated images. */
19
+ type AnimationOutputFormat = "webp" | "apng" | "gif";
20
+ /** The output dithering algorithm. */
21
+ type DitheringAlgorithm = "none" | "ordered-bayer" | "floyd-steinberg";
22
+ /** Cache policy for a decoded image. Defaults to `"auto"`. */
23
+ type ImageCacheMode = "auto" | "none";
15
24
  type RenderOptions$1 = {
16
25
  /**
17
26
  * The width of the image. If not provided, the width will be automatically calculated based on the content.
@@ -25,11 +34,17 @@ type RenderOptions$1 = {
25
34
  * The format of the image.
26
35
  * @default "png"
27
36
  */
28
- format?: "png" | "jpeg" | "webp" | "ico" | "raw";
37
+ format?: OutputFormat;
29
38
  /**
30
- * The quality of JPEG format (0-100).
39
+ * The quality of lossy formats (0-100). For JPEG; on wasm, WebP is always
40
+ * lossless so this is ignored for WebP.
31
41
  */
32
42
  quality?: number;
43
+ /**
44
+ * Encode WebP losslessly. On wasm, WebP is always lossless, so this is
45
+ * accepted for parity with the native backend but has no effect.
46
+ */
47
+ lossless?: boolean;
33
48
  /**
34
49
  * Images keyed by `src`, each carrying raw bytes. Provided up front and used
35
50
  * in place of fetching external `src` URLs during rendering.
@@ -60,26 +75,33 @@ type RenderOptions$1 = {
60
75
  * The output dithering algorithm.
61
76
  * @default "none"
62
77
  */
63
- dithering?: "none" | "ordered-bayer" | "floyd-steinberg";
78
+ dithering?: DitheringAlgorithm;
64
79
  /**
65
80
  * Per-render font stack: ordered family names used as the fallback chain.
66
81
  * Defaults to all registered families in registration order.
67
82
  */
68
- fontFamilies?: string[];
83
+ fontFamilies?: string[]; /** Default BCP-47 language applied to the root, inherited by nodes without their own lang. */
84
+ lang?: string;
69
85
  };
70
86
  /**
71
87
  * SVG is a vector format, so the raster-only knobs do not apply.
72
88
  */
73
- type SvgRenderOptions$1 = Omit<RenderOptions$1, "format" | "quality" | "drawDebugBorder" | "devicePixelRatio" | "dithering">;
89
+ type SvgRenderOptions$1 = Omit<RenderOptions$1, "format" | "quality" | "lossless" | "drawDebugBorder" | "devicePixelRatio" | "dithering">;
74
90
  type RenderAnimationOptions$1 = {
75
- scenes: AnimationSceneSource[];
91
+ scenes: AnimationScene[];
76
92
  width: number;
77
93
  height: number;
78
- format?: "webp" | "apng" | "gif";
94
+ format?: AnimationOutputFormat;
79
95
  /**
80
- * The quality of WebP format (0-100). Ignored for APNG and GIF.
96
+ * The quality of lossy WebP (0-100). Ignored for APNG and GIF; on wasm, WebP
97
+ * is always lossless so this is ignored for WebP too.
81
98
  */
82
99
  quality?: number;
100
+ /**
101
+ * Encode WebP losslessly. On wasm, animated WebP is always lossless, so this
102
+ * is accepted for parity with the native backend but has no effect.
103
+ */
104
+ lossless?: boolean;
83
105
  /**
84
106
  * Images keyed by `src`, each carrying raw bytes. Provided up front and used
85
107
  * in place of fetching external `src` URLs during rendering.
@@ -90,6 +112,10 @@ type RenderAnimationOptions$1 = {
90
112
  * CSS stylesheets to apply before rendering.
91
113
  */
92
114
  stylesheets?: string[];
115
+ /**
116
+ * Structured keyframes to register alongside stylesheets.
117
+ */
118
+ keyframes?: Keyframes;
93
119
  /**
94
120
  * Defines the ratio resolution of the image to the physical pixels.
95
121
  * @default 1.0
@@ -103,7 +129,8 @@ type RenderAnimationOptions$1 = {
103
129
  * Per-render font stack: ordered family names used as the fallback chain.
104
130
  * Defaults to all registered families in registration order.
105
131
  */
106
- fontFamilies?: string[];
132
+ fontFamilies?: string[]; /** Default BCP-47 language applied to the root, inherited by nodes without their own lang. */
133
+ lang?: string;
107
134
  };
108
135
  type FontDetails = {
109
136
  name?: string;
@@ -120,7 +147,7 @@ type FontDetails = {
120
147
  type ImageSource = {
121
148
  src: string;
122
149
  data: ByteBuf; /** Cache policy for the decoded image. Defaults to `"auto"`. */
123
- cache?: "auto" | "none";
150
+ cache?: ImageCacheMode;
124
151
  };
125
152
  type KeyframeRule = {
126
153
  offsets: number[];
@@ -155,7 +182,7 @@ type MeasuredNode = {
155
182
  children: MeasuredNode[];
156
183
  runs: MeasuredTextRun[];
157
184
  };
158
- type AnimationSceneSource = {
185
+ type AnimationScene = {
159
186
  node: Node$1;
160
187
  durationMs: number;
161
188
  };
@@ -204,80 +231,32 @@ declare function __wbg_init(module_or_path?: {
204
231
  } | InitInput | Promise<InitInput>): Promise<InitOutput>;
205
232
  //#endregion
206
233
  //#region src/export.d.ts
207
- /**
208
- * A font to register. Either a URL string (fetched on demand, with name/weight/style read from the
209
- * file), raw bytes, or a descriptor with a lazy `data()` loader.
210
- */
211
- type FontLoader = string | Font | (Omit<FontDetails, "data"> & {
212
- data: () => Promise<FontDetails["data"]> | FontDetails["data"]; /** Inclusive codepoint ranges this face covers; lets `render` skip it when unused. */
213
- ranges?: [number, number][];
214
- } & ({
215
- key: string;
216
- } | {
217
- name: string;
218
- }));
219
- type ImageLoaderData = ImageSource["data"];
220
- type ImageLoader = Omit<ImageSource, "data"> & {
221
- data: ImageLoaderData | (() => ImageLoaderData | Promise<ImageLoaderData>);
222
- };
223
- /**
224
- * Output format. Format-specific options live on the variant that supports them,
225
- * so `quality` cannot be paired with a lossless format. On wasm, WebP is always
226
- * lossless (lossy WebP is native-only).
227
- */
228
- type OutputFormatOptions = {
229
- format?: "png";
230
- } | {
231
- format: "jpeg";
232
- quality?: number;
233
- } | {
234
- format: "webp";
235
- } | {
236
- format: "ico";
237
- } | {
238
- format: "raw";
239
- };
240
- type RenderOptions = Omit<RenderOptions$1, "images" | "format" | "quality"> & OutputFormatOptions & {
241
- fonts?: FontLoader[];
242
- images?: ImageLoader[]; /** Register only the `fonts` subsets the content renders. @default true */
243
- subset?: boolean;
244
- };
245
- /**
246
- * Animation output format. On wasm, WebP animation is always lossless (lossy
247
- * WebP is native-only).
248
- */
249
- type AnimationOutputFormatOptions = {
250
- format?: "webp";
251
- } | {
252
- format: "apng";
253
- } | {
254
- format: "gif";
234
+ type RenderOptions = Omit<RenderOptions$1, "images" | "format" | "quality" | "lossless"> & OutputFormatOptions$1 & {
235
+ fonts?: FontLoader$1[];
236
+ signal?: AbortSignal;
237
+ images?: ImagesInput$1;
255
238
  };
256
- type RenderAnimationOptions = Omit<RenderAnimationOptions$1, "images" | "format"> & AnimationOutputFormatOptions & {
257
- fonts?: FontLoader[];
258
- images?: ImageLoader[]; /** Register only the `fonts` subsets the content renders. @default true */
259
- subset?: boolean;
239
+ type RenderAnimationOptions = Omit<RenderAnimationOptions$1, "images" | "format" | "quality" | "lossless"> & AnimationOutputFormatOptions$1 & {
240
+ fonts?: FontLoader$1[];
241
+ signal?: AbortSignal;
242
+ images?: ImagesInput$1;
260
243
  };
261
244
  type SvgRenderOptions = Omit<SvgRenderOptions$1, "images"> & {
262
- fonts?: FontLoader[];
263
- images?: ImageLoader[]; /** Register only the `fonts` subsets the content renders. @default true */
264
- subset?: boolean;
245
+ fonts?: FontLoader$1[];
246
+ signal?: AbortSignal;
247
+ images?: ImagesInput$1;
265
248
  };
266
249
  declare class Renderer {
267
- private fontsByKey;
268
- private fontsByData;
269
250
  private inner;
270
- private getFont;
271
- private setFont;
272
- private deleteFont;
273
- private prepareFonts;
251
+ private fonts;
274
252
  render(node: Node, options?: RenderOptions): Promise<Uint8Array<ArrayBufferLike>>;
275
253
  renderAsDataUrl(node: Node, options?: RenderOptions): Promise<string>;
276
254
  renderSvg(node: Node, options?: SvgRenderOptions): Promise<string>;
277
255
  measure(node: Node, options?: RenderOptions): Promise<MeasuredNode>;
278
256
  renderAnimation(options: RenderAnimationOptions): Promise<Uint8Array<ArrayBufferLike>>;
257
+ registerFont(font: FontLoader$1): any;
258
+ /** Releases the underlying wasm renderer's memory. */
279
259
  free(): void;
280
- registerFont(font: FontLoader): Promise<RegisteredFamily[]>;
281
260
  }
282
261
  //#endregion
283
- export { AnimationOutputFormatOptions, AnimationSceneSource, ByteBuf, ContainerNode, Font, FontDetails, FontLoader, ImageLoader, ImageNode, ImageSource, InitInput, InitOutput, KeyframeRule, Keyframes, KeyframesMap, KeyframesRule, KeyframesRuleList, MeasuredNode, MeasuredTextRun, type Node, NodeMetadata, OutputFormatOptions, RegisteredFace, RegisteredFamily, RenderAnimationOptions, RenderOptions, Renderer, SvgRenderOptions, SyncInitInput, TextNode, __wbg_init as default, extractResourceUrls, initSync };
262
+ export { AnimationOutputFormat, type AnimationOutputFormatOptions, AnimationScene, ByteBuf, ContainerNode, DitheringAlgorithm, Font, FontDetails, type FontLoader, ImageCacheMode, type ImageLoader, ImageNode, ImageSource, type ImagesInput, InitInput, InitOutput, KeyframeRule, Keyframes, KeyframesMap, KeyframesRule, KeyframesRuleList, MeasuredNode, MeasuredTextRun, type Node, NodeMetadata, OutputFormat, type OutputFormatOptions, RegisteredFace, RegisteredFamily, RenderAnimationOptions, RenderOptions, Renderer, SvgRenderOptions, SyncInitInput, TextNode, __wbg_init as default, initSync };
package/dist/export.mjs CHANGED
@@ -1,4 +1,5 @@
1
- import { extractResourceUrls, fontFromUrl, pickFonts } from "@takumi-rs/helpers";
1
+ import { subsetFonts } from "@takumi-rs/helpers";
2
+ import { FontRegistry } from "@takumi-rs/helpers/renderer";
2
3
  //#region pkg/takumi_wasm.js
3
4
  /**
4
5
  * The main renderer for Takumi image rendering engine.
@@ -513,145 +514,82 @@ async function __wbg_init(module_or_path) {
513
514
  }
514
515
  //#endregion
515
516
  //#region src/export.ts
516
- async function resolveImageLoaders(images) {
517
- const bySrc = /* @__PURE__ */ new Map();
518
- for (const image of images) bySrc.set(image.src, image);
519
- return Promise.all([...bySrc.values()].map(async ({ src, data, cache }) => ({
520
- src,
521
- data: typeof data === "function" ? await data() : data,
522
- cache
523
- })));
524
- }
525
517
  var Renderer = class {
526
- fontsByKey = /* @__PURE__ */ new Map();
527
- fontsByData = /* @__PURE__ */ new WeakMap();
528
518
  inner = new Renderer$1();
529
- getFont(key) {
530
- return typeof key === "string" ? this.fontsByKey.get(key) : this.fontsByData.get(key);
531
- }
532
- setFont(key, family) {
533
- if (typeof key === "string") this.fontsByKey.set(key, family);
534
- else this.fontsByData.set(key, family);
535
- }
536
- deleteFont(key) {
537
- if (typeof key === "string") this.fontsByKey.delete(key);
538
- else this.fontsByData.delete(key);
539
- }
540
- async prepareFonts(fonts) {
541
- if (!fonts) return;
542
- const families = await Promise.all(fonts.map(this.registerFont.bind(this)));
543
- return [...new Set(families.flat().map((f) => f.name))];
544
- }
519
+ fonts = new FontRegistry((font) => this.inner.registerFont(font));
545
520
  async render(node, options) {
546
- const { fonts, fontFamilies, images, subset, ...rest } = options ?? {};
547
- const registeredFamilies = await this.prepareFonts(pickFonts({
521
+ const { fonts, fontFamilies, signal, images, ...rest } = options ?? {};
522
+ signal?.throwIfAborted();
523
+ const resolved = await this.fonts.resolveResources(fonts && subsetFonts({
548
524
  fonts,
549
- source: node,
550
- subset
551
- }));
552
- const resolvedImages = images ? await resolveImageLoaders(images) : void 0;
525
+ source: node
526
+ }), images, fontFamilies);
527
+ signal?.throwIfAborted();
553
528
  return this.inner.render(node, {
554
529
  ...rest,
555
- images: resolvedImages,
556
- fontFamilies: fontFamilies ?? registeredFamilies
530
+ ...resolved
557
531
  });
558
532
  }
559
533
  async renderAsDataUrl(node, options) {
560
- const { fonts, fontFamilies, images, subset, ...rest } = options ?? {};
561
- const registeredFamilies = await this.prepareFonts(pickFonts({
534
+ const { fonts, fontFamilies, signal, images, ...rest } = options ?? {};
535
+ signal?.throwIfAborted();
536
+ const resolved = await this.fonts.resolveResources(fonts && subsetFonts({
562
537
  fonts,
563
- source: node,
564
- subset
565
- }));
566
- const resolvedImages = images ? await resolveImageLoaders(images) : void 0;
538
+ source: node
539
+ }), images, fontFamilies);
540
+ signal?.throwIfAborted();
567
541
  return this.inner.renderAsDataUrl(node, {
568
542
  ...rest,
569
- images: resolvedImages,
570
- fontFamilies: fontFamilies ?? registeredFamilies
543
+ ...resolved
571
544
  });
572
545
  }
573
546
  async renderSvg(node, options) {
574
- const { fonts, fontFamilies, images, subset, ...rest } = options ?? {};
575
- const registeredFamilies = await this.prepareFonts(pickFonts({
547
+ const { fonts, fontFamilies, signal, images, ...rest } = options ?? {};
548
+ signal?.throwIfAborted();
549
+ const resolved = await this.fonts.resolveResources(fonts && subsetFonts({
576
550
  fonts,
577
- source: node,
578
- subset
579
- }));
580
- const resolvedImages = images ? await resolveImageLoaders(images) : void 0;
551
+ source: node
552
+ }), images, fontFamilies);
553
+ signal?.throwIfAborted();
581
554
  return this.inner.renderSvg(node, {
582
555
  ...rest,
583
- images: resolvedImages,
584
- fontFamilies: fontFamilies ?? registeredFamilies
556
+ ...resolved
585
557
  });
586
558
  }
587
559
  async measure(node, options) {
588
- const { fonts, fontFamilies, images, subset, ...rest } = options ?? {};
589
- const registeredFamilies = await this.prepareFonts(pickFonts({
560
+ const { fonts, fontFamilies, signal, images, ...rest } = options ?? {};
561
+ signal?.throwIfAborted();
562
+ const resolved = await this.fonts.resolveResources(fonts && subsetFonts({
590
563
  fonts,
591
- source: node,
592
- subset
593
- }));
594
- const resolvedImages = images ? await resolveImageLoaders(images) : void 0;
564
+ source: node
565
+ }), images, fontFamilies);
566
+ signal?.throwIfAborted();
595
567
  return this.inner.measure(node, {
596
568
  ...rest,
597
- images: resolvedImages,
598
- fontFamilies: fontFamilies ?? registeredFamilies
569
+ ...resolved
599
570
  });
600
571
  }
601
572
  async renderAnimation(options) {
602
- const { fonts, fontFamilies, images, subset, ...rest } = options;
573
+ const { fonts, fontFamilies, signal, images, ...rest } = options;
574
+ signal?.throwIfAborted();
603
575
  const nodes = options.scenes.map((scene) => scene.node);
604
- const registeredFamilies = await this.prepareFonts(pickFonts({
576
+ const resolved = await this.fonts.resolveResources(fonts && subsetFonts({
605
577
  fonts,
606
- source: nodes,
607
- subset
608
- }));
609
- const resolvedImages = images ? await resolveImageLoaders(images) : void 0;
578
+ source: nodes
579
+ }), images, fontFamilies);
580
+ signal?.throwIfAborted();
610
581
  return this.inner.renderAnimation({
611
582
  ...rest,
612
- images: resolvedImages,
613
- fontFamilies: fontFamilies ?? registeredFamilies
583
+ ...resolved
614
584
  });
615
585
  }
586
+ registerFont(font) {
587
+ return this.fonts.register(font);
588
+ }
589
+ /** Releases the underlying wasm renderer's memory. */
616
590
  free() {
617
591
  this.inner.free();
618
592
  }
619
- async registerFont(font) {
620
- const loader = typeof font === "string" ? fontFromUrl(font) : font;
621
- const key = createFontKey(loader);
622
- const cached = this.getFont(key);
623
- if (cached) return cached;
624
- const extracted = extractFontBuffer(loader);
625
- const register = (data) => this.inner.registerFont(isBuffer(loader) ? data : {
626
- ...loader,
627
- data
628
- });
629
- if (isBuffer(extracted)) {
630
- const binded = register(extracted);
631
- this.setFont(key, Promise.resolve(binded));
632
- return binded;
633
- }
634
- const promise = extracted.then(register).catch((error) => {
635
- this.deleteFont(key);
636
- throw error;
637
- });
638
- this.setFont(key, promise);
639
- return promise;
640
- }
641
593
  };
642
- function extractFontBuffer(font) {
643
- if (isBuffer(font)) return font;
644
- if (typeof font.data !== "function") return font.data;
645
- return font.data();
646
- }
647
- function createFontKey(font) {
648
- if (isBuffer(font)) return font;
649
- if ("key" in font && font.key) return font.key;
650
- if (typeof font.data === "function") return `${font.name ?? ""}:${font.weight ?? ""}:${font.style ?? ""}`;
651
- return font.data;
652
- }
653
- function isBuffer(data) {
654
- return data instanceof Uint8Array || data instanceof ArrayBuffer || typeof Buffer !== "undefined" && Buffer.isBuffer(data);
655
- }
656
594
  //#endregion
657
- export { Renderer, __wbg_init as default, extractResourceUrls, initSync };
595
+ export { Renderer, __wbg_init as default, initSync };
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@takumi-rs/wasm",
3
- "version": "2.0.0-rc.1",
3
+ "version": "2.0.0-rc.11",
4
+ "description": "WebAssembly bindings for Takumi, a Rust image rendering engine.",
4
5
  "keywords": [
5
6
  "css",
6
7
  "image",
@@ -116,7 +117,7 @@
116
117
  "publish-lint": "attw --pack . && publint --strict ."
117
118
  },
118
119
  "dependencies": {
119
- "@takumi-rs/helpers": "2.0.0-rc.1",
120
+ "@takumi-rs/helpers": "2.0.0-rc.11",
120
121
  "csstype": "^3.2.3"
121
122
  },
122
123
  "devDependencies": {
Binary file