web-annotation-renderer 0.3.0 → 0.5.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 (96) hide show
  1. package/dist/index.cjs +1 -1
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.js +49 -42
  4. package/dist/index.js.map +1 -1
  5. package/dist/index10.cjs +1 -1
  6. package/dist/index10.cjs.map +1 -1
  7. package/dist/index10.js +172 -17
  8. package/dist/index10.js.map +1 -1
  9. package/dist/index11.cjs +1 -1
  10. package/dist/index11.cjs.map +1 -1
  11. package/dist/index11.js +13 -20
  12. package/dist/index11.js.map +1 -1
  13. package/dist/index12.cjs +1 -1
  14. package/dist/index12.cjs.map +1 -1
  15. package/dist/index12.js +167 -124
  16. package/dist/index12.js.map +1 -1
  17. package/dist/index13.cjs +1 -1
  18. package/dist/index13.cjs.map +1 -1
  19. package/dist/index13.js +29 -198
  20. package/dist/index13.js.map +1 -1
  21. package/dist/index14.cjs +1 -1
  22. package/dist/index14.cjs.map +1 -1
  23. package/dist/index14.js +78 -15
  24. package/dist/index14.js.map +1 -1
  25. package/dist/index15.cjs +1 -1
  26. package/dist/index15.cjs.map +1 -1
  27. package/dist/index15.js +115 -120
  28. package/dist/index15.js.map +1 -1
  29. package/dist/index16.cjs +1 -1
  30. package/dist/index16.cjs.map +1 -1
  31. package/dist/index16.js +100 -212
  32. package/dist/index16.js.map +1 -1
  33. package/dist/index17.cjs +1 -1
  34. package/dist/index17.cjs.map +1 -1
  35. package/dist/index17.js +55 -37
  36. package/dist/index17.js.map +1 -1
  37. package/dist/index18.cjs +1 -1
  38. package/dist/index18.cjs.map +1 -1
  39. package/dist/index18.js +139 -35
  40. package/dist/index18.js.map +1 -1
  41. package/dist/index19.cjs +1 -1
  42. package/dist/index19.cjs.map +1 -1
  43. package/dist/index19.js +37 -37
  44. package/dist/index19.js.map +1 -1
  45. package/dist/index2.cjs +1 -1
  46. package/dist/index2.cjs.map +1 -1
  47. package/dist/index2.js +95 -73
  48. package/dist/index2.js.map +1 -1
  49. package/dist/index20.cjs +1 -1
  50. package/dist/index20.cjs.map +1 -1
  51. package/dist/index20.js +29 -39
  52. package/dist/index20.js.map +1 -1
  53. package/dist/index21.cjs +1 -1
  54. package/dist/index21.cjs.map +1 -1
  55. package/dist/index21.js +38 -32
  56. package/dist/index21.js.map +1 -1
  57. package/dist/index22.cjs +1 -1
  58. package/dist/index22.cjs.map +1 -1
  59. package/dist/index22.js +22 -5
  60. package/dist/index22.js.map +1 -1
  61. package/dist/index23.cjs +2 -0
  62. package/dist/index23.cjs.map +1 -0
  63. package/dist/index23.js +8 -0
  64. package/dist/index23.js.map +1 -0
  65. package/dist/index24.cjs +2 -0
  66. package/dist/index24.cjs.map +1 -0
  67. package/dist/index24.js +8 -0
  68. package/dist/index24.js.map +1 -0
  69. package/dist/index3.cjs +1 -1
  70. package/dist/index3.cjs.map +1 -1
  71. package/dist/index3.js +1 -1
  72. package/dist/index4.cjs +1 -1
  73. package/dist/index4.cjs.map +1 -1
  74. package/dist/index4.js +72 -71
  75. package/dist/index4.js.map +1 -1
  76. package/dist/index5.cjs +1 -1
  77. package/dist/index5.cjs.map +1 -1
  78. package/dist/index5.js +217 -65
  79. package/dist/index5.js.map +1 -1
  80. package/dist/index6.cjs +1 -1
  81. package/dist/index6.cjs.map +1 -1
  82. package/dist/index6.js +60 -114
  83. package/dist/index6.js.map +1 -1
  84. package/dist/index7.cjs +1 -1
  85. package/dist/index7.cjs.map +1 -1
  86. package/dist/index7.js +19 -91
  87. package/dist/index7.js.map +1 -1
  88. package/dist/index8.cjs +1 -1
  89. package/dist/index8.cjs.map +1 -1
  90. package/dist/index8.js +19 -105
  91. package/dist/index8.js.map +1 -1
  92. package/dist/index9.cjs +1 -1
  93. package/dist/index9.cjs.map +1 -1
  94. package/dist/index9.js +123 -98
  95. package/dist/index9.js.map +1 -1
  96. package/package.json +4 -3
