@oh-just-another/renderer-canvas 0.59.0 → 0.61.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.
Files changed (70) hide show
  1. package/CHANGELOG.md +108 -0
  2. package/README.md +10 -7
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/canvas-target.d.ts +6 -1
  5. package/dist/canvas-target.d.ts.map +1 -1
  6. package/dist/canvas-target.js +12 -2
  7. package/dist/canvas-target.js.map +1 -1
  8. package/dist/constants.d.ts +49 -0
  9. package/dist/constants.d.ts.map +1 -1
  10. package/dist/constants.js +49 -0
  11. package/dist/constants.js.map +1 -1
  12. package/dist/hi-dpi.d.ts +10 -0
  13. package/dist/hi-dpi.d.ts.map +1 -1
  14. package/dist/hi-dpi.js +12 -1
  15. package/dist/hi-dpi.js.map +1 -1
  16. package/dist/image-source.d.ts +11 -0
  17. package/dist/image-source.d.ts.map +1 -1
  18. package/dist/image-source.js +20 -0
  19. package/dist/image-source.js.map +1 -1
  20. package/dist/index.d.ts +3 -3
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +2 -2
  23. package/dist/index.js.map +1 -1
  24. package/dist/layered-canvas.d.ts +7 -1
  25. package/dist/layered-canvas.d.ts.map +1 -1
  26. package/dist/layered-canvas.js +6 -3
  27. package/dist/layered-canvas.js.map +1 -1
  28. package/dist/layered-surface.d.ts +16 -0
  29. package/dist/layered-surface.d.ts.map +1 -1
  30. package/dist/layered-surface.js +59 -14
  31. package/dist/layered-surface.js.map +1 -1
  32. package/dist/recording-target.d.ts +56 -13
  33. package/dist/recording-target.d.ts.map +1 -1
  34. package/dist/recording-target.js +229 -38
  35. package/dist/recording-target.js.map +1 -1
  36. package/dist/render-worker.js +19 -7
  37. package/dist/render-worker.js.map +1 -1
  38. package/dist/replay-codec.d.ts +56 -0
  39. package/dist/replay-codec.d.ts.map +1 -0
  40. package/dist/replay-codec.js +543 -0
  41. package/dist/replay-codec.js.map +1 -0
  42. package/dist/tile-compositor.d.ts +52 -1
  43. package/dist/tile-compositor.d.ts.map +1 -1
  44. package/dist/tile-compositor.js +82 -5
  45. package/dist/tile-compositor.js.map +1 -1
  46. package/dist/webgl2-curve.d.ts +12 -2
  47. package/dist/webgl2-curve.d.ts.map +1 -1
  48. package/dist/webgl2-curve.js +168 -40
  49. package/dist/webgl2-curve.js.map +1 -1
  50. package/dist/webgl2-ellipse.d.ts +1 -29
  51. package/dist/webgl2-ellipse.d.ts.map +1 -1
  52. package/dist/webgl2-ellipse.js +35 -22
  53. package/dist/webgl2-ellipse.js.map +1 -1
  54. package/dist/webgl2-msdf-text.d.ts +15 -2
  55. package/dist/webgl2-msdf-text.d.ts.map +1 -1
  56. package/dist/webgl2-msdf-text.js +101 -9
  57. package/dist/webgl2-msdf-text.js.map +1 -1
  58. package/dist/webgl2-rect-batch.d.ts +81 -0
  59. package/dist/webgl2-rect-batch.d.ts.map +1 -0
  60. package/dist/webgl2-rect-batch.js +201 -0
  61. package/dist/webgl2-rect-batch.js.map +1 -0
  62. package/dist/webgl2-stroke.d.ts +14 -35
  63. package/dist/webgl2-stroke.d.ts.map +1 -1
  64. package/dist/webgl2-stroke.js +27 -30
  65. package/dist/webgl2-stroke.js.map +1 -1
  66. package/dist/webgl2-target.d.ts +95 -13
  67. package/dist/webgl2-target.d.ts.map +1 -1
  68. package/dist/webgl2-target.js +423 -230
  69. package/dist/webgl2-target.js.map +1 -1
  70. package/package.json +4 -3
@@ -2,7 +2,7 @@
2
2
  import { LruCache, installBuiltinRenderers, renderScene } from "@oh-just-another/renderer-core";
3
3
  import { registerBundledFonts } from "@oh-just-another/fonts";
4
4
  import { Canvas2DTarget } from "./canvas-target.js";
5
- import { replayCommands } from "./recording-target.js";
5
+ import { replayPackedFrame } from "./replay-codec.js";
6
6
  import { OFFSCREEN_IMAGE_CACHE_CAP } from "./constants.js";
