@glissade/backend-canvas2d 0.19.0-pre.0 → 0.19.0-pre.2

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.ts CHANGED
@@ -28,6 +28,13 @@ declare class Canvas2DBackend implements RenderBackend {
28
28
  measureText(text: string, font: FontSpec): TextMetricsLite$1;
29
29
  render(list: DisplayList): void;
30
30
  readPixels(): Promise<Uint8ClampedArray>;
31
+ /**
32
+ * The backend's underlying canvas. Exposed so the tree-shakeable
33
+ * `@glissade/backend-canvas2d/snapshot` subpath (`snapshotCanvas`) can read it
34
+ * WITHOUT pulling the data-URL/Blob-encode code onto this base index — a
35
+ * trivial getter that adds no encode bytes here.
36
+ */
37
+ get canvas(): AnyCanvas;
31
38
  dispose(): void;
32
39
  private context;
33
40
  }
package/dist/index.js CHANGED
@@ -72,6 +72,15 @@ var Canvas2DBackend = class {
72
72
  async readPixels() {
73
73
  return this.context(this.target).getImageData(0, 0, this.target.width, this.target.height).data;
74
74
  }
75
+ /**
76
+ * The backend's underlying canvas. Exposed so the tree-shakeable
77
+ * `@glissade/backend-canvas2d/snapshot` subpath (`snapshotCanvas`) can read it
78
+ * WITHOUT pulling the data-URL/Blob-encode code onto this base index — a
79
+ * trivial getter that adds no encode bytes here.
80
+ */
81
+ get canvas() {
82
+ return this.target;
83
+ }
75
84
  dispose() {
76
85
  this.raster.dispose();
77
86
  }
@@ -0,0 +1,48 @@
1
+ import { Canvas2DBackend } from "./index.js";
2
+ import { Scene } from "@glissade/scene";
3
+ import { Timeline } from "@glissade/core";
4
+
5
+ //#region src/snapshot.d.ts
6
+
7
+ type AnyCanvas = HTMLCanvasElement | OffscreenCanvas;
8
+ /**
9
+ * Capture a canvas (or a `Canvas2DBackend`'s current canvas) as a
10
+ * `data:image/png;base64,…` URL — the DX seam an AI consumer hit ("can't
11
+ * screenshot a live canvas"). Render a frame first (`backend.render(list)`),
12
+ * then `await snapshotCanvas(backend)`.
13
+ *
14
+ * Browser-only: relies on `OffscreenCanvas.convertToBlob` /
15
+ * `HTMLCanvasElement.toDataURL`. Async to match the underlying serialization
16
+ * (OffscreenCanvas is Blob-based, hence Promise-shaped).
17
+ *
18
+ * @param canvasOrBackend a `Canvas2DBackend`, an `HTMLCanvasElement`, or an `OffscreenCanvas`.
19
+ * @param type image MIME type (default `image/png`); `quality` for lossy types.
20
+ */
21
+ declare function snapshotCanvas(canvasOrBackend: AnyCanvas | Canvas2DBackend, type?: string, quality?: number): Promise<string>;
22
+ /**
23
+ * One-shot DX convenience (0.19): `evaluate(scene, timeline, t)` →
24
+ * `Canvas2DBackend.render` → `snapshotCanvas()`, returning a
25
+ * `data:image/png;base64,…` URL for the frame. The "screenshot a frame" path a
26
+ * host/test reaches for when it can't drive a live `<canvas>` (the AI-consumer
27
+ * wall).
28
+ *
29
+ * Allocates an offscreen target sized to the scene (`OffscreenCanvas`, falling
30
+ * back to a detached `<canvas>`), so the caller needs no DOM canvas. Browser-only
31
+ * (same constraint as `snapshotCanvas()` — Skia/Node has its own encode path).
32
+ *
33
+ * Mirrors the `evaluate` overload pair: pass a timeline + time to sample an
34
+ * animated frame, or omit both for the controlled-drive form (host-owned
35
+ * playhead via `node.set(...)`, evaluated at the scene's current playhead).
36
+ *
37
+ * @param type image MIME type (default `image/png`); `quality` for lossy types.
38
+ */
39
+ declare function renderToDataURL(scene: Scene, timeline: Timeline, t: number, opts?: {
40
+ type?: string;
41
+ quality?: number;
42
+ }): Promise<string>;
43
+ declare function renderToDataURL(scene: Scene, opts?: {
44
+ type?: string;
45
+ quality?: number;
46
+ }): Promise<string>;
47
+ //#endregion
48
+ export { renderToDataURL, snapshotCanvas };
@@ -0,0 +1,77 @@
1
+ import { Canvas2DBackend } from "./index.js";
2
+ import { evaluate } from "@glissade/scene";
3
+ //#region src/snapshot.ts
4
+ /**
5
+ * @glissade/backend-canvas2d/snapshot — the "screenshot a rendered frame as a
6
+ * data URL" DX seam (the AI-consumer wall: "can't screenshot a live canvas").
7
+ *
8
+ * This is DX / screenshot TOOLING, NOT a render path: a no-build playback embed
9
+ * never needs it. So it lives on this SEPARATE, tree-shakeable subpath — off the
10
+ * `@glissade/backend-canvas2d` base index (which is on the base embed budget),
11
+ * mirroring how `@glissade/scene/path` / `/layout` / `/describe` keep their
12
+ * byte-expensive code off the base scene index. The data-URL/Blob-encode bytes
13
+ * here must never enter the base embed bundle.
14
+ *
15
+ * Browser-only by design: relies on `OffscreenCanvas.convertToBlob` /
16
+ * `HTMLCanvasElement.toDataURL`. The Skia/Node twin has its own
17
+ * `encodePNG`/`toBuffer` path (@napi-rs/canvas). Importing this module in a
18
+ * headless Node env never throws — the browser-only constraint is enforced at
19
+ * call time, never at load.
20
+ */
21
+ /**
22
+ * Encode a `Blob` (from `OffscreenCanvas.convertToBlob`) as a `data:` URL.
23
+ * `arrayBuffer()` + `btoa()` (both Web standards present in browser, worker, and
24
+ * Node) — leaner than wiring a `FileReader` and with no Node `Buffer`
25
+ * dependency, so the bundle stays browser-clean.
26
+ */
27
+ async function blobToDataURL(blob) {
28
+ const bytes = new Uint8Array(await blob.arrayBuffer());
29
+ let binary = "";
30
+ for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
31
+ return `data:${blob.type || "image/png"};base64,${btoa(binary)}`;
32
+ }
33
+ /** Pull the underlying canvas out of a `Canvas2DBackend`, or pass a canvas through. */
34
+ function canvasOf(canvasOrBackend) {
35
+ return canvasOrBackend instanceof Canvas2DBackend ? canvasOrBackend.canvas : canvasOrBackend;
36
+ }
37
+ /**
38
+ * Capture a canvas (or a `Canvas2DBackend`'s current canvas) as a
39
+ * `data:image/png;base64,…` URL — the DX seam an AI consumer hit ("can't
40
+ * screenshot a live canvas"). Render a frame first (`backend.render(list)`),
41
+ * then `await snapshotCanvas(backend)`.
42
+ *
43
+ * Browser-only: relies on `OffscreenCanvas.convertToBlob` /
44
+ * `HTMLCanvasElement.toDataURL`. Async to match the underlying serialization
45
+ * (OffscreenCanvas is Blob-based, hence Promise-shaped).
46
+ *
47
+ * @param canvasOrBackend a `Canvas2DBackend`, an `HTMLCanvasElement`, or an `OffscreenCanvas`.
48
+ * @param type image MIME type (default `image/png`); `quality` for lossy types.
49
+ */
50
+ async function snapshotCanvas(canvasOrBackend, type = "image/png", quality) {
51
+ const canvas = canvasOf(canvasOrBackend);
52
+ if (typeof OffscreenCanvas !== "undefined" && canvas instanceof OffscreenCanvas) return await blobToDataURL(await canvas.convertToBlob({
53
+ type,
54
+ ...quality !== void 0 ? { quality } : {}
55
+ }));
56
+ const el = canvas;
57
+ if (typeof el.toDataURL !== "function") throw new Error("snapshotCanvas() is browser-only: canvas has no toDataURL/convertToBlob");
58
+ return quality !== void 0 ? el.toDataURL(type, quality) : el.toDataURL(type);
59
+ }
60
+ async function renderToDataURL(scene, timelineOrOpts, t, opts) {
61
+ const hasTimeline = timelineOrOpts !== void 0 && typeof timelineOrOpts.version === "number";
62
+ const timeline = hasTimeline ? timelineOrOpts : void 0;
63
+ const o = (hasTimeline ? opts : timelineOrOpts) ?? {};
64
+ const { w, h } = scene.size;
65
+ const backend = new Canvas2DBackend(typeof OffscreenCanvas !== "undefined" ? new OffscreenCanvas(w, h) : Object.assign(document.createElement("canvas"), {
66
+ width: w,
67
+ height: h
68
+ }));
69
+ try {
70
+ backend.render(timeline !== void 0 ? evaluate(scene, timeline, t ?? 0) : evaluate(scene));
71
+ return await snapshotCanvas(backend, o.type, o.quality);
72
+ } finally {
73
+ backend.dispose();
74
+ }
75
+ }
76
+ //#endregion
77
+ export { renderToDataURL, snapshotCanvas };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glissade/backend-canvas2d",
3
- "version": "0.19.0-pre.0",
3
+ "version": "0.19.0-pre.2",
4
4
  "description": "glissade Canvas 2D render backend: DisplayList -> CanvasRenderingContext2D/OffscreenCanvas.",
5
5
  "license": "Apache-2.0",
6
6
  "engines": {
@@ -12,14 +12,18 @@
12
12
  ".": {
13
13
  "types": "./dist/index.d.ts",
14
14
  "default": "./dist/index.js"
15
+ },
16
+ "./snapshot": {
17
+ "types": "./dist/snapshot.d.ts",
18
+ "default": "./dist/snapshot.js"
15
19
  }
16
20
  },
17
21
  "files": [
18
22
  "dist"
19
23
  ],
20
24
  "dependencies": {
21
- "@glissade/core": "0.19.0-pre.0",
22
- "@glissade/scene": "0.19.0-pre.0"
25
+ "@glissade/core": "0.19.0-pre.2",
26
+ "@glissade/scene": "0.19.0-pre.2"
23
27
  },
24
28
  "repository": {
25
29
  "type": "git",