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/index4.js CHANGED
@@ -1,104 +1,105 @@
1
- import h from "./index7.js";
2
- import l from "./index8.js";
3
- import o from "./index9.js";
4
- class f {
1
+ class e {
2
+ constructor() {
3
+ this.currentTime = 0, this.subscribers = /* @__PURE__ */ new Set(), this.animationFrameId = null, this.isRunning = !1;
4
+ }
5
5
  /**
6
- * Create LayerManager instance
6
+ * Set timeline position and notify subscribers if changed
7
7
  *
8
- * Instantiates all layer classes and manages their lifecycle.
9
- * Layers are created immediately and appended to container.
8
+ * @param {number} timestamp - Timeline position in seconds
9
+ * @returns {void}
10
+ */
11
+ setTime(n) {
12
+ n !== this.currentTime && (this.currentTime = n, this.notifySubscribers());
13
+ }
14
+ /**
15
+ * Get current timeline position
10
16
  *
11
- * @param {HTMLElement} containerElement - DOM element for layer rendering
12
- * @param {Object} viewport - Initial viewport dimensions
13
- * @param {number} viewport.width - Viewport width in pixels
14
- * @param {number} viewport.height - Viewport height in pixels
15
- * @param {number} viewport.scale - Scale factor
16
- * @throws {Error} If containerElement is not a valid DOM element
17
- * @throws {Error} If viewport is invalid or missing required properties
17
+ * @returns {number} Current timeline position in seconds
18
18
  */
19
- constructor(t, e) {
20
- if (!t || !(t instanceof HTMLElement))
21
- throw new Error("LayerManager: containerElement must be a valid DOM element");
22
- if (!e || typeof e != "object")
23
- throw new Error("LayerManager: viewport must be a valid object");
24
- if (typeof e.width != "number" || typeof e.height != "number")
25
- throw new Error("LayerManager: viewport must have width and height properties");
26
- this.container = t, this.currentPage = null, this.currentViewport = e, this.allAnnotations = [], this.layers = {
27
- highlight: new h(t, e),
28
- text: new l(t, e),
29
- drawing: new o(t, e)
30
- };
19
+ getCurrentTime() {
20
+ return this.currentTime;
31
21
  }
32
22
  /**
33
- * Set annotations and route to appropriate layers
23
+ * Subscribe to timeline updates
34
24
  *
35
- * Filters annotations for the specified page and groups by type.
36
- * Passes annotations to layer instances and triggers render.
37
- * Layers create/update their DOM elements during render.
25
+ * @param {Function} callback - Function to call on timeline updates
26
+ * @returns {Function} Unsubscribe function
27
+ * @throws {Error} If callback is not a function
28
+ */
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);
33
+ }
34
+ /**
35
+ * Unsubscribe from timeline updates
38
36
  *
39
- * @param {Array} annotations - Complete annotation array (all pages, all types)
40
- * @param {number} pageNum - Current page number (1-indexed)
37
+ * @param {Function} callback - Callback function to remove
41
38
  * @returns {void}
42
39
  */
43
- setAnnotations(t, e) {
44
- if (Array.isArray(t) || (console.warn("LayerManager.setAnnotations: annotations must be an array"), t = []), typeof e != "number" || e < 1) {
45
- console.warn("LayerManager.setAnnotations: invalid page number");
46
- return;
47
- }
48
- this.allAnnotations = t, this.currentPage = e;
49
- const i = t.filter((r) => r.page === e), s = i.filter((r) => r.type === "highlight"), n = i.filter((r) => r.type === "text"), a = i.filter((r) => r.type === "ink");
50
- this.layers.highlight.setAnnotations(s), this.layers.text.setAnnotations(n), this.layers.drawing.setAnnotations(a), this.layers.highlight.render(), this.layers.text.render(), this.layers.drawing.render();
40
+ unsubscribe(n) {
41
+ this.subscribers.delete(n);
51
42
  }
52
43
  /**
53
- * Update viewport dimensions for all layers
44
+ * Start continuous timeline synchronization
54
45
  *
55
- * Propagates viewport object to all layer instances.
56
- * Triggers render to recalculate element positions and dimensions.
57
- * Viewport contains width, height, scale from PDFRenderer.
58
- *
59
- * @param {Object} viewport - Viewport object from PDFRenderer
60
- * @param {number} viewport.width - Viewport width in pixels
61
- * @param {number} viewport.height - Viewport height in pixels
62
- * @param {number} viewport.scale - Scale factor
46
+ * @param {Function} getTimeFunction - Function that returns current time
63
47
  * @returns {void}
48
+ * @throws {Error} If getTimeFunction is not a function
64
49
  */
65
- setViewport(t) {
66
- if (!t || typeof t != "object") {
67
- console.warn("LayerManager.setViewport: invalid viewport object");
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");
68
55
  return;
69
56
  }
70
- this.currentViewport = t, this.layers.highlight.setViewport(t), this.layers.text.setViewport(t), this.layers.drawing.setViewport(t), this.layers.highlight.render(), this.layers.text.render(), this.layers.drawing.render();
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();
71
70
  }
72
71
  /**
73
- * Update timeline position for all layers
74
- *
75
- * Propagates timestamp to all layer instances.
76
- * Layers handle their own animation updates (RAF loops, transforms, etc).
72
+ * Stop continuous timeline synchronization
77
73
  *
78
- * @param {number} timestamp - Current timeline position in seconds
79
74
  * @returns {void}
80
75
  */
81
- updateTimeline(t) {
82
- if (typeof t != "number") {
83
- console.warn("LayerManager.updateTimeline: timestamp must be a number");
84
- return;
85
- }
86
- this.layers.highlight.updateTime(t), this.layers.text.updateTime(t), this.layers.drawing.updateTime(t);
76
+ stopContinuousSync() {
77
+ this.isRunning = !1, this.animationFrameId !== null && (cancelAnimationFrame(this.animationFrameId), this.animationFrameId = null);
87
78
  }
88
79
  /**
89
- * Clean up resources and destroy layer instances
90
- *
91
- * Calls destroy() on all layer instances to clean up DOM elements,
92
- * cancel animations, and release references.
93
- * Call before removing LayerManager instance.
80
+ * Clean up resources and release references
94
81
  *
95
82
  * @returns {void}
96
83
  */
97
84
  destroy() {
98
- this.layers && (this.layers.highlight.destroy(), this.layers.text.destroy(), this.layers.drawing.destroy(), this.layers = null), this.container = null, this.currentPage = null, this.currentViewport = null, this.allAnnotations = [];
85
+ this.stopContinuousSync(), this.subscribers.clear(), this.currentTime = 0;
86
+ }
87
+ /**
88
+ * Notify all subscribers of current time
89
+ *
90
+ * @private
91
+ * @returns {void}
92
+ */
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
+ }
99
100
  }
100
101
  }