7
7
  const state = {
8
8
  canvas: null,
@@ -77,17 +77,29 @@ const snapshot = (scene) => {
77
77
  post({ type: "frame-done", bitmap }, [bitmap]);
78
78
  };
79
79
  /**
80
- * Replay a serialised RecordingTarget command stream onto the owned
80
+ * Replay a packed RecordingTarget command stream onto the owned
81
81
  * OffscreenCanvas. Used by the LayeredSurface "offscreen" backend: the
82
- * main thread captures every RenderTarget call into a buffer and ships
83
- * it here per frame; the worker replays.
82
+ * main thread captures every RenderTarget call into a buffer, packs it
83
+ * via `packReplayFrame`, and ships it here per frame (numeric stream in
84
+ * the transfer list, bitmaps cloned alongside); the worker replays.
84
85
  */
85
- const replay = (commands) => {
86
+ const replay = (msg) => {
86
87
  if (!state.target) {
87
88
  post({ type: "error", message: "Worker not initialised" });
88
89
  return;
89
90
  }
90
- replayCommands(state.target, commands, state.images);
91
+ // Register this frame's bitmaps BEFORE replaying so the stream's
92
+ // drawImage id references resolve. These are worker-owned clones —
93
+ // the LRU's evict hook closes them.
94
+ for (const { id, bitmap } of msg.bitmaps) {
95
+ // A re-defined id (re-captured video frame) replaces the stored clone;
96
+ // close the old one — LruCache.set does not fire onEvict on overwrite.
97
+ const prev = state.images.get(id);
98
+ if (prev && prev !== bitmap)
99
+ prev.close();
100
+ state.images.set(id, bitmap);
101
+ }
102
+ replayPackedFrame(state.target, msg.buffer, msg.strings, state.images);
91
103
  };
92
104
  self.addEventListener("message", (ev) => {
93
105
  const msg = ev.data;
@@ -105,7 +117,7 @@ self.addEventListener("message", (ev) => {
105
117
  snapshot(msg.scene);
106
118
  break;
107
119
  case "replay":
108
- replay(msg.commands);
120
+ replay(msg);
109
121
  break;
110
122
  case "frame":
111
123
  // Patch-stream frames are not implemented; the protocol is
@@ -1 +1 @@
1
- {"version":3,"file":"render-worker.js","sourceRoot":"","sources":["../src/render-worker.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"render-worker.js","sourceRoot":"","sources":["../src/render-worker.ts"],"names":[],"mappings":"AAAA,iCAAiC;AACjC,OAAO,EAAE,QAAQ,EAAE,uBAAuB,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAGhG,OAAO,EAAE,oBAAoB,EAAkB,MAAM,wBAAwB,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAA4B,MAAM,mBAAmB,CAAC;AAChF,OAAO,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AA6B3D,MAAM,KAAK,GAAgB;IACzB,MAAM,EAAE,IAAI;IACZ,MAAM,EAAE,IAAI;IACZ,GAAG,EAAE,CAAC;IACN,MAAM,EAAE,IAAI,QAAQ,CAAsB,yBAAyB,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE;QACnF,MAAM,CAAC,KAAK,EAAE,CAAC;IACjB,CAAC,CAAC;CACH,CAAC;AAEF,IAAI,kBAAkB,GAAG,KAAK,CAAC;AAE/B,MAAM,eAAe,GAAG,GAAS,EAAE;IACjC,IAAI,kBAAkB;QAAE,OAAO;IAC/B,uBAAuB,EAAE,CAAC;IAC1B,kBAAkB,GAAG,IAAI,CAAC;AAC5B,CAAC,CAAC;AAEF,MAAM,IAAI,GAAG,CAAC,GAAyB,EAAE,QAAyB,EAAQ,EAAE;IAC1E,IAAI,QAAQ,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACnC,IAA8C,CAAC,WAAW,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IAC7E,CAAC;SAAM,CAAC;QACL,IAA8C,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACnE,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,IAAI,GAAG,CAAC,MAAuB,EAAE,KAAa,EAAE,MAAc,EAAE,GAAW,EAAQ,EAAE;IACzF,2EAA2E;IAC3E,2EAA2E;IAC3E,wCAAwC;IACxC,KAAK,oBAAoB,CAAC,IAA4B,CAAC,CAAC;IACxD,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC;IACtB,KAAK,CAAC,GAAG,GAAG,GAAG,CAAC;IAChB,kEAAkE;IAClE,6DAA6D;IAC7D,uCAAuC;IACvC,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC,CAAC;IACpD,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC;IACtD,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;IACpC,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;IACpE,GAAG,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACvC,KAAK,CAAC,MAAM,GAAG,IAAI,cAAc,CAAC,GAA0C,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC;IAClG,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;AAC1B,CAAC,CAAC;AAEF,MAAM,MAAM,GAAG,CAAC,KAAa,EAAE,MAAc,EAAQ,EAAE;IACrD,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM;QAAE,OAAO;IAC3C,KAAK,CAAC,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;IAChE,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;IAClE,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;IAC1C,IAAI,CAAC,GAAG;QAAE,OAAO;IACjB,GAAG,CAAC,YAAY,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACnD,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;AAChD,CAAC,CAAC;AAEF,MAAM,QAAQ,GAAG,CAAC,KAAY,EAAQ,EAAE;IACtC,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;QACnC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,wBAAwB,EAAE,CAAC,CAAC;QAC3D,OAAO;IACT,CAAC;IACD,eAAe,EAAE,CAAC;IAClB,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;IAC1C,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;QACjB,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,wBAAwB,EAAE,CAAC,CAAC;QAC3D,OAAO;IACT,CAAC;IACD,GAAG,CAAC,IAAI,EAAE,CAAC;IACX,GAAG,CAAC,YAAY,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACnD,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACvE,WAAW,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACjC,GAAG,CAAC,OAAO,EAAE,CAAC;IACd,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,qBAAqB,EAAE,CAAC;IACpD,IAAI,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;AACjD,CAAC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,GAAG,CAAC,GAAwB,EAAQ,EAAE;IAChD,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;QAClB,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,wBAAwB,EAAE,CAAC,CAAC;QAC3D,OAAO;IACT,CAAC;IACD,iEAAiE;IACjE,mEAAmE;IACnE,oCAAoC;IACpC,KAAK,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;QACzC,uEAAuE;QACvE,uEAAuE;QACvE,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAClC,IAAI,IAAI,IAAI,IAAI,KAAK,MAAM;YAAE,IAAI,CAAC,KAAK,EAAE,CAAC;QAC1C,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;IAC/B,CAAC;IACD,iBAAiB,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;AACzE,CAAC,CAAC;AAID,IAA8C,CAAC,gBAAgB,CAC9D,SAAS,EACT,CAAC,EAAgC,EAAE,EAAE;IACnC,MAAM,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC;IACpB,IAAI,CAAC;QACH,QAAQ,GAAG,CAAC,IAAI,EAAE,CAAC;YACjB,KAAK,MAAM;gBACT,IAAI,CAAC,GAAG,CAAC,MAAyB,EAAE,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;gBACpE,MAAM;YACR,KAAK,QAAQ;gBACX,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;gBAC9B,MAAM;YACR,KAAK,UAAU;gBACb,8DAA8D;gBAC9D,+CAA+C;gBAC/C,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;gBACpB,MAAM;YACR,KAAK,QAAQ;gBACX,MAAM,CAAC,GAAG,CAAC,CAAC;gBACZ,MAAM;YACR,KAAK,OAAO;gBACV,2DAA2D;gBAC3D,6DAA6D;gBAC7D,oBAAoB;gBACpB,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,qCAAqC,EAAE,CAAC,CAAC;gBACxE,MAAM;QACV,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC;YACH,IAAI,EAAE,OAAO;YACb,OAAO,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;SAC1D,CAAC,CAAC;IACL,CAAC;AACH,CAAC,CACF,CAAC"}
@@ -0,0 +1,56 @@
1
+ import { LruCache, type RenderTarget } from "@oh-just-another/renderer-core";
2
+ import type { RenderCommand } from "./recording-target.js";
3
+ /** One `defineImage` payload carried alongside the numeric stream. */
4
+ export interface PackedFrameBitmap {
5
+ readonly id: number;
6
+ readonly bitmap: ImageBitmap;
7
+ }
8
+ /**
9
+ * Result of {@link packReplayFrame}: `buffer` is the transferable numeric
10
+ * stream, `strings` the per-frame deduplicated string table it indexes
11
+ * into, `bitmaps` the `defineImage` payloads (worker registers them BEFORE
12
+ * replaying the stream).
13
+ */
14
+ export interface PackedReplayFrame {
15
+ readonly buffer: ArrayBuffer;
16
+ readonly strings: readonly string[];
17
+ readonly bitmaps: readonly PackedFrameBitmap[];
18
+ }
19
+ /**
20
+ * postMessage shape the offscreen surface posts per changed layer and the
21
+ * render worker consumes. `buffer` goes in the transfer list; `strings`
22
+ * are cloned (cheap — deduplicated); `bitmaps` are CLONED, never
23
+ * transferred — see {@link packReplayFrame}.
24
+ */
25
+ export interface PackedReplayMessage {
26
+ readonly type: "replay";
27
+ readonly buffer: ArrayBuffer;
28
+ readonly strings: readonly string[];
29
+ readonly bitmaps: readonly PackedFrameBitmap[];
30
+ }
31
+ /**
32
+ * Flatten a flushed {@link RenderCommand} buffer into a transferable packed
33
+ * frame: one Float64 word per opcode / argument, strings deduplicated into
34
+ * a side table, enums and presence flags as small ints.
35
+ *
36
+ * `defineImage` commands emit nothing into the numeric stream; their
37
+ * `{ id, bitmap }` payloads are collected into `bitmaps` instead. The
38
+ * caller must post them WITHOUT a transfer-list entry so `postMessage`
39
+ * clones the pixels: the recorder's intern LRU still owns the source
40
+ * bitmap and will keep drawing it on later frames (GIF / video), so
41
+ * transferring (detaching) it would break the main thread's copy.
42
+ */
43
+ export declare const packReplayFrame: (commands: readonly RenderCommand[]) => PackedReplayFrame;
44
+ /**
45
+ * Decode a packed frame and dispatch each command straight onto `target`
46
+ * in one pass — no intermediate {@link RenderCommand} objects.
47
+ *
48
+ * `images` is the worker's persistent id → bitmap LRU (mirrors the
49
+ * recorder's same-capacity intern LRU): the caller must have registered
50
+ * the frame's side bitmaps into it BEFORE calling. Semantics match
51
+ * {@link replayCommands}: `resize` is a no-op (the worker owns the canvas
52
+ * size via its own `resize` message) and a `drawImage` whose id misses the
53
+ * cache is skipped rather than thrown.
54
+ */
55
+ export declare const replayPackedFrame: (target: RenderTarget, buffer: ArrayBuffer, strings: readonly string[], images?: LruCache<number, ImageBitmap>) => void;
56
+ //# sourceMappingURL=replay-codec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay-codec.d.ts","sourceRoot":"","sources":["../src/replay-codec.ts"],"names":[],"mappings":"AACA,OAAO,EACL,QAAQ,EAIR,KAAK,YAAY,EAGlB,MAAM,gCAAgC,CAAC;AAExC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AA0G3D,sEAAsE;AACtE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;CAC9B;AAED;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,SAAS,iBAAiB,EAAE,CAAC;CAChD;AAED;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,SAAS,iBAAiB,EAAE,CAAC;CAChD;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,eAAe,GAAI,UAAU,SAAS,aAAa,EAAE,KAAG,iBAgOpE,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,GAC5B,QAAQ,YAAY,EACpB,QAAQ,WAAW,EACnB,SAAS,SAAS,MAAM,EAAE,EAC1B,SAAQ,QAAQ,CAAC,MAAM,EAAE,WAAW,CAA2C,KAC9E,IAuLF,CAAC"}
@@ -0,0 +1,543 @@
1
+ import { LruCache, } from "@oh-just-another/renderer-core";
2
+ import { OFFSCREEN_IMAGE_CACHE_CAP } from "./constants.js";
3
+ /**
4
+ * Packed-frame codec for the offscreen backend's per-frame worker hop.
5
+ *
6
+ * `structuredClone`-ing an array of {@link RenderCommand} objects costs
7
+ * ~1.6 ms for a ~4.5k-command frame (see `tests/offscreen-transfer.bench.ts`)
8
+ * — every object, key, and string is walked and copied. This codec flattens
9
+ * the stream into one transferable `ArrayBuffer` of Float64 words (opcode +
10
+ * args per command) plus a per-frame deduplicated string table, so
11
+ * `postMessage` transfers the numeric bulk for free and only clones a small
12
+ * string array. `ImageBitmap` payloads (`defineImage`) travel in a side
13
+ * array — see {@link packReplayFrame}.
14
+ */
15
+ /**
16
+ * Opcodes for the packed numeric stream — one per {@link RenderCommand}
17
+ * variant except `defineImage`, which travels in the side bitmap array and
18
+ * emits nothing here. Wire format only: values are arbitrary but must stay
19
+ * in sync between {@link packReplayFrame} and {@link replayPackedFrame}
20
+ * (same-package protocol; both sides always ship together).
21
+ */
22
+ const OP_SET_FILL = 0;
23
+ const OP_SET_STROKE = 1;
24
+ const OP_SET_STROKE_WIDTH = 2;
25
+ const OP_SET_OPACITY = 3;
26
+ const OP_SET_LINE_CAP = 4;
27
+ const OP_SET_LINE_JOIN = 5;
28
+ const OP_SET_DASH_ARRAY = 6;
29
+ const OP_SET_FONT = 7;
30
+ const OP_SET_TEXT_ALIGN = 8;
31
+ const OP_SET_TEXT_BASELINE = 9;
32
+ const OP_SAVE = 10;
33
+ const OP_RESTORE = 11;
34
+ const OP_TRANSLATE = 12;
35
+ const OP_ROTATE = 13;
36
+ const OP_SCALE = 14;
37
+ const OP_SET_TRANSFORM = 15;
38
+ const OP_RESET_TRANSFORM = 16;
39
+ const OP_BEGIN_PATH = 17;
40
+ const OP_CLOSE_PATH = 18;
41
+ const OP_MOVE_TO = 19;
42
+ const OP_LINE_TO = 20;
43
+ const OP_QUADRATIC_CURVE_TO = 21;
44
+ const OP_BEZIER_CURVE_TO = 22;
45
+ const OP_RECT = 23;
46
+ const OP_ELLIPSE = 24;
47
+ const OP_FILL = 25;
48
+ const OP_STROKE = 26;
49
+ const OP_FILL_TEXT = 27;
50
+ const OP_CLEAR = 28;
51
+ const OP_MARK_DIRTY = 29;
52
+ const OP_RESIZE = 30;
53
+ const OP_DRAW_IMAGE = 31;
54
+ /**
55
+ * String-table index sentinel for a `null` color (`setFill` / `setStroke`
56
+ * accept `Color | null`). Real indices are >= 0.
57
+ */
58
+ const NULL_STRING_INDEX = -1;
59
+ /** `setDashArray(null)` marker written in place of the dash length. */
60
+ const NULL_DASH_LENGTH = -1;
61
+ /**
62
+ * Enum wire codes. Encode side uses the `*_CODE` records, decode side the
63
+ * positional arrays — index === code. Order is wire format; append only.
64
+ */
65
+ const LINE_CAPS = ["butt", "round", "square"];
66
+ const LINE_CAP_CODE = { butt: 0, round: 1, square: 2 };
67
+ const LINE_JOINS = ["miter", "round", "bevel"];
68
+ const LINE_JOIN_CODE = { miter: 0, round: 1, bevel: 2 };
69
+ const TEXT_ALIGNS = ["left", "center", "right"];
70
+ const TEXT_ALIGN_CODE = { left: 0, center: 1, right: 2 };
71
+ const TEXT_BASELINES = ["top", "middle", "bottom"];
72
+ const TEXT_BASELINE_CODE = { top: 0, middle: 1, bottom: 2 };
73
+ /**
74
+ * `fill(rule?)` wire codes: 0 = no rule argument, 1 = "nonzero",
75
+ * 2 = "evenodd".
76
+ */
77
+ const FILL_RULE_CODE = { nonzero: 1, evenodd: 2 };
78
+ /**
79
+ * `setFont` options wire codes. 0 = key absent from the options object;
80
+ * 1 / 2 = the two allowed values. A separate leading flag word (0 / 1)
81
+ * distinguishes "no options argument at all" from an empty options object.
82
+ */
83
+ const FONT_WEIGHT_CODE = { normal: 1, bold: 2 };
84
+ const FONT_STYLE_CODE = { normal: 1, italic: 2 };
85
+ /** Optional-argument presence flags (`fillText` maxWidth, `clear` bounds). */
86
+ const ABSENT = 0;
87
+ const PRESENT = 1;
88
+ /**
89
+ * Initial capacity (in Float64 words) of the packed stream, as a multiple
90
+ * of the command count. Most commands fit in opcode + ≤6 args; the writer
91
+ * doubles on overflow, so this only tunes how often the first frames
92
+ * reallocate.
93
+ */
94
+ const PACK_WORDS_PER_COMMAND = 4;
95
+ /** Floor for the writer's initial capacity so tiny frames don't thrash. */
96
+ const PACK_MIN_CAPACITY = 64;
97
+ /**
98
+ * Flatten a flushed {@link RenderCommand} buffer into a transferable packed
99
+ * frame: one Float64 word per opcode / argument, strings deduplicated into
100
+ * a side table, enums and presence flags as small ints.
101
+ *
102
+ * `defineImage` commands emit nothing into the numeric stream; their
103
+ * `{ id, bitmap }` payloads are collected into `bitmaps` instead. The
104
+ * caller must post them WITHOUT a transfer-list entry so `postMessage`
105
+ * clones the pixels: the recorder's intern LRU still owns the source
106
+ * bitmap and will keep drawing it on later frames (GIF / video), so
107
+ * transferring (detaching) it would break the main thread's copy.
108
+ */
109
+ export const packReplayFrame = (commands) => {
110
+ let words = new Float64Array(Math.max(PACK_MIN_CAPACITY, commands.length * PACK_WORDS_PER_COMMAND));
111
+ let used = 0;
112
+ const push = (v) => {
113
+ if (used === words.length) {
114
+ const grown = new Float64Array(words.length * 2);
115
+ grown.set(words);
116
+ words = grown;
117
+ }
118
+ words[used++] = v;
119
+ };
120
+ const strings = [];
121
+ const stringIndex = new Map();
122
+ /** Dedup a string through the per-frame table, returning its index. */
123
+ const intern = (s) => {
124
+ let idx = stringIndex.get(s);
125
+ if (idx === undefined) {
126
+ idx = strings.length;
127
+ strings.push(s);
128
+ stringIndex.set(s, idx);
129
+ }
130
+ return idx;
131
+ };
132
+ const bitmaps = [];
133
+ for (const cmd of commands) {
134
+ switch (cmd.k) {
135
+ case "setFill":
136
+ push(OP_SET_FILL);
137
+ push(cmd.color === null ? NULL_STRING_INDEX : intern(cmd.color));
138
+ break;
139
+ case "setStroke":
140
+ push(OP_SET_STROKE);
141
+ push(cmd.color === null ? NULL_STRING_INDEX : intern(cmd.color));
142
+ break;
143
+ case "setStrokeWidth":
144
+ push(OP_SET_STROKE_WIDTH);
145
+ push(cmd.w);
146
+ break;
147
+ case "setOpacity":
148
+ push(OP_SET_OPACITY);
149
+ push(cmd.a);
150
+ break;
151
+ case "setLineCap":
152
+ push(OP_SET_LINE_CAP);
153
+ push(LINE_CAP_CODE[cmd.cap]);
154
+ break;
155
+ case "setLineJoin":
156
+ push(OP_SET_LINE_JOIN);
157
+ push(LINE_JOIN_CODE[cmd.join]);
158
+ break;
159
+ case "setDashArray":
160
+ push(OP_SET_DASH_ARRAY);
161
+ if (cmd.dash === null) {
162
+ push(NULL_DASH_LENGTH);
163
+ }
164
+ else {
165
+ push(cmd.dash.length);
166
+ for (const d of cmd.dash)
167
+ push(d);
168
+ }
169
+ break;
170
+ case "setFont":
171
+ push(OP_SET_FONT);
172
+ push(intern(cmd.family));
173
+ push(cmd.size);
174
+ if (cmd.options === undefined) {
175
+ push(ABSENT);
176
+ }
177
+ else {
178
+ push(PRESENT);
179
+ push(cmd.options.weight === undefined ? 0 : FONT_WEIGHT_CODE[cmd.options.weight]);
180
+ push(cmd.options.style === undefined ? 0 : FONT_STYLE_CODE[cmd.options.style]);
181
+ }
182
+ break;
183
+ case "setTextAlign":
184
+ push(OP_SET_TEXT_ALIGN);
185
+ push(TEXT_ALIGN_CODE[cmd.align]);
186
+ break;
187
+ case "setTextBaseline":
188
+ push(OP_SET_TEXT_BASELINE);
189
+ push(TEXT_BASELINE_CODE[cmd.baseline]);
190
+ break;
191
+ case "save":
192
+ push(OP_SAVE);
193
+ break;
194
+ case "restore":
195
+ push(OP_RESTORE);
196
+ break;
197
+ case "translate":
198
+ push(OP_TRANSLATE);
199
+ push(cmd.x);
200
+ push(cmd.y);
201
+ break;
202
+ case "rotate":
203
+ push(OP_ROTATE);
204
+ push(cmd.r);
205
+ break;
206
+ case "scale":
207
+ push(OP_SCALE);
208
+ push(cmd.sx);
209
+ push(cmd.sy);
210
+ break;
211
+ case "setTransform":
212
+ push(OP_SET_TRANSFORM);
213
+ push(cmd.t.a);
214
+ push(cmd.t.b);
215
+ push(cmd.t.c);
216
+ push(cmd.t.d);
217
+ push(cmd.t.e);
218
+ push(cmd.t.f);
219
+ break;
220
+ case "resetTransform":
221
+ push(OP_RESET_TRANSFORM);
222
+ break;
223
+ case "beginPath":
224
+ push(OP_BEGIN_PATH);
225
+ break;
226
+ case "closePath":
227
+ push(OP_CLOSE_PATH);
228
+ break;
229
+ case "moveTo":
230
+ push(OP_MOVE_TO);
231
+ push(cmd.x);
232
+ push(cmd.y);
233
+ break;
234
+ case "lineTo":
235
+ push(OP_LINE_TO);
236
+ push(cmd.x);
237
+ push(cmd.y);
238
+ break;
239
+ case "quadraticCurveTo":
240
+ push(OP_QUADRATIC_CURVE_TO);
241
+ push(cmd.cx);
242
+ push(cmd.cy);
243
+ push(cmd.x);
244
+ push(cmd.y);
245
+ break;
246
+ case "bezierCurveTo":
247
+ push(OP_BEZIER_CURVE_TO);
248
+ push(cmd.c1x);
249
+ push(cmd.c1y);
250
+ push(cmd.c2x);
251
+ push(cmd.c2y);
252
+ push(cmd.x);
253
+ push(cmd.y);
254
+ break;
255
+ case "rect":
256
+ push(OP_RECT);
257
+ push(cmd.x);
258
+ push(cmd.y);
259
+ push(cmd.w);
260
+ push(cmd.h);
261
+ break;
262
+ case "ellipse":
263
+ push(OP_ELLIPSE);
264
+ push(cmd.cx);
265
+ push(cmd.cy);
266
+ push(cmd.rx);
267
+ push(cmd.ry);
268
+ break;
269
+ case "fill":
270
+ push(OP_FILL);
271
+ push(cmd.rule === undefined ? ABSENT : FILL_RULE_CODE[cmd.rule]);
272
+ break;
273
+ case "stroke":
274
+ push(OP_STROKE);
275
+ break;
276
+ case "fillText":
277
+ push(OP_FILL_TEXT);
278
+ push(intern(cmd.text));
279
+ push(cmd.x);
280
+ push(cmd.y);
281
+ if (cmd.maxWidth === undefined) {
282
+ push(ABSENT);
283
+ }
284
+ else {
285
+ push(PRESENT);
286
+ push(cmd.maxWidth);
287
+ }
288
+ break;
289
+ case "clear":
290
+ push(OP_CLEAR);
291
+ if (cmd.bounds === undefined) {
292
+ push(ABSENT);
293
+ }
294
+ else {
295
+ push(PRESENT);
296
+ push(cmd.bounds.x);
297
+ push(cmd.bounds.y);
298
+ push(cmd.bounds.width);
299
+ push(cmd.bounds.height);
300
+ }
301
+ break;
302
+ case "markDirty":
303
+ push(OP_MARK_DIRTY);
304
+ push(cmd.bounds.x);
305
+ push(cmd.bounds.y);
306
+ push(cmd.bounds.width);
307
+ push(cmd.bounds.height);
308
+ break;
309
+ case "resize":
310
+ push(OP_RESIZE);
311
+ push(cmd.w);
312
+ push(cmd.h);
313
+ break;
314
+ case "defineImage":
315
+ // Not packed: the bitmap travels beside the numeric stream. The
316
+ // worker registers all side bitmaps before replaying, so the
317
+ // stream's `drawImage` id references always resolve.
318
+ bitmaps.push({ id: cmd.id, bitmap: cmd.bitmap });
319
+ break;
320
+ case "drawImage":
321
+ push(OP_DRAW_IMAGE);
322
+ push(cmd.id);
323
+ push(cmd.dx);
324
+ push(cmd.dy);
325
+ push(cmd.dw);
326
+ push(cmd.dh);
327
+ break;
328
+ }
329
+ }
330
+ // Exact-size copy so the transferred buffer carries no slack capacity.
331
+ return { buffer: words.slice(0, used).buffer, strings, bitmaps };
332
+ };
333
+ /**
334
+ * Decode a packed frame and dispatch each command straight onto `target`
335
+ * in one pass — no intermediate {@link RenderCommand} objects.
336
+ *
337
+ * `images` is the worker's persistent id → bitmap LRU (mirrors the
338
+ * recorder's same-capacity intern LRU): the caller must have registered
339
+ * the frame's side bitmaps into it BEFORE calling. Semantics match
340
+ * {@link replayCommands}: `resize` is a no-op (the worker owns the canvas
341
+ * size via its own `resize` message) and a `drawImage` whose id misses the
342
+ * cache is skipped rather than thrown.
343
+ */
344
+ export const replayPackedFrame = (target, buffer, strings, images = new LruCache(OFFSCREEN_IMAGE_CACHE_CAP)) => {
345
+ const words = new Float64Array(buffer);
346
+ let i = 0;
347
+ const next = () => {
348
+ const v = words[i++];
349
+ if (v === undefined)
350
+ throw new Error("replayPackedFrame: truncated stream");
351
+ return v;
352
+ };
353
+ const str = (idx) => {
354
+ const s = strings[idx];
355
+ if (s === undefined)
356
+ throw new Error(`replayPackedFrame: bad string index ${String(idx)}`);
357
+ return s;
358
+ };
359
+ const at = (table, code) => {
360
+ const v = table[code];
361
+ if (v === undefined)
362
+ throw new Error(`replayPackedFrame: bad enum code ${String(code)}`);
363
+ return v;
364
+ };
365
+ while (i < words.length) {
366
+ const op = next();
367
+ switch (op) {
368
+ case OP_SET_FILL: {
369
+ const idx = next();
370
+ target.setFill(idx === NULL_STRING_INDEX ? null : str(idx));
371
+ break;
372
+ }
373
+ case OP_SET_STROKE: {
374
+ const idx = next();
375
+ target.setStroke(idx === NULL_STRING_INDEX ? null : str(idx));
376
+ break;
377
+ }
378
+ case OP_SET_STROKE_WIDTH:
379
+ target.setStrokeWidth(next());
380
+ break;
381
+ case OP_SET_OPACITY:
382
+ target.setOpacity(next());
383
+ break;
384
+ case OP_SET_LINE_CAP:
385
+ target.setLineCap(at(LINE_CAPS, next()));
386
+ break;
387
+ case OP_SET_LINE_JOIN:
388
+ target.setLineJoin(at(LINE_JOINS, next()));
389
+ break;
390
+ case OP_SET_DASH_ARRAY: {
391
+ const n = next();
392
+ if (n === NULL_DASH_LENGTH) {
393
+ target.setDashArray(null);
394
+ }
395
+ else {
396
+ const dash = [];
397
+ for (let d = 0; d < n; d++)
398
+ dash.push(next());
399
+ target.setDashArray(dash);
400
+ }
401
+ break;
402
+ }
403
+ case OP_SET_FONT: {
404
+ const family = str(next());
405
+ const size = next();
406
+ if (next() === ABSENT) {
407
+ target.setFont(family, size);
408
+ break;
409
+ }
410
+ const weight = next();
411
+ const style = next();
412
+ const options = {};
413
+ if (weight === FONT_WEIGHT_CODE.normal)
414
+ options.weight = "normal";
415
+ else if (weight === FONT_WEIGHT_CODE.bold)
416
+ options.weight = "bold";
417
+ if (style === FONT_STYLE_CODE.normal)
418
+ options.style = "normal";
419
+ else if (style === FONT_STYLE_CODE.italic)
420
+ options.style = "italic";
421
+ target.setFont(family, size, options);
422
+ break;
423
+ }
424
+ case OP_SET_TEXT_ALIGN:
425
+ target.setTextAlign(at(TEXT_ALIGNS, next()));
426
+ break;
427
+ case OP_SET_TEXT_BASELINE:
428
+ target.setTextBaseline(at(TEXT_BASELINES, next()));
429
+ break;
430
+ case OP_SAVE:
431
+ target.save();
432
+ break;
433
+ case OP_RESTORE:
434
+ target.restore();
435
+ break;
436
+ case OP_TRANSLATE:
437
+ target.translate(next(), next());
438
+ break;
439
+ case OP_ROTATE:
440
+ target.rotate(next());
441
+ break;
442
+ case OP_SCALE:
443
+ target.scale(next(), next());
444
+ break;
445
+ case OP_SET_TRANSFORM:
446
+ target.setTransform({
447
+ a: next(),
448
+ b: next(),
449
+ c: next(),
450
+ d: next(),
451
+ e: next(),
452
+ f: next(),
453
+ });
454
+ break;
455
+ case OP_RESET_TRANSFORM:
456
+ target.resetTransform();
457
+ break;
458
+ case OP_BEGIN_PATH:
459
+ target.beginPath();
460
+ break;
461
+ case OP_CLOSE_PATH:
462
+ target.closePath();
463
+ break;
464
+ case OP_MOVE_TO:
465
+ target.moveTo(next(), next());
466
+ break;
467
+ case OP_LINE_TO:
468
+ target.lineTo(next(), next());
469
+ break;
470
+ case OP_QUADRATIC_CURVE_TO:
471
+ target.quadraticCurveTo(next(), next(), next(), next());
472
+ break;
473
+ case OP_BEZIER_CURVE_TO:
474
+ target.bezierCurveTo(next(), next(), next(), next(), next(), next());
475
+ break;
476
+ case OP_RECT:
477
+ target.rect(next(), next(), next(), next());
478
+ break;
479
+ case OP_ELLIPSE:
480
+ target.ellipse(next(), next(), next(), next());
481
+ break;
482
+ case OP_FILL: {
483
+ const code = next();
484
+ if (code === FILL_RULE_CODE.nonzero)
485
+ target.fill("nonzero");
486
+ else if (code === FILL_RULE_CODE.evenodd)
487
+ target.fill("evenodd");
488
+ else
489
+ target.fill();
490
+ break;
491
+ }
492
+ case OP_STROKE:
493
+ target.stroke();
494
+ break;
495
+ case OP_FILL_TEXT: {
496
+ const text = str(next());
497
+ const x = next();
498
+ const y = next();
499
+ if (next() === PRESENT)
500
+ target.fillText(text, x, y, next());
501
+ else
502
+ target.fillText(text, x, y);
503
+ break;
504
+ }
505
+ case OP_CLEAR:
506
+ if (next() === PRESENT) {
507
+ const bounds = { x: next(), y: next(), width: next(), height: next() };
508
+ target.clear(bounds);
509
+ }
510
+ else {
511
+ target.clear();
512
+ }
513
+ break;
514
+ case OP_MARK_DIRTY:
515
+ target.markDirty?.({ x: next(), y: next(), width: next(), height: next() });
516
+ break;
517
+ case OP_RESIZE:
518
+ // No-op for replay — the worker owns the canvas size and resizes
519
+ // via its own `resize` message, not via the command stream. Still
520
+ // consume the args to stay in sync with the stream.
521
+ next();
522
+ next();
523
+ break;
524
+ case OP_DRAW_IMAGE: {
525
+ // `get` bumps recency so the worker LRU evicts in lockstep with
526
+ // the recorder's. A miss means an out-of-sync stream — skip
527
+ // rather than throw (matches the non-drawable skip on record).
528
+ const id = next();
529
+ const dx = next();
530
+ const dy = next();
531
+ const dw = next();
532
+ const dh = next();
533
+ const bitmap = images.get(id);
534
+ if (bitmap)
535
+ target.drawImage(bitmap, dx, dy, dw, dh);
536
+ break;
537
+ }
538
+ default:
539
+ throw new Error(`replayPackedFrame: unknown opcode ${String(op)}`);
540
+ }
541
+ }
542
+ };
543
+ //# sourceMappingURL=replay-codec.js.map