package/dist/index5.js CHANGED
@@ -1,105 +1,257 @@
1
- class e {
2
- constructor() {
3
- this.currentTime = 0, this.subscribers = /* @__PURE__ */ new Set(), this.animationFrameId = null, this.isRunning = !1;
1
+ import { deepMerge as k } from "./index6.js";
2
+ import { textToStrokes as _ } from "./index14.js";
3
+ import { highlightToStrokes as b } from "./index13.js";
4
+ const P = {
5
+ pen: {
6
+ jitter: { amplitude: 1, frequency: 0.08 },
7
+ pressure: { taperIn: 0.15, taperOut: 0.2 },
8
+ wobble: { amplitude: 1.5, frequency: 0.05 }
9
+ },
10
+ highlight: {
11
+ color: "rgba(255, 255, 0, 0.3)",
12
+ width: 24,
13
+ lineCap: "butt",
14
+ jitter: { amplitude: 0 }
15
+ },
16
+ text: {
17
+ color: "rgba(220, 20, 60, 1.0)",
18
+ width: 2,
19
+ fontSize: 16,
20
+ lineCap: "round",
21
+ jitter: { amplitude: 0 },
22
+ pressure: { taperIn: 0, taperOut: 0 }
4
23
  }
24
+ };
25
+ class W {
5
26
  /**
6
- * Set timeline position and notify subscribers if changed
27
+ * Create StrokeRenderer instance
7
28
  *
8
- * @param {number} timestamp - Timeline position in seconds
9
- * @returns {void}
29
+ * @param {HTMLCanvasElement} canvas - Canvas element for rendering
30
+ * @param {Object} [config={}] - Configuration overrides
31
+ * @param {Object} [config.pen] - Global pen settings
32
+ * @param {Object} [config.highlight] - Highlight type settings
33
+ * @param {Object} [config.text] - Text type settings
10
34
  */
11
- setTime(n) {
12
- n !== this.currentTime && (this.currentTime = n, this.notifySubscribers());
35
+ constructor(t, r = {}) {
36
+ if (!t || !(t instanceof HTMLCanvasElement))
37
+ throw new Error("StrokeRenderer: canvas must be a valid HTMLCanvasElement");
38
+ this.canvas = t, this.ctx = t.getContext("2d"), this.config = k(P, r), this.strokes = [], this.viewport = { width: 0, height: 0 }, this.converters = {
39
+ highlight: b,
40
+ text: _
41
+ };
13
42
  }
14
43
  /**
15
- * Get current timeline position
44
+ * Register a custom converter for a new annotation type
16
45
  *
17
- * @returns {number} Current timeline position in seconds
46
+ * @param {string} type - Annotation type name
47
+ * @param {Function} converter - Converter function (annotation, style) => strokes[]
18
48
  */
19
- getCurrentTime() {
20
- return this.currentTime;
49
+ registerConverter(t, r) {
50
+ if (typeof r != "function")
51
+ throw new Error("StrokeRenderer.registerConverter: converter must be a function");
52
+ this.converters[t] = r;
21
53
  }
22
54
  /**
23
- * Subscribe to timeline updates
55
+ * Set viewport dimensions and configure canvas
56
+ *
57
+ * Handles high-DPI displays by scaling the canvas buffer.
24
58
  *
25
- * @param {Function} callback - Function to call on timeline updates
26
- * @returns {Function} Unsubscribe function
27
- * @throws {Error} If callback is not a function
59
+ * @param {number} width - Viewport width in CSS pixels
60
+ * @param {number} height - Viewport height in CSS pixels
28
61
  */
29
- subscribe(n) {
30
- if (typeof n != "function")
31
- throw new Error("TimelineSync.subscribe: callback must be a function");
32
- return this.subscribers.add(n), () => this.unsubscribe(n);
62
+ setViewport(t, r) {
63
+ this.viewport = { width: t, height: r };
64
+ const i = window.devicePixelRatio || 1;
65
+ this.canvas.width = t * i, this.canvas.height = r * i, this.canvas.style.width = `${t}px`, this.canvas.style.height = `${r}px`, this.ctx.setTransform(i, 0, 0, i, 0, 0);
33
66
  }
34
67
  /**
35
- * Unsubscribe from timeline updates
68
+ * Set annotations and convert them to strokes
36
69
  *
37
- * @param {Function} callback - Callback function to remove
38
- * @returns {void}
70
+ * Clears existing strokes and converts all annotations.
71
+ *
72
+ * @param {Array} annotations - Array of annotation objects
73
+ * @param {number} [page] - Optional page filter (only convert annotations for this page)
39
74
  */
40
- unsubscribe(n) {
41
- this.subscribers.delete(n);
75
+ setAnnotations(t, r = null) {
76
+ if (!Array.isArray(t)) {
77
+ console.warn("StrokeRenderer.setAnnotations: annotations must be an array"), this.strokes = [];
78
+ return;
79
+ }
80
+ const i = r !== null ? t.filter((e) => e.page === r) : t;
81
+ this.strokes = i.flatMap((e) => {
82
+ const n = this.converters[e.type];
83
+ if (!n)
84
+ return console.warn(`StrokeRenderer: Unknown annotation type "${e.type}"`), [];
85
+ const o = this._resolveStyle(e);
86
+ return n(e, o);
87
+ });
42
88
  }
43
89
  /**
44
- * Start continuous timeline synchronization
90
+ * Set pre-converted strokes directly
45
91
  *
46
- * @param {Function} getTimeFunction - Function that returns current time
47
- * @returns {void}
48
- * @throws {Error} If getTimeFunction is not a function
92
+ * Bypasses conversion, useful when strokes come from external source.
93
+ *
94
+ * @param {Array} strokes - Array of stroke command objects
49
95
  */
50
- startContinuousSync(n) {
51
- if (typeof n != "function")
52
- throw new Error("TimelineSync.startContinuousSync: getTimeFunction must be a function");
53
- if (this.isRunning) {
54
- console.warn("TimelineSync: Continuous sync already running");
96
+ setStrokes(t) {
97
+ if (!Array.isArray(t)) {
98
+ console.warn("StrokeRenderer.setStrokes: strokes must be an array"), this.strokes = [];
55
99
  return;
56
100
  }
57
- this.isRunning = !0;
58
- const i = () => {
59
- if (this.isRunning) {
60
- try {
61
- const r = n();
62
- this.setTime(r);
63
- } catch (r) {
64
- console.error("TimelineSync: Error in continuous sync:", r);
65
- }
66
- this.animationFrameId = requestAnimationFrame(i);
67
- }
68
- };
69
- i();
101
+ this.strokes = t;
70
102
  }
71
103
  /**
72
- * Stop continuous timeline synchronization
104
+ * Render strokes at the given time
105
+ *
106
+ * Clears canvas and draws all visible strokes with appropriate progress.
73
107
  *
74
- * @returns {void}
108
+ * @param {number} time - Current time in seconds
75
109
  */
76
- stopContinuousSync() {
77
- this.isRunning = !1, this.animationFrameId !== null && (cancelAnimationFrame(this.animationFrameId), this.animationFrameId = null);
110
+ render(t) {
111
+ const { ctx: r, viewport: i, strokes: e } = this;
112
+ r.clearRect(0, 0, i.width, i.height);
113
+ for (const n of e) {
114
+ if (t < n.start)
115
+ continue;
116
+ const o = n.end - n.start, h = t - n.start, s = o > 0 ? Math.min(1, h / o) : 1;
117
+ this._drawStroke(n, s);
118
+ }
78
119
  }
79
120
  /**
80
- * Clean up resources and release references
81
- *
82
- * @returns {void}
121
+ * Clear the canvas
122
+ */
123
+ clear() {
124
+ this.ctx.clearRect(0, 0, this.viewport.width, this.viewport.height);
125
+ }
126
+ /**
127
+ * Destroy the renderer and release resources
83
128
  */
84
129
  destroy() {
85
- this.stopContinuousSync(), this.subscribers.clear(), this.currentTime = 0;
130
+ this.strokes = [], this.ctx = null, this.canvas = null, this.config = null, this.converters = null;
131
+ }
132
+ /**
133
+ * Resolve style for an annotation
134
+ *
135
+ * Merges: pen config → type config → annotation.style
136
+ *
137
+ * @private
138
+ * @param {Object} annotation - Annotation object
139
+ * @returns {Object} Resolved style object
140
+ */
141
+ _resolveStyle(t) {
142
+ const { type: r, style: i } = t;
143
+ let e = { ...this.config.pen };
144
+ return this.config[r] && (e = k(e, this.config[r])), i && (e = k(e, i)), e;
86
145
  }
87
146
  /**
88
- * Notify all subscribers of current time
147
+ * Linearly interpolate between two points
89
148
  *
90
149
  * @private
91
- * @returns {void}
150
+ * @param {Array} p1 - Start point [x, y]
151
+ * @param {Array} p2 - End point [x, y]
152
+ * @param {number} t - Interpolation factor (0-1)
153
+ * @returns {Array} Interpolated point [x, y]
92
154
  */
93
- notifySubscribers() {
94
- for (const n of this.subscribers)
95
- try {
96
- n(this.currentTime);
97
- } catch (i) {
98
- console.error("TimelineSync: Subscriber callback error:", i);
99
- }
155
+ _interpolatePoint(t, r, i) {
156
+ return [
157
+ t[0] + (r[0] - t[0]) * i,
158
+ t[1] + (r[1] - t[1]) * i
159
+ ];
160
+ }
161
+ /**
162
+ * Get pressure values for visible points including interpolated final
163
+ *
164
+ * @private
165
+ * @param {Array} pressures - Full pressure array
166
+ * @param {number} lastCompleteIndex - Index of last complete point
167
+ * @param {number} segmentProgress - Progress within current segment (0-1)
168
+ * @returns {Array} Pressure values for visible points
169
+ */
170
+ _interpolatePressures(t, r, i) {
171
+ const e = t.slice(0, r + 1);
172
+ if (r < t.length - 1 && i > 0) {
173
+ const n = t[r], o = t[r + 1];
174
+ e.push(n + (o - n) * i);
175
+ }
176
+ return e;
177
+ }
178
+ /**
179
+ * Draw a single stroke with progress
180
+ *
181
+ * Uses endpoint interpolation for smooth progressive rendering.
182
+ *
183
+ * @private
184
+ * @param {Object} stroke - Stroke command object
185
+ * @param {number} progress - Progress from 0 to 1
186
+ */
187
+ _drawStroke(t, r) {
188
+ const { ctx: i } = this, { points: e, color: n, width: o, lineCap: h, pressures: s, uniformScale: g, baseX: l, baseY: a } = t;
189
+ if (!e || e.length < 2) return;
190
+ if (i.strokeStyle = n || "rgba(0, 0, 0, 0.5)", i.lineCap = h || "round", i.lineJoin = "round", r >= 1) {
191
+ s && s.length >= e.length ? this._drawVariableWidthStroke(e, o, s, g, l, a) : this._drawConstantWidthStroke(e, o, g, l, a);
192
+ return;
193
+ }
194
+ const c = e.length - 1, d = r * c, u = Math.floor(d), f = d - u, p = e.slice(0, u + 1);
195
+ if (u < c && f > 0) {
196
+ const w = e[u], v = e[u + 1];
197
+ p.push(this._interpolatePoint(w, v, f));
198
+ }
199
+ if (!(p.length < 2))
200
+ if (s && s.length >= e.length) {
201
+ const w = this._interpolatePressures(
202
+ s,
203
+ u,
204
+ f
205
+ );
206
+ this._drawVariableWidthStroke(p, o, w, g, l, a);
207
+ } else
208
+ this._drawConstantWidthStroke(p, o, g, l, a);
209
+ }
210
+ /**
211
+ * Draw stroke with constant width
212
+ *
213
+ * @private
214
+ * @param {Array} points - Array of [x, y] coordinates (normalized or offset)
215
+ * @param {number} width - Stroke width in pixels
216
+ * @param {boolean} [uniformScale=false] - Use uniform scaling to preserve aspect ratio
217
+ * @param {number} [baseX] - Base X position in width-normalized coords (for uniformScale with offset points)
218
+ * @param {number} [baseY] - Base Y position in height-normalized coords (for uniformScale with offset points)
219
+ */
220
+ _drawConstantWidthStroke(t, r, i = !1, e = null, n = null) {
221
+ const { ctx: o, viewport: h } = this;
222
+ o.lineWidth = r || 2, o.beginPath();
223
+ const s = i && e !== null && n !== null;
224
+ for (let g = 0; g < t.length; g++) {
225
+ const [l, a] = t[g];
226
+ let c, d;
227
+ s ? (c = e * h.width + l * h.height, d = n * h.height + a * h.height) : i ? (c = l * h.height, d = a * h.height) : (c = l * h.width, d = a * h.height), g === 0 ? o.moveTo(c, d) : o.lineTo(c, d);
228
+ }
229
+ o.stroke();
230
+ }
231
+ /**
232
+ * Draw stroke with variable width based on pressure
233
+ *
234
+ * @private
235
+ * @param {Array} points - Array of [x, y] coordinates (normalized or offset)
236
+ * @param {number} baseWidth - Base stroke width in pixels
237
+ * @param {Array} pressures - Pressure values (0-1) per point
238
+ * @param {boolean} [uniformScale=false] - Use uniform scaling to preserve aspect ratio
239
+ * @param {number} [baseX] - Base X position in width-normalized coords (for uniformScale with offset points)
240
+ * @param {number} [baseY] - Base Y position in height-normalized coords (for uniformScale with offset points)
241
+ */
242
+ _drawVariableWidthStroke(t, r, i, e = !1, n = null, o = null) {
243
+ const { ctx: h, viewport: s } = this, g = e && n !== null && o !== null;
244
+ for (let l = 1; l < t.length; l++) {
245
+ const [a, c] = t[l - 1], [d, u] = t[l];
246
+ let f, p, w, v;
247
+ g ? (f = n * s.width + a * s.height, p = o * s.height + c * s.height, w = n * s.width + d * s.height, v = o * s.height + u * s.height) : e ? (f = a * s.height, p = c * s.height, w = d * s.height, v = u * s.height) : (f = a * s.width, p = c * s.height, w = d * s.width, v = u * s.height);
248
+ const y = i[l - 1] || 1, S = i[l] || 1, x = (y + S) / 2, m = Math.max(0.5, r * x);
249
+ h.lineWidth = m, h.beginPath(), h.moveTo(f, p), h.lineTo(w, v), h.stroke();
250
+ }
100
251
  }
101
252
  }
102
253
  export {
103
- e as TimelineSync
254
+ W as StrokeRenderer,
255
+ W as default
104
256
  };
105
257
  //# sourceMappingURL=index5.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index5.js","sources":["../src/core/TimelineSync.js"],"sourcesContent":["/**\n * TimelineSync - Framework-agnostic timeline synchronization subsystem\n *\n * This module manages timeline position and provides a subscriber notification\n * system for timeline updates. Supports both discrete updates (manual setTime)\n * and continuous synchronization via requestAnimationFrame for audio/video.\n *\n * @module core/TimelineSync\n */\n\n/**\n * TimelineSync class\n *\n * Provides timeline state management and pub-sub notification system.\n * Zero dependencies - pure JavaScript implementation.\n *\n * @class\n * @example\n * // Discrete mode\n * const sync = new TimelineSync();\n * sync.subscribe((time) => console.log('Time:', time));\n * sync.setTime(5.0);\n *\n * @example\n * // Continuous mode with audio\n * const audio = document.getElementById('audio');\n * sync.startContinuousSync(() => audio.currentTime);\n */\nexport class TimelineSync {\n constructor() {\n /**\n * @private\n * @type {number}\n */\n this.currentTime = 0;\n\n /**\n * @private\n * @type {Set<Function>}\n */\n this.subscribers = new Set();\n\n /**\n * @private\n * @type {number|null}\n */\n this.animationFrameId = null;\n\n /**\n * @private\n * @type {boolean}\n */\n this.isRunning = false;\n }\n\n /**\n * Set timeline position and notify subscribers if changed\n *\n * @param {number} timestamp - Timeline position in seconds\n * @returns {void}\n */\n setTime(timestamp) {\n if (timestamp === this.currentTime) {\n return;\n }\n\n this.currentTime = timestamp;\n this.notifySubscribers();\n }\n\n /**\n * Get current timeline position\n *\n * @returns {number} Current timeline position in seconds\n */\n getCurrentTime() {\n return this.currentTime;\n }\n\n /**\n * Subscribe to timeline updates\n *\n * @param {Function} callback - Function to call on timeline updates\n * @returns {Function} Unsubscribe function\n * @throws {Error} If callback is not a function\n */\n subscribe(callback) {\n if (typeof callback !== 'function') {\n throw new Error('TimelineSync.subscribe: callback must be a function');\n }\n\n this.subscribers.add(callback);\n\n return () => this.unsubscribe(callback);\n }\n\n /**\n * Unsubscribe from timeline updates\n *\n * @param {Function} callback - Callback function to remove\n * @returns {void}\n */\n unsubscribe(callback) {\n this.subscribers.delete(callback);\n }\n\n /**\n * Start continuous timeline synchronization\n *\n * @param {Function} getTimeFunction - Function that returns current time\n * @returns {void}\n * @throws {Error} If getTimeFunction is not a function\n */\n startContinuousSync(getTimeFunction) {\n if (typeof getTimeFunction !== 'function') {\n throw new Error('TimelineSync.startContinuousSync: getTimeFunction must be a function');\n }\n\n if (this.isRunning) {\n console.warn('TimelineSync: Continuous sync already running');\n return;\n }\n\n this.isRunning = true;\n\n const syncLoop = () => {\n if (!this.isRunning) {\n return;\n }\n\n try {\n const newTime = getTimeFunction();\n this.setTime(newTime);\n } catch (err) {\n console.error('TimelineSync: Error in continuous sync:', err);\n }\n\n this.animationFrameId = requestAnimationFrame(syncLoop);\n };\n\n syncLoop();\n }\n\n /**\n * Stop continuous timeline synchronization\n *\n * @returns {void}\n */\n stopContinuousSync() {\n this.isRunning = false;\n\n if (this.animationFrameId !== null) {\n cancelAnimationFrame(this.animationFrameId);\n this.animationFrameId = null;\n }\n }\n\n /**\n * Clean up resources and release references\n *\n * @returns {void}\n */\n destroy() {\n this.stopContinuousSync();\n this.subscribers.clear();\n this.currentTime = 0;\n }\n\n /**\n * Notify all subscribers of current time\n *\n * @private\n * @returns {void}\n */\n notifySubscribers() {\n for (const callback of this.subscribers) {\n try {\n callback(this.currentTime);\n } catch (err) {\n console.error('TimelineSync: Subscriber callback error:', err);\n }\n }\n }\n}\n"],"names":["TimelineSync","timestamp","callback","getTimeFunction","syncLoop","newTime","err"],"mappings":"AA4BO,MAAMA,EAAa;AAAA,EACxB,cAAc;AAKZ,SAAK,cAAc,GAMnB,KAAK,cAAc,oBAAI,IAAG,GAM1B,KAAK,mBAAmB,MAMxB,KAAK,YAAY;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,QAAQC,GAAW;AACjB,IAAIA,MAAc,KAAK,gBAIvB,KAAK,cAAcA,GACnB,KAAK,kBAAiB;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,iBAAiB;AACf,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,UAAUC,GAAU;AAClB,QAAI,OAAOA,KAAa;AACtB,YAAM,IAAI,MAAM,qDAAqD;AAGvE,gBAAK,YAAY,IAAIA,CAAQ,GAEtB,MAAM,KAAK,YAAYA,CAAQ;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,YAAYA,GAAU;AACpB,SAAK,YAAY,OAAOA,CAAQ;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,oBAAoBC,GAAiB;AACnC,QAAI,OAAOA,KAAoB;AAC7B,YAAM,IAAI,MAAM,sEAAsE;AAGxF,QAAI,KAAK,WAAW;AAClB,cAAQ,KAAK,+CAA+C;AAC5D;AAAA,IACF;AAEA,SAAK,YAAY;AAEjB,UAAMC,IAAW,MAAM;AACrB,UAAK,KAAK,WAIV;AAAA,YAAI;AACF,gBAAMC,IAAUF,EAAe;AAC/B,eAAK,QAAQE,CAAO;AAAA,QACtB,SAASC,GAAK;AACZ,kBAAQ,MAAM,2CAA2CA,CAAG;AAAA,QAC9D;AAEA,aAAK,mBAAmB,sBAAsBF,CAAQ;AAAA;AAAA,IACxD;AAEA,IAAAA,EAAQ;AAAA,EACV;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,qBAAqB;AACnB,SAAK,YAAY,IAEb,KAAK,qBAAqB,SAC5B,qBAAqB,KAAK,gBAAgB,GAC1C,KAAK,mBAAmB;AAAA,EAE5B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,UAAU;AACR,SAAK,mBAAkB,GACvB,KAAK,YAAY,MAAK,GACtB,KAAK,cAAc;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,oBAAoB;AAClB,eAAWF,KAAY,KAAK;AAC1B,UAAI;AACF,QAAAA,EAAS,KAAK,WAAW;AAAA,MAC3B,SAASI,GAAK;AACZ,gBAAQ,MAAM,4CAA4CA,CAAG;AAAA,MAC/D;AAAA,EAEJ;AACF;"}
1
+ {"version":3,"file":"index5.js","sources":["../src/renderer/StrokeRenderer.js"],"sourcesContent":["/**\n * StrokeRenderer - Unified canvas-based annotation renderer\n *\n * Single entry point for rendering annotations as strokes on canvas.\n * Handles conversion from annotations to strokes and progressive rendering.\n *\n * @module renderer/StrokeRenderer\n */\n\nimport { highlightToStrokes, textToStrokes } from '../converters/index.js';\nimport { deepMerge } from '../pen/effects.js';\n\n/**\n * Default configuration\n */\nconst DEFAULT_CONFIG = {\n pen: {\n jitter: { amplitude: 1.0, frequency: 0.08 },\n pressure: { taperIn: 0.15, taperOut: 0.20 },\n wobble: { amplitude: 1.5, frequency: 0.05 }\n },\n highlight: {\n color: 'rgba(255, 255, 0, 0.3)',\n width: 24,\n lineCap: 'butt',\n jitter: { amplitude: 0 }\n },\n text: {\n color: 'rgba(220, 20, 60, 1.0)',\n width: 2,\n fontSize: 16,\n lineCap: 'round',\n jitter: { amplitude: 0 },\n pressure: { taperIn: 0, taperOut: 0 }\n }\n};\n\n/**\n * StrokeRenderer class\n *\n * Converts annotations to strokes and renders them progressively on canvas.\n *\n * @class\n * @example\n * const renderer = new StrokeRenderer(canvas, {\n * highlight: { color: 'rgba(0, 255, 200, 0.4)' }\n * });\n * renderer.setViewport(800, 600);\n * renderer.setAnnotations(annotations);\n * renderer.render(1.5); // Render at t=1.5 seconds\n */\nclass StrokeRenderer {\n /**\n * Create StrokeRenderer instance\n *\n * @param {HTMLCanvasElement} canvas - Canvas element for rendering\n * @param {Object} [config={}] - Configuration overrides\n * @param {Object} [config.pen] - Global pen settings\n * @param {Object} [config.highlight] - Highlight type settings\n * @param {Object} [config.text] - Text type settings\n */\n constructor(canvas, config = {}) {\n if (!canvas || !(canvas instanceof HTMLCanvasElement)) {\n throw new Error('StrokeRenderer: canvas must be a valid HTMLCanvasElement');\n }\n\n this.canvas = canvas;\n this.ctx = canvas.getContext('2d');\n this.config = deepMerge(DEFAULT_CONFIG, config);\n this.strokes = [];\n this.viewport = { width: 0, height: 0 };\n\n // Register built-in converters\n this.converters = {\n highlight: highlightToStrokes,\n text: textToStrokes\n };\n }\n\n /**\n * Register a custom converter for a new annotation type\n *\n * @param {string} type - Annotation type name\n * @param {Function} converter - Converter function (annotation, style) => strokes[]\n */\n registerConverter(type, converter) {\n if (typeof converter !== 'function') {\n throw new Error('StrokeRenderer.registerConverter: converter must be a function');\n }\n this.converters[type] = converter;\n }\n\n /**\n * Set viewport dimensions and configure canvas\n *\n * Handles high-DPI displays by scaling the canvas buffer.\n *\n * @param {number} width - Viewport width in CSS pixels\n * @param {number} height - Viewport height in CSS pixels\n */\n setViewport(width, height) {\n this.viewport = { width, height };\n\n const dpr = window.devicePixelRatio || 1;\n\n // Set canvas buffer size (high-res)\n this.canvas.width = width * dpr;\n this.canvas.height = height * dpr;\n\n // Set canvas display size (CSS)\n this.canvas.style.width = `${width}px`;\n this.canvas.style.height = `${height}px`;\n\n // Scale context for high-DPI\n this.ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n }\n\n /**\n * Set annotations and convert them to strokes\n *\n * Clears existing strokes and converts all annotations.\n *\n * @param {Array} annotations - Array of annotation objects\n * @param {number} [page] - Optional page filter (only convert annotations for this page)\n */\n setAnnotations(annotations, page = null) {\n if (!Array.isArray(annotations)) {\n console.warn('StrokeRenderer.setAnnotations: annotations must be an array');\n this.strokes = [];\n return;\n }\n\n // Filter by page if specified\n const filtered = page !== null\n ? annotations.filter(a => a.page === page)\n : annotations;\n\n // Convert each annotation to strokes\n this.strokes = filtered.flatMap(annotation => {\n const converter = this.converters[annotation.type];\n\n if (!converter) {\n console.warn(`StrokeRenderer: Unknown annotation type \"${annotation.type}\"`);\n return [];\n }\n\n // Resolve style: pen → type → annotation.style\n const style = this._resolveStyle(annotation);\n\n return converter(annotation, style);\n });\n }\n\n /**\n * Set pre-converted strokes directly\n *\n * Bypasses conversion, useful when strokes come from external source.\n *\n * @param {Array} strokes - Array of stroke command objects\n */\n setStrokes(strokes) {\n if (!Array.isArray(strokes)) {\n console.warn('StrokeRenderer.setStrokes: strokes must be an array');\n this.strokes = [];\n return;\n }\n this.strokes = strokes;\n }\n\n /**\n * Render strokes at the given time\n *\n * Clears canvas and draws all visible strokes with appropriate progress.\n *\n * @param {number} time - Current time in seconds\n */\n render(time) {\n const { ctx, viewport, strokes } = this;\n\n // Clear canvas\n ctx.clearRect(0, 0, viewport.width, viewport.height);\n\n // Draw each stroke\n for (const stroke of strokes) {\n // Skip if not started yet\n if (time < stroke.start) {\n continue;\n }\n\n // Calculate progress\n const duration = stroke.end - stroke.start;\n const elapsed = time - stroke.start;\n const progress = duration > 0 ? Math.min(1, elapsed / duration) : 1;\n\n this._drawStroke(stroke, progress);\n }\n }\n\n /**\n * Clear the canvas\n */\n clear() {\n this.ctx.clearRect(0, 0, this.viewport.width, this.viewport.height);\n }\n\n /**\n * Destroy the renderer and release resources\n */\n destroy() {\n this.strokes = [];\n this.ctx = null;\n this.canvas = null;\n this.config = null;\n this.converters = null;\n }\n\n /**\n * Resolve style for an annotation\n *\n * Merges: pen config → type config → annotation.style\n *\n * @private\n * @param {Object} annotation - Annotation object\n * @returns {Object} Resolved style object\n */\n _resolveStyle(annotation) {\n const { type, style: annotationStyle } = annotation;\n\n // Start with pen config (global)\n let resolved = { ...this.config.pen };\n\n // Merge type config\n if (this.config[type]) {\n resolved = deepMerge(resolved, this.config[type]);\n }\n\n // Merge annotation-level style\n if (annotationStyle) {\n resolved = deepMerge(resolved, annotationStyle);\n }\n\n return resolved;\n }\n\n /**\n * Linearly interpolate between two points\n *\n * @private\n * @param {Array} p1 - Start point [x, y]\n * @param {Array} p2 - End point [x, y]\n * @param {number} t - Interpolation factor (0-1)\n * @returns {Array} Interpolated point [x, y]\n */\n _interpolatePoint(p1, p2, t) {\n return [\n p1[0] + (p2[0] - p1[0]) * t,\n p1[1] + (p2[1] - p1[1]) * t\n ];\n }\n\n /**\n * Get pressure values for visible points including interpolated final\n *\n * @private\n * @param {Array} pressures - Full pressure array\n * @param {number} lastCompleteIndex - Index of last complete point\n * @param {number} segmentProgress - Progress within current segment (0-1)\n * @returns {Array} Pressure values for visible points\n */\n _interpolatePressures(pressures, lastCompleteIndex, segmentProgress) {\n const visiblePressures = pressures.slice(0, lastCompleteIndex + 1);\n\n if (lastCompleteIndex < pressures.length - 1 && segmentProgress > 0) {\n const p1 = pressures[lastCompleteIndex];\n const p2 = pressures[lastCompleteIndex + 1];\n visiblePressures.push(p1 + (p2 - p1) * segmentProgress);\n }\n\n return visiblePressures;\n }\n\n /**\n * Draw a single stroke with progress\n *\n * Uses endpoint interpolation for smooth progressive rendering.\n *\n * @private\n * @param {Object} stroke - Stroke command object\n * @param {number} progress - Progress from 0 to 1\n */\n _drawStroke(stroke, progress) {\n const { ctx } = this;\n const { points, color, width, lineCap, pressures, uniformScale, baseX, baseY } = stroke;\n\n if (!points || points.length < 2) return;\n\n // Configure stroke style\n ctx.strokeStyle = color || 'rgba(0, 0, 0, 0.5)';\n ctx.lineCap = lineCap || 'round';\n ctx.lineJoin = 'round';\n\n // Fast path: full stroke, no interpolation needed\n if (progress >= 1) {\n if (pressures && pressures.length >= points.length) {\n this._drawVariableWidthStroke(points, width, pressures, uniformScale, baseX, baseY);\n } else {\n this._drawConstantWidthStroke(points, width, uniformScale, baseX, baseY);\n }\n return;\n }\n\n // Calculate segment position for interpolation\n const totalSegments = points.length - 1;\n const progressAlongPath = progress * totalSegments;\n const lastCompleteIndex = Math.floor(progressAlongPath);\n const segmentProgress = progressAlongPath - lastCompleteIndex;\n\n // Build visible points array with complete points\n const visiblePoints = points.slice(0, lastCompleteIndex + 1);\n\n // Interpolate final point if mid-segment\n if (lastCompleteIndex < totalSegments && segmentProgress > 0) {\n const p1 = points[lastCompleteIndex];\n const p2 = points[lastCompleteIndex + 1];\n visiblePoints.push(this._interpolatePoint(p1, p2, segmentProgress));\n }\n\n // Need at least 2 points to draw a line\n if (visiblePoints.length < 2) return;\n\n // Draw with variable width if pressures provided\n if (pressures && pressures.length >= points.length) {\n const visiblePressures = this._interpolatePressures(\n pressures, lastCompleteIndex, segmentProgress\n );\n this._drawVariableWidthStroke(visiblePoints, width, visiblePressures, uniformScale, baseX, baseY);\n } else {\n this._drawConstantWidthStroke(visiblePoints, width, uniformScale, baseX, baseY);\n }\n }\n\n /**\n * Draw stroke with constant width\n *\n * @private\n * @param {Array} points - Array of [x, y] coordinates (normalized or offset)\n * @param {number} width - Stroke width in pixels\n * @param {boolean} [uniformScale=false] - Use uniform scaling to preserve aspect ratio\n * @param {number} [baseX] - Base X position in width-normalized coords (for uniformScale with offset points)\n * @param {number} [baseY] - Base Y position in height-normalized coords (for uniformScale with offset points)\n */\n _drawConstantWidthStroke(points, width, uniformScale = false, baseX = null, baseY = null) {\n const { ctx, viewport } = this;\n\n ctx.lineWidth = width || 2;\n ctx.beginPath();\n\n // When uniformScale is true and baseX/baseY provided, points are offsets from base position\n // Base position is in mixed coords (X: width-normalized, Y: height-normalized)\n // Offsets are in uniform space (both scaled by viewport.height)\n const hasBasePosition = uniformScale && baseX !== null && baseY !== null;\n\n for (let i = 0; i < points.length; i++) {\n const [coordX, coordY] = points[i];\n let px, py;\n\n if (hasBasePosition) {\n // Base position in pixels + uniform-scaled offset\n px = baseX * viewport.width + coordX * viewport.height;\n py = baseY * viewport.height + coordY * viewport.height;\n } else if (uniformScale) {\n // Legacy: full coordinates in uniform space\n px = coordX * viewport.height;\n py = coordY * viewport.height;\n } else {\n // Standard: X scaled by width, Y by height\n px = coordX * viewport.width;\n py = coordY * viewport.height;\n }\n\n if (i === 0) {\n ctx.moveTo(px, py);\n } else {\n ctx.lineTo(px, py);\n }\n }\n\n ctx.stroke();\n }\n\n /**\n * Draw stroke with variable width based on pressure\n *\n * @private\n * @param {Array} points - Array of [x, y] coordinates (normalized or offset)\n * @param {number} baseWidth - Base stroke width in pixels\n * @param {Array} pressures - Pressure values (0-1) per point\n * @param {boolean} [uniformScale=false] - Use uniform scaling to preserve aspect ratio\n * @param {number} [baseX] - Base X position in width-normalized coords (for uniformScale with offset points)\n * @param {number} [baseY] - Base Y position in height-normalized coords (for uniformScale with offset points)\n */\n _drawVariableWidthStroke(points, baseWidth, pressures, uniformScale = false, baseX = null, baseY = null) {\n const { ctx, viewport } = this;\n\n // When uniformScale is true and baseX/baseY provided, points are offsets from base position\n const hasBasePosition = uniformScale && baseX !== null && baseY !== null;\n\n for (let i = 1; i < points.length; i++) {\n const [c1x, c1y] = points[i - 1];\n const [c2x, c2y] = points[i];\n\n let px1, py1, px2, py2;\n\n if (hasBasePosition) {\n // Base position in pixels + uniform-scaled offset\n px1 = baseX * viewport.width + c1x * viewport.height;\n py1 = baseY * viewport.height + c1y * viewport.height;\n px2 = baseX * viewport.width + c2x * viewport.height;\n py2 = baseY * viewport.height + c2y * viewport.height;\n } else if (uniformScale) {\n // Legacy: full coordinates in uniform space\n px1 = c1x * viewport.height;\n py1 = c1y * viewport.height;\n px2 = c2x * viewport.height;\n py2 = c2y * viewport.height;\n } else {\n // Standard: X scaled by width, Y by height\n px1 = c1x * viewport.width;\n py1 = c1y * viewport.height;\n px2 = c2x * viewport.width;\n py2 = c2y * viewport.height;\n }\n\n // Average pressure between two points\n const p1 = pressures[i - 1] || 1;\n const p2 = pressures[i] || 1;\n const avgPressure = (p1 + p2) / 2;\n\n // Apply pressure to width (min 0.5)\n const width = Math.max(0.5, baseWidth * avgPressure);\n\n ctx.lineWidth = width;\n ctx.beginPath();\n ctx.moveTo(px1, py1);\n ctx.lineTo(px2, py2);\n ctx.stroke();\n }\n }\n}\n\nexport { StrokeRenderer };\nexport default StrokeRenderer;\n"],"names":["DEFAULT_CONFIG","StrokeRenderer","canvas","config","deepMerge","highlightToStrokes","textToStrokes","type","converter","width","height","dpr","annotations","page","filtered","a","annotation","style","strokes","time","ctx","viewport","stroke","duration","elapsed","progress","annotationStyle","resolved","p1","p2","t","pressures","lastCompleteIndex","segmentProgress","visiblePressures","points","color","lineCap","uniformScale","baseX","baseY","totalSegments","progressAlongPath","visiblePoints","hasBasePosition","i","coordX","coordY","px","py","baseWidth","c1x","c1y","c2x","c2y","px1","py1","px2","py2","avgPressure"],"mappings":"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"}
package/dist/index6.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});class t{constructor(e,r){if(this._validateContainer(e),this._validateViewport(r),this.container=e,this.viewport={...r},this.annotations=[],this.currentTime=0,this.isDestroyed=!1,new.target===t)throw new Error("BaseLayer is an abstract class and cannot be instantiated directly. Extend it with a concrete implementation.")}setAnnotations(e){this._checkDestroyed("setAnnotations"),this.annotations=e||[]}setViewport(e){this._checkDestroyed("setViewport"),this._validateViewport(e),this.viewport={...e}}updateTime(e){this._checkDestroyed("updateTime"),this.currentTime=e}destroy(){this.isDestroyed||(this.annotations=null,this.viewport=null,this.container=null,this.isDestroyed=!0)}render(){throw new Error("render() must be implemented by subclass")}update(){throw new Error("update() must be implemented by subclass")}_validateContainer(e){if(!e||!(e instanceof HTMLElement))throw new Error("BaseLayer: container must be a valid HTMLElement")}_validateViewport(e){if(!e||typeof e!="object")throw new Error("BaseLayer: viewport must be an object");if(typeof e.width!="number"||e.width<=0)throw new Error("BaseLayer: viewport.width must be a positive number");if(typeof e.height!="number"||e.height<=0)throw new Error("BaseLayer: viewport.height must be a positive number");if(typeof e.scale!="number"||e.scale<=0)throw new Error("BaseLayer: viewport.scale must be a positive number")}_checkDestroyed(e){if(this.isDestroyed)throw new Error(`BaseLayer: Cannot call ${e}() on destroyed layer`)}}exports.default=t;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function h(e){if(e==null)return Math.random;let r=typeof e=="string"?e.split("").reduce((t,n)=>t+n.charCodeAt(0),0):e;return function(){r|=0,r=r+1831565813|0;let t=Math.imul(r^r>>>15,1|r);return t=t+Math.imul(t^t>>>7,61|t)^t,((t^t>>>14)>>>0)/4294967296}}function b(e,r,t=null){const{amplitude:n=0}=r||{};if(n===0||!e||e.length===0)return e;const l=h(t);return e.map(([u,a])=>[u+(l()-.5)*n,a+(l()-.5)*n])}function M(e,r){const{taperIn:t=0,taperOut:n=0}=r||{};if(!e||e.length===0)return[];if(t===0&&n===0)return e.map(()=>1);const l=e.length;return e.map((u,a)=>{const f=l>1?a/(l-1):0;return t>0&&f<t?f/t:n>0&&f>1-n?(1-f)/n:1})}function A(e,r){const{amplitude:t=0,frequency:n=.05}=r||{};return t===0||!e||e.length<2?e:e.map(([l,u],a)=>{const f=a/(e.length-1),y=Math.sin(f*Math.PI*2*n*e.length)*t;let d=0,p=1;if(a<e.length-1){const[m,g]=e[a+1],c=m-l,o=g-u,i=Math.sqrt(c*c+o*o);i>1e-4&&(d=-o/i,p=c/i)}return[l+d*y,u+p*y]})}function s(e,r){if(!r)return{...e};if(!e)return{...r};const t={...e};for(const n of Object.keys(r)){const l=r[n],u=e[n];l!==null&&typeof l=="object"&&!Array.isArray(l)&&u!==null&&typeof u=="object"&&!Array.isArray(u)?t[n]=s(u,l):l!==void 0&&(t[n]=l)}return t}exports.applyJitter=b;exports.applyPressure=M;exports.applyWobble=A;exports.deepMerge=s;exports.seededRandom=h;
2
2
  //# sourceMappingURL=index6.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index6.cjs","sources":["../src/layers/BaseLayer.js"],"sourcesContent":["/**\n * BaseLayer - Abstract base class for annotation layers\n *\n * Provides common interface and lifecycle management for all annotation layer types.\n * Subclasses must implement render() and update() abstract methods.\n *\n * @abstract\n */\nclass BaseLayer {\n /**\n * Creates a new BaseLayer instance\n *\n * @param {HTMLElement} container - Parent DOM element for layer content\n * @param {Object} viewport - Initial viewport dimensions\n * @param {number} viewport.width - Viewport width in pixels\n * @param {number} viewport.height - Viewport height in pixels\n * @param {number} viewport.scale - PDF scale/zoom level\n * @throws {Error} If container is not a valid HTMLElement\n * @throws {Error} If viewport is missing required properties\n * @throws {Error} If instantiated directly (abstract class)\n */\n constructor(container, viewport) {\n // Validate parameters\n this._validateContainer(container);\n this._validateViewport(viewport);\n\n // Initialize core properties\n this.container = container;\n this.viewport = { ...viewport };\n this.annotations = [];\n this.currentTime = 0;\n this.isDestroyed = false;\n\n // Prevent direct instantiation\n if (new.target === BaseLayer) {\n throw new Error('BaseLayer is an abstract class and cannot be instantiated directly. Extend it with a concrete implementation.');\n }\n }\n\n /**\n * Sets the annotation data for this layer\n *\n * @param {Array} annotations - Array of annotation objects\n * @throws {Error} If called after layer is destroyed\n */\n setAnnotations(annotations) {\n this._checkDestroyed('setAnnotations');\n this.annotations = annotations || [];\n }\n\n /**\n * Updates the viewport dimensions\n *\n * @param {Object} viewport - New viewport dimensions\n * @param {number} viewport.width - Viewport width in pixels\n * @param {number} viewport.height - Viewport height in pixels\n * @param {number} viewport.scale - PDF scale/zoom level\n * @throws {Error} If viewport is missing required properties\n * @throws {Error} If called after layer is destroyed\n */\n setViewport(viewport) {\n this._checkDestroyed('setViewport');\n this._validateViewport(viewport);\n this.viewport = { ...viewport };\n }\n\n /**\n * Updates the current timeline position\n *\n * @param {number} nowSec - Current timeline position in seconds\n * @throws {Error} If called after layer is destroyed\n */\n updateTime(nowSec) {\n this._checkDestroyed('updateTime');\n this.currentTime = nowSec;\n }\n\n /**\n * Destroys the layer and releases resources\n *\n * Safe to call multiple times (idempotent).\n * Subclasses must call super.destroy() after their own cleanup.\n */\n destroy() {\n if (this.isDestroyed) {\n return;\n }\n\n this.annotations = null;\n this.viewport = null;\n this.container = null;\n this.isDestroyed = true;\n }\n\n /**\n * Renders the layer content\n *\n * @abstract\n * @throws {Error} If not implemented by subclass\n */\n render() {\n throw new Error('render() must be implemented by subclass');\n }\n\n /**\n * Updates the visual state of the layer\n *\n * @abstract\n * @throws {Error} If not implemented by subclass\n */\n update() {\n throw new Error('update() must be implemented by subclass');\n }\n\n /**\n * Validates that container is a valid HTMLElement\n *\n * @private\n * @param {*} container - Value to validate\n * @throws {Error} If container is not a valid HTMLElement\n */\n _validateContainer(container) {\n if (!container || !(container instanceof HTMLElement)) {\n throw new Error('BaseLayer: container must be a valid HTMLElement');\n }\n }\n\n /**\n * Validates that viewport has required properties\n *\n * @private\n * @param {*} viewport - Value to validate\n * @throws {Error} If viewport is missing required properties\n */\n _validateViewport(viewport) {\n if (!viewport || typeof viewport !== 'object') {\n throw new Error('BaseLayer: viewport must be an object');\n }\n\n if (typeof viewport.width !== 'number' || viewport.width <= 0) {\n throw new Error('BaseLayer: viewport.width must be a positive number');\n }\n\n if (typeof viewport.height !== 'number' || viewport.height <= 0) {\n throw new Error('BaseLayer: viewport.height must be a positive number');\n }\n\n if (typeof viewport.scale !== 'number' || viewport.scale <= 0) {\n throw new Error('BaseLayer: viewport.scale must be a positive number');\n }\n }\n\n /**\n * Checks if layer is destroyed and throws error if so\n *\n * @private\n * @param {string} methodName - Name of method being called\n * @throws {Error} If layer is destroyed\n */\n _checkDestroyed(methodName) {\n if (this.isDestroyed) {\n throw new Error(`BaseLayer: Cannot call ${methodName}() on destroyed layer`);\n }\n }\n}\n\nexport default BaseLayer;\n"],"names":["BaseLayer","container","viewport","annotations","nowSec","methodName"],"mappings":"4GAQA,MAAMA,CAAU,CAad,YAAYC,EAAWC,EAAU,CAa/B,GAXA,KAAK,mBAAmBD,CAAS,EACjC,KAAK,kBAAkBC,CAAQ,EAG/B,KAAK,UAAYD,EACjB,KAAK,SAAW,CAAE,GAAGC,CAAQ,EAC7B,KAAK,YAAc,CAAA,EACnB,KAAK,YAAc,EACnB,KAAK,YAAc,GAGf,aAAeF,EACjB,MAAM,IAAI,MAAM,+GAA+G,CAEnI,CAQA,eAAeG,EAAa,CAC1B,KAAK,gBAAgB,gBAAgB,EACrC,KAAK,YAAcA,GAAe,CAAA,CACpC,CAYA,YAAYD,EAAU,CACpB,KAAK,gBAAgB,aAAa,EAClC,KAAK,kBAAkBA,CAAQ,EAC/B,KAAK,SAAW,CAAE,GAAGA,CAAQ,CAC/B,CAQA,WAAWE,EAAQ,CACjB,KAAK,gBAAgB,YAAY,EACjC,KAAK,YAAcA,CACrB,CAQA,SAAU,CACJ,KAAK,cAIT,KAAK,YAAc,KACnB,KAAK,SAAW,KAChB,KAAK,UAAY,KACjB,KAAK,YAAc,GACrB,CAQA,QAAS,CACP,MAAM,IAAI,MAAM,0CAA0C,CAC5D,CAQA,QAAS,CACP,MAAM,IAAI,MAAM,0CAA0C,CAC5D,CASA,mBAAmBH,EAAW,CAC5B,GAAI,CAACA,GAAa,EAAEA,aAAqB,aACvC,MAAM,IAAI,MAAM,kDAAkD,CAEtE,CASA,kBAAkBC,EAAU,CAC1B,GAAI,CAACA,GAAY,OAAOA,GAAa,SACnC,MAAM,IAAI,MAAM,uCAAuC,EAGzD,GAAI,OAAOA,EAAS,OAAU,UAAYA,EAAS,OAAS,EAC1D,MAAM,IAAI,MAAM,qDAAqD,EAGvE,GAAI,OAAOA,EAAS,QAAW,UAAYA,EAAS,QAAU,EAC5D,MAAM,IAAI,MAAM,sDAAsD,EAGxE,GAAI,OAAOA,EAAS,OAAU,UAAYA,EAAS,OAAS,EAC1D,MAAM,IAAI,MAAM,qDAAqD,CAEzE,CASA,gBAAgBG,EAAY,CAC1B,GAAI,KAAK,YACP,MAAM,IAAI,MAAM,0BAA0BA,CAAU,uBAAuB,CAE/E,CACF"}
1
+ {"version":3,"file":"index6.cjs","sources":["../src/pen/effects.js"],"sourcesContent":["/**\n * Pen Effects - Utility functions for hand-drawn feel\n *\n * Provides jitter, pressure, and wobble effects for stroke paths.\n * Pure functions that transform point arrays without side effects.\n *\n * @module pen/effects\n */\n\n/**\n * Creates a seeded random number generator\n *\n * Uses mulberry32 algorithm for deterministic randomness.\n * Same seed produces same sequence of random numbers.\n *\n * @param {string|number|null} seed - Seed value (null uses Math.random)\n * @returns {Function} Function that returns random numbers 0-1\n */\nexport function seededRandom(seed) {\n if (seed === null || seed === undefined) {\n return Math.random;\n }\n\n // Convert string seed to number\n let numSeed = typeof seed === 'string'\n ? seed.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0)\n : seed;\n\n // Mulberry32 algorithm\n return function() {\n numSeed |= 0;\n numSeed = (numSeed + 0x6D2B79F5) | 0;\n let t = Math.imul(numSeed ^ (numSeed >>> 15), 1 | numSeed);\n t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n}\n\n/**\n * Applies random jitter offset to points\n *\n * Adds small random displacement to each point for hand-drawn imperfection.\n * Returns new array, does not mutate input.\n *\n * @param {Array<[number, number]>} points - Array of [x, y] coordinates\n * @param {Object} config - Jitter configuration\n * @param {number} config.amplitude - Max offset in normalized units (0 = disabled)\n * @param {number} [config.frequency=1] - Not currently used, reserved for future\n * @param {string|number|null} [seed=null] - Seed for reproducible randomness\n * @returns {Array<[number, number]>} New array with jittered points\n */\nexport function applyJitter(points, config, seed = null) {\n const { amplitude = 0 } = config || {};\n\n if (amplitude === 0 || !points || points.length === 0) {\n return points;\n }\n\n const rng = seededRandom(seed);\n\n return points.map(([x, y]) => [\n x + (rng() - 0.5) * amplitude,\n y + (rng() - 0.5) * amplitude\n ]);\n}\n\n/**\n * Calculates pressure values for variable stroke width\n *\n * Simulates pen pressure with taper at start and end of stroke.\n * Returns pressure multipliers (0-1) for each point.\n *\n * @param {Array<[number, number]>} points - Array of [x, y] coordinates\n * @param {Object} config - Pressure configuration\n * @param {number} [config.taperIn=0] - Start taper length (0-1, fraction of stroke)\n * @param {number} [config.taperOut=0] - End taper length (0-1, fraction of stroke)\n * @returns {Array<number>} Pressure values (0-1) per point\n */\nexport function applyPressure(points, config) {\n const { taperIn = 0, taperOut = 0 } = config || {};\n\n if (!points || points.length === 0) {\n return [];\n }\n\n if (taperIn === 0 && taperOut === 0) {\n return points.map(() => 1.0);\n }\n\n const len = points.length;\n\n return points.map((_, i) => {\n const t = len > 1 ? i / (len - 1) : 0;\n\n // Taper in at start\n if (taperIn > 0 && t < taperIn) {\n return t / taperIn;\n }\n\n // Taper out at end\n if (taperOut > 0 && t > 1 - taperOut) {\n return (1 - t) / taperOut;\n }\n\n // Full pressure in middle\n return 1.0;\n });\n}\n\n/**\n * Applies low-frequency wobble perpendicular to stroke direction\n *\n * Adds gentle wave effect to simulate natural hand movement.\n * Returns new array, does not mutate input.\n *\n * @param {Array<[number, number]>} points - Array of [x, y] coordinates\n * @param {Object} config - Wobble configuration\n * @param {number} config.amplitude - Wave height in normalized units (0 = disabled)\n * @param {number} [config.frequency=0.05] - Wave frequency\n * @returns {Array<[number, number]>} New array with wobbled points\n */\nexport function applyWobble(points, config) {\n const { amplitude = 0, frequency = 0.05 } = config || {};\n\n if (amplitude === 0 || !points || points.length < 2) {\n return points;\n }\n\n return points.map(([x, y], i) => {\n // Calculate perpendicular direction based on position in stroke\n // Use simple sine wave for now\n const t = i / (points.length - 1);\n const offset = Math.sin(t * Math.PI * 2 * frequency * points.length) * amplitude;\n\n // Calculate perpendicular direction from neighbors\n let perpX = 0;\n let perpY = 1;\n\n if (i < points.length - 1) {\n const [nx, ny] = points[i + 1];\n const dx = nx - x;\n const dy = ny - y;\n const len = Math.sqrt(dx * dx + dy * dy);\n\n if (len > 0.0001) {\n // Perpendicular is (-dy, dx) normalized\n perpX = -dy / len;\n perpY = dx / len;\n }\n }\n\n return [\n x + perpX * offset,\n y + perpY * offset\n ];\n });\n}\n\n/**\n * Deep merges configuration objects\n *\n * Merges source into target, handling nested objects.\n * Returns new object, does not mutate inputs.\n *\n * @param {Object} target - Target object\n * @param {Object} source - Source object to merge\n * @returns {Object} Merged object\n */\nexport function deepMerge(target, source) {\n if (!source) return { ...target };\n if (!target) return { ...source };\n\n const result = { ...target };\n\n for (const key of Object.keys(source)) {\n const sourceVal = source[key];\n const targetVal = target[key];\n\n if (\n sourceVal !== null &&\n typeof sourceVal === 'object' &&\n !Array.isArray(sourceVal) &&\n targetVal !== null &&\n typeof targetVal === 'object' &&\n !Array.isArray(targetVal)\n ) {\n result[key] = deepMerge(targetVal, sourceVal);\n } else if (sourceVal !== undefined) {\n result[key] = sourceVal;\n }\n }\n\n return result;\n}\n"],"names":["seededRandom","seed","numSeed","acc","char","applyJitter","points","config","amplitude","rng","x","y","applyPressure","taperIn","taperOut","len","_","i","t","applyWobble","frequency","offset","perpX","perpY","nx","ny","dx","dy","deepMerge","target","source","result","key","sourceVal","targetVal"],"mappings":"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"}