101
102
  export {
102
- f as LayerManager
103
+ e as TimelineSync
103
104
  };
104
105
  //# sourceMappingURL=index4.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index4.js","sources":["../src/core/LayerManager.js"],"sourcesContent":["/**\n * LayerManager - Framework-agnostic layer orchestration subsystem\n *\n * This module manages annotation layer instances, routes annotations by type,\n * and coordinates viewport and timeline state across all layers.\n * Instantiates layer classes directly and manages their lifecycle.\n *\n * @module core/LayerManager\n */\n\nimport HighlightLayer from '../layers/HighlightLayer.js';\nimport TextLayer from '../layers/TextLayer.js';\nimport DrawingLayer from '../layers/DrawingLayer.js';\n\n/**\n * LayerManager class\n *\n * Orchestrates annotation layers by instantiating and managing layer instances.\n * Routes annotations to appropriate layers by type, propagates viewport changes,\n * and coordinates timeline updates. Uses Direct Instantiation Pattern for\n * framework-agnostic layer management.\n *\n * @class\n * @example\n * const viewport = { width: 800, height: 600, scale: 1.0 };\n * const manager = new LayerManager(containerElement, viewport);\n * manager.setAnnotations(annotations, 1);\n * manager.setViewport(newViewport);\n * manager.updateTimeline(5.0);\n * // Layers render automatically\n * manager.destroy();\n */\nexport class LayerManager {\n /**\n * Create LayerManager instance\n *\n * Instantiates all layer classes and manages their lifecycle.\n * Layers are created immediately and appended to container.\n *\n * @param {HTMLElement} containerElement - DOM element for layer rendering\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 - Scale factor\n * @throws {Error} If containerElement is not a valid DOM element\n * @throws {Error} If viewport is invalid or missing required properties\n */\n constructor(containerElement, viewport) {\n // Validate container element\n if (!containerElement || !(containerElement instanceof HTMLElement)) {\n throw new Error('LayerManager: containerElement must be a valid DOM element');\n }\n\n // Validate viewport\n if (!viewport || typeof viewport !== 'object') {\n throw new Error('LayerManager: viewport must be a valid object');\n }\n if (typeof viewport.width !== 'number' || typeof viewport.height !== 'number') {\n throw new Error('LayerManager: viewport must have width and height properties');\n }\n\n /**\n * @private\n * @type {HTMLElement}\n */\n this.container = containerElement;\n\n /**\n * @private\n * @type {number|null}\n */\n this.currentPage = null;\n\n /**\n * @private\n * @type {Object}\n */\n this.currentViewport = viewport;\n\n /**\n * @private\n * @type {Array}\n */\n this.allAnnotations = [];\n\n /**\n * Layer instances\n * @private\n * @type {Object}\n */\n this.layers = {\n highlight: new HighlightLayer(containerElement, viewport),\n text: new TextLayer(containerElement, viewport),\n drawing: new DrawingLayer(containerElement, viewport)\n };\n }\n\n /**\n * Set annotations and route to appropriate layers\n *\n * Filters annotations for the specified page and groups by type.\n * Passes annotations to layer instances and triggers render.\n * Layers create/update their DOM elements during render.\n *\n * @param {Array} annotations - Complete annotation array (all pages, all types)\n * @param {number} pageNum - Current page number (1-indexed)\n * @returns {void}\n */\n setAnnotations(annotations, pageNum) {\n // Validate inputs\n if (!Array.isArray(annotations)) {\n console.warn('LayerManager.setAnnotations: annotations must be an array');\n annotations = [];\n }\n\n if (typeof pageNum !== 'number' || pageNum < 1) {\n console.warn('LayerManager.setAnnotations: invalid page number');\n return;\n }\n\n // Store for reference\n this.allAnnotations = annotations;\n this.currentPage = pageNum;\n\n // Filter annotations for current page only\n const pageAnnotations = annotations.filter(a => a.page === pageNum);\n\n // Group by type\n const highlights = pageAnnotations.filter(a => a.type === 'highlight');\n const textAnnotations = pageAnnotations.filter(a => a.type === 'text');\n const inkAnnotations = pageAnnotations.filter(a => a.type === 'ink');\n\n // Pass annotations to layer instances\n this.layers.highlight.setAnnotations(highlights);\n this.layers.text.setAnnotations(textAnnotations);\n this.layers.drawing.setAnnotations(inkAnnotations);\n\n // Trigger render on all layers\n this.layers.highlight.render();\n this.layers.text.render();\n this.layers.drawing.render();\n }\n\n /**\n * Update viewport dimensions for all layers\n *\n * Propagates viewport object to all layer instances.\n * Triggers render to recalculate element positions and dimensions.\n * Viewport contains width, height, scale from PDFRenderer.\n *\n * @param {Object} viewport - Viewport object from PDFRenderer\n * @param {number} viewport.width - Viewport width in pixels\n * @param {number} viewport.height - Viewport height in pixels\n * @param {number} viewport.scale - Scale factor\n * @returns {void}\n */\n setViewport(viewport) {\n // Validate viewport\n if (!viewport || typeof viewport !== 'object') {\n console.warn('LayerManager.setViewport: invalid viewport object');\n return;\n }\n\n // Store viewport reference\n this.currentViewport = viewport;\n\n // Propagate to all layer instances\n this.layers.highlight.setViewport(viewport);\n this.layers.text.setViewport(viewport);\n this.layers.drawing.setViewport(viewport);\n\n // Trigger render on all layers (viewport change requires re-layout)\n this.layers.highlight.render();\n this.layers.text.render();\n this.layers.drawing.render();\n }\n\n /**\n * Update timeline position for all layers\n *\n * Propagates timestamp to all layer instances.\n * Layers handle their own animation updates (RAF loops, transforms, etc).\n *\n * @param {number} timestamp - Current timeline position in seconds\n * @returns {void}\n */\n updateTimeline(timestamp) {\n // Validate timestamp\n if (typeof timestamp !== 'number') {\n console.warn('LayerManager.updateTimeline: timestamp must be a number');\n return;\n }\n\n // Propagate time update to all layer instances\n this.layers.highlight.updateTime(timestamp);\n this.layers.text.updateTime(timestamp);\n this.layers.drawing.updateTime(timestamp);\n }\n\n /**\n * Clean up resources and destroy layer instances\n *\n * Calls destroy() on all layer instances to clean up DOM elements,\n * cancel animations, and release references.\n * Call before removing LayerManager instance.\n *\n * @returns {void}\n */\n destroy() {\n // Destroy all layer instances\n if (this.layers) {\n this.layers.highlight.destroy();\n this.layers.text.destroy();\n this.layers.drawing.destroy();\n this.layers = null;\n }\n\n // Clear all references\n this.container = null;\n this.currentPage = null;\n this.currentViewport = null;\n this.allAnnotations = [];\n }\n}\n"],"names":["LayerManager","containerElement","viewport","HighlightLayer","TextLayer","DrawingLayer","annotations","pageNum","pageAnnotations","a","highlights","textAnnotations","inkAnnotations","timestamp"],"mappings":"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"}
1
+ {"version":3,"file":"index4.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;"}
package/dist/index5.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class r{constructor(){this.currentTime=0,this.subscribers=new Set,this.animationFrameId=null,this.isRunning=!1}setTime(n){n!==this.currentTime&&(this.currentTime=n,this.notifySubscribers())}getCurrentTime(){return this.currentTime}subscribe(n){if(typeof n!="function")throw new Error("TimelineSync.subscribe: callback must be a function");return this.subscribers.add(n),()=>this.unsubscribe(n)}unsubscribe(n){this.subscribers.delete(n)}startContinuousSync(n){if(typeof n!="function")throw new Error("TimelineSync.startContinuousSync: getTimeFunction must be a function");if(this.isRunning){console.warn("TimelineSync: Continuous sync already running");return}this.isRunning=!0;const i=()=>{if(this.isRunning){try{const e=n();this.setTime(e)}catch(e){console.error("TimelineSync: Error in continuous sync:",e)}this.animationFrameId=requestAnimationFrame(i)}};i()}stopContinuousSync(){this.isRunning=!1,this.animationFrameId!==null&&(cancelAnimationFrame(this.animationFrameId),this.animationFrameId=null)}destroy(){this.stopContinuousSync(),this.subscribers.clear(),this.currentTime=0}notifySubscribers(){for(const n of this.subscribers)try{n(this.currentTime)}catch(i){console.error("TimelineSync: Subscriber callback error:",i)}}}exports.TimelineSync=r;
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const k=require("./index6.cjs"),P=require("./index14.cjs"),m=require("./index13.cjs"),C={pen:{jitter:{amplitude:1,frequency:.08},pressure:{taperIn:.15,taperOut:.2},wobble:{amplitude:1.5,frequency:.05}},highlight:{color:"rgba(255, 255, 0, 0.3)",width:24,lineCap:"butt",jitter:{amplitude:0}},text:{color:"rgba(220, 20, 60, 1.0)",width:2,fontSize:16,lineCap:"round",jitter:{amplitude:0},pressure:{taperIn:0,taperOut:0}}};class y{constructor(t,r={}){if(!t||!(t instanceof HTMLCanvasElement))throw new Error("StrokeRenderer: canvas must be a valid HTMLCanvasElement");this.canvas=t,this.ctx=t.getContext("2d"),this.config=k.deepMerge(C,r),this.strokes=[],this.viewport={width:0,height:0},this.converters={highlight:m.highlightToStrokes,text:P.textToStrokes}}registerConverter(t,r){if(typeof r!="function")throw new Error("StrokeRenderer.registerConverter: converter must be a function");this.converters[t]=r}setViewport(t,r){this.viewport={width:t,height:r};const i=window.devicePixelRatio||1;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)}setAnnotations(t,r=null){if(!Array.isArray(t)){console.warn("StrokeRenderer.setAnnotations: annotations must be an array"),this.strokes=[];return}const i=r!==null?t.filter(e=>e.page===r):t;this.strokes=i.flatMap(e=>{const o=this.converters[e.type];if(!o)return console.warn(`StrokeRenderer: Unknown annotation type "${e.type}"`),[];const n=this._resolveStyle(e);return o(e,n)})}setStrokes(t){if(!Array.isArray(t)){console.warn("StrokeRenderer.setStrokes: strokes must be an array"),this.strokes=[];return}this.strokes=t}render(t){const{ctx:r,viewport:i,strokes:e}=this;r.clearRect(0,0,i.width,i.height);for(const o of e){if(t<o.start)continue;const n=o.end-o.start,h=t-o.start,s=n>0?Math.min(1,h/n):1;this._drawStroke(o,s)}}clear(){this.ctx.clearRect(0,0,this.viewport.width,this.viewport.height)}destroy(){this.strokes=[],this.ctx=null,this.canvas=null,this.config=null,this.converters=null}_resolveStyle(t){const{type:r,style:i}=t;let e={...this.config.pen};return this.config[r]&&(e=k.deepMerge(e,this.config[r])),i&&(e=k.deepMerge(e,i)),e}_interpolatePoint(t,r,i){return[t[0]+(r[0]-t[0])*i,t[1]+(r[1]-t[1])*i]}_interpolatePressures(t,r,i){const e=t.slice(0,r+1);if(r<t.length-1&&i>0){const o=t[r],n=t[r+1];e.push(o+(n-o)*i)}return e}_drawStroke(t,r){const{ctx:i}=this,{points:e,color:o,width:n,lineCap:h,pressures:s,uniformScale:g,baseX:l,baseY:c}=t;if(!e||e.length<2)return;if(i.strokeStyle=o||"rgba(0, 0, 0, 0.5)",i.lineCap=h||"round",i.lineJoin="round",r>=1){s&&s.length>=e.length?this._drawVariableWidthStroke(e,n,s,g,l,c):this._drawConstantWidthStroke(e,n,g,l,c);return}const a=e.length-1,d=r*a,u=Math.floor(d),p=d-u,f=e.slice(0,u+1);if(u<a&&p>0){const w=e[u],v=e[u+1];f.push(this._interpolatePoint(w,v,p))}if(!(f.length<2))if(s&&s.length>=e.length){const w=this._interpolatePressures(s,u,p);this._drawVariableWidthStroke(f,n,w,g,l,c)}else this._drawConstantWidthStroke(f,n,g,l,c)}_drawConstantWidthStroke(t,r,i=!1,e=null,o=null){const{ctx:n,viewport:h}=this;n.lineWidth=r||2,n.beginPath();const s=i&&e!==null&&o!==null;for(let g=0;g<t.length;g++){const[l,c]=t[g];let a,d;s?(a=e*h.width+l*h.height,d=o*h.height+c*h.height):i?(a=l*h.height,d=c*h.height):(a=l*h.width,d=c*h.height),g===0?n.moveTo(a,d):n.lineTo(a,d)}n.stroke()}_drawVariableWidthStroke(t,r,i,e=!1,o=null,n=null){const{ctx:h,viewport:s}=this,g=e&&o!==null&&n!==null;for(let l=1;l<t.length;l++){const[c,a]=t[l-1],[d,u]=t[l];let p,f,w,v;g?(p=o*s.width+c*s.height,f=n*s.height+a*s.height,w=o*s.width+d*s.height,v=n*s.height+u*s.height):e?(p=c*s.height,f=a*s.height,w=d*s.height,v=u*s.height):(p=c*s.width,f=a*s.height,w=d*s.width,v=u*s.height);const S=i[l-1]||1,x=i[l]||1,_=(S+x)/2,b=Math.max(.5,r*_);h.lineWidth=b,h.beginPath(),h.moveTo(p,f),h.lineTo(w,v),h.stroke()}}}exports.StrokeRenderer=y;exports.default=y;
2
2
  //# sourceMappingURL=index5.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index5.cjs","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":"gFA4BO,MAAMA,CAAa,CACxB,aAAc,CAKZ,KAAK,YAAc,EAMnB,KAAK,YAAc,IAAI,IAMvB,KAAK,iBAAmB,KAMxB,KAAK,UAAY,EACnB,CAQA,QAAQC,EAAW,CACbA,IAAc,KAAK,cAIvB,KAAK,YAAcA,EACnB,KAAK,kBAAiB,EACxB,CAOA,gBAAiB,CACf,OAAO,KAAK,WACd,CASA,UAAUC,EAAU,CAClB,GAAI,OAAOA,GAAa,WACtB,MAAM,IAAI,MAAM,qDAAqD,EAGvE,YAAK,YAAY,IAAIA,CAAQ,EAEtB,IAAM,KAAK,YAAYA,CAAQ,CACxC,CAQA,YAAYA,EAAU,CACpB,KAAK,YAAY,OAAOA,CAAQ,CAClC,CASA,oBAAoBC,EAAiB,CACnC,GAAI,OAAOA,GAAoB,WAC7B,MAAM,IAAI,MAAM,sEAAsE,EAGxF,GAAI,KAAK,UAAW,CAClB,QAAQ,KAAK,+CAA+C,EAC5D,MACF,CAEA,KAAK,UAAY,GAEjB,MAAMC,EAAW,IAAM,CACrB,GAAK,KAAK,UAIV,IAAI,CACF,MAAMC,EAAUF,EAAe,EAC/B,KAAK,QAAQE,CAAO,CACtB,OAASC,EAAK,CACZ,QAAQ,MAAM,0CAA2CA,CAAG,CAC9D,CAEA,KAAK,iBAAmB,sBAAsBF,CAAQ,EACxD,EAEAA,EAAQ,CACV,CAOA,oBAAqB,CACnB,KAAK,UAAY,GAEb,KAAK,mBAAqB,OAC5B,qBAAqB,KAAK,gBAAgB,EAC1C,KAAK,iBAAmB,KAE5B,CAOA,SAAU,CACR,KAAK,mBAAkB,EACvB,KAAK,YAAY,MAAK,EACtB,KAAK,YAAc,CACrB,CAQA,mBAAoB,CAClB,UAAWF,KAAY,KAAK,YAC1B,GAAI,CACFA,EAAS,KAAK,WAAW,CAC3B,OAASI,EAAK,CACZ,QAAQ,MAAM,2CAA4CA,CAAG,CAC/D,CAEJ,CACF"}
1
+ {"version":3,"file":"index5.cjs","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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"}