web-annotation-renderer 0.6.3 → 0.7.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 (112) hide show
  1. package/CHANGELOG.md +129 -0
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.js +62 -61
  5. package/dist/index.js.map +1 -1
  6. package/dist/index10.cjs +1 -1
  7. package/dist/index10.cjs.map +1 -1
  8. package/dist/index10.js +160 -13
  9. package/dist/index10.js.map +1 -1
  10. package/dist/index11.cjs +1 -1
  11. package/dist/index11.cjs.map +1 -1
  12. package/dist/index11.js +13 -50
  13. package/dist/index11.js.map +1 -1
  14. package/dist/index12.cjs +1 -1
  15. package/dist/index12.cjs.map +1 -1
  16. package/dist/index12.js +48 -155
  17. package/dist/index12.js.map +1 -1
  18. package/dist/index13.cjs +1 -1
  19. package/dist/index13.cjs.map +1 -1
  20. package/dist/index13.js +150 -34
  21. package/dist/index13.js.map +1 -1
  22. package/dist/index14.cjs +1 -1
  23. package/dist/index14.cjs.map +1 -1
  24. package/dist/index14.js +32 -65
  25. package/dist/index14.js.map +1 -1
  26. package/dist/index15.cjs +1 -1
  27. package/dist/index15.cjs.map +1 -1
  28. package/dist/index15.js +66 -33
  29. package/dist/index15.js.map +1 -1
  30. package/dist/index16.cjs +1 -1
  31. package/dist/index16.cjs.map +1 -1
  32. package/dist/index16.js +35 -77
  33. package/dist/index16.js.map +1 -1
  34. package/dist/index17.cjs +1 -1
  35. package/dist/index17.cjs.map +1 -1
  36. package/dist/index17.js +53 -28
  37. package/dist/index17.js.map +1 -1
  38. package/dist/index18.cjs +1 -1
  39. package/dist/index18.cjs.map +1 -1
  40. package/dist/index18.js +28 -22
  41. package/dist/index18.js.map +1 -1
  42. package/dist/index19.cjs +1 -1
  43. package/dist/index19.cjs.map +1 -1
  44. package/dist/index19.js +22 -117
  45. package/dist/index19.js.map +1 -1
  46. package/dist/index2.cjs +1 -1
  47. package/dist/index2.cjs.map +1 -1
  48. package/dist/index2.js +94 -98
  49. package/dist/index2.js.map +1 -1
  50. package/dist/index20.cjs +1 -1
  51. package/dist/index20.cjs.map +1 -1
  52. package/dist/index20.js +137 -100
  53. package/dist/index20.js.map +1 -1
  54. package/dist/index21.cjs +1 -1
  55. package/dist/index21.cjs.map +1 -1
  56. package/dist/index21.js +34 -76
  57. package/dist/index21.js.map +1 -1
  58. package/dist/index22.cjs +1 -1
  59. package/dist/index22.cjs.map +1 -1
  60. package/dist/index22.js +35 -139
  61. package/dist/index22.js.map +1 -1
  62. package/dist/index23.cjs +1 -1
  63. package/dist/index23.cjs.map +1 -1
  64. package/dist/index23.js +37 -37
  65. package/dist/index23.js.map +1 -1
  66. package/dist/index24.cjs +1 -1
  67. package/dist/index24.cjs.map +1 -1
  68. package/dist/index24.js +69 -37
  69. package/dist/index24.js.map +1 -1
  70. package/dist/index25.cjs +1 -1
  71. package/dist/index25.cjs.map +1 -1
  72. package/dist/index25.js +40 -38
  73. package/dist/index25.js.map +1 -1
  74. package/dist/index26.cjs +1 -1
  75. package/dist/index26.cjs.map +1 -1
  76. package/dist/index26.js +4 -39
  77. package/dist/index26.js.map +1 -1
  78. package/dist/index27.cjs +1 -1
  79. package/dist/index27.js +4 -4
  80. package/dist/index28.cjs +1 -1
  81. package/dist/index28.cjs.map +1 -1
  82. package/dist/index28.js +71 -5
  83. package/dist/index28.js.map +1 -1
  84. package/dist/index29.cjs +1 -1
  85. package/dist/index29.cjs.map +1 -1
  86. package/dist/index29.js +24 -69
  87. package/dist/index29.js.map +1 -1
  88. package/dist/index3.cjs +1 -1
  89. package/dist/index3.cjs.map +1 -1
  90. package/dist/index3.js +31 -31
  91. package/dist/index3.js.map +1 -1
  92. package/dist/index5.cjs +1 -1
  93. package/dist/index5.cjs.map +1 -1
  94. package/dist/index5.js +237 -190
  95. package/dist/index5.js.map +1 -1
  96. package/dist/index6.cjs +1 -1
  97. package/dist/index6.cjs.map +1 -1
  98. package/dist/index6.js +37 -19
  99. package/dist/index6.js.map +1 -1
  100. package/dist/index7.cjs +1 -1
  101. package/dist/index7.cjs.map +1 -1
  102. package/dist/index7.js +11 -17
  103. package/dist/index7.js.map +1 -1
  104. package/dist/index8.cjs +1 -1
  105. package/dist/index8.cjs.map +1 -1
  106. package/dist/index8.js +16 -125
  107. package/dist/index8.js.map +1 -1
  108. package/dist/index9.cjs +1 -1
  109. package/dist/index9.cjs.map +1 -1
  110. package/dist/index9.js +118 -201
  111. package/dist/index9.js.map +1 -1
  112. package/package.json +6 -3
package/dist/index2.js CHANGED
@@ -1,19 +1,20 @@
1
- import { PDFRenderer as n } from "./index3.js";
2
- import { TimelineSync as s } from "./index4.js";
3
- import { StrokeRenderer as i } from "./index5.js";
4
- class u {
1
+ import { PDFRenderer as o } from "./index3.js";
2
+ import { TimelineSync as a } from "./index4.js";
3
+ import { StrokeRenderer as u } from "./index5.js";
4
+ const r = (i) => ({ success: !1, error: i }), s = (i) => ({ success: !0, ...i || {} }), n = "AnnotationRenderer has been destroyed";
5
+ class l {
5
6
  /**
6
- * Create AnnotationRenderer instance
7
- *
8
- * @param {Object} config - Configuration object
9
- * @param {HTMLElement} config.container - DOM element for annotation canvas
10
- * @param {HTMLCanvasElement} config.canvasElement - Canvas element for PDF rendering
11
- * @param {string} [config.pdfUrl] - PDF URL to load immediately
12
- * @param {number} [config.initialPage=1] - Initial page number
13
- * @param {number} [config.initialScale=1.0] - Initial scale factor
14
- * @param {Array} [config.annotations=[]] - Initial annotation data
15
- * @param {Object} [config.strokeConfig] - StrokeRenderer configuration
16
- * @throws {Error} If config is invalid or required elements are missing
7
+ * Create AnnotationRenderer instance.
8
+ *
9
+ * @param {Object} config
10
+ * @param {HTMLElement} config.container DOM element for the stroke canvas
11
+ * @param {HTMLCanvasElement} config.canvasElement Canvas for PDF page rendering
12
+ * @param {string} [config.pdfUrl] PDF URL to auto-load
13
+ * @param {number} [config.initialPage=1]
14
+ * @param {number} [config.initialScale=1.0]
15
+ * @param {Array} [config.annotations=[]]
16
+ * @param {Object} [config.strokeConfig]
17
+ * @throws {Error} when config is missing required DOM elements
17
18
  */
18
19
  constructor(e) {
19
20
  if (!e || typeof e != "object")
@@ -22,165 +23,162 @@ class u {
22
23
  throw new Error("AnnotationRenderer: config.container must be a valid DOM element");
23
24
  if (!e.canvasElement || !(e.canvasElement instanceof HTMLCanvasElement))
24
25
  throw new Error("AnnotationRenderer: config.canvasElement must be a valid canvas element");
25
- this.config = e, this.canvasElement = e.canvasElement, this.container = e.container, this.pdfRenderer = new n(), this.strokeCanvas = this._createStrokeCanvas(), this.strokeRenderer = new i(this.strokeCanvas, e.strokeConfig || {}), this.timelineSync = new s(), this.currentPage = e.initialPage || 1, this.currentScale = e.initialScale || 1, this.annotations = e.annotations || [], this.pageCount = 0, this.currentViewport = null, this.pdfUrl = null, this.timelineSync.subscribe((t) => {
26
+ this.config = e, this.canvasElement = e.canvasElement, this.container = e.container, this.pdfRenderer = new o(), this.strokeCanvas = this._createStrokeCanvas(), this.strokeRenderer = new u(this.strokeCanvas, e.strokeConfig || {}), this.timelineSync = new a(), this.currentPage = e.initialPage || 1, this.currentScale = e.initialScale || 1, this.annotations = e.annotations || [], this.pageCount = 0, this.currentViewport = null, this.pdfUrl = null, this.timelineSync.subscribe((t) => {
26
27
  this.strokeRenderer.render(t);
27
- }), e.pdfUrl && this.loadPDF(e.pdfUrl).catch((t) => {
28
- console.error("AnnotationRenderer: Failed to auto-load PDF:", t);
28
+ }), e.pdfUrl && this.loadPDF(e.pdfUrl).then((t) => {
29
+ t.success || console.error("AnnotationRenderer: Failed to auto-load PDF:", t.error);
29
30
  });
30
31
  }
31
32
  /**
32
- * Create stroke canvas overlay
33
+ * Whether the renderer has been torn down via `destroy()`. After destroy,
34
+ * the subsystem references are nulled — every public method consults this
35
+ * predicate first and returns a Result-shaped error instead of throwing
36
+ * a TypeError on null deref. (T17b — DA H1)
33
37
  *
34
38
  * @private
35
- * @returns {HTMLCanvasElement} Stroke canvas element
39
+ * @returns {boolean}
40
+ */
41
+ _isDestroyed() {
42
+ return !this.strokeRenderer || !this.timelineSync || !this.pdfRenderer;
43
+ }
44
+ /**
45
+ * Create the absolute-positioned overlay canvas used for stroke rendering.
46
+ * @private
36
47
  */
37
48
  _createStrokeCanvas() {
38
49
  const e = document.createElement("canvas");
39
50
  return e.className = "stroke-canvas", e.style.position = "absolute", e.style.top = "0", e.style.left = "0", e.style.pointerEvents = "none", e.style.zIndex = "10", this.container.appendChild(e), e;
40
51
  }
41
52
  /**
42
- * Load PDF document from URL
53
+ * Re-render the current frame after a state mutation.
54
+ * @private
55
+ */
56
+ _renderCurrentFrame() {
57
+ this.strokeRenderer.render(this.timelineSync.getCurrentTime());
58
+ }
59
+ /**
60
+ * Load a PDF document.
43
61
  *
44
- * @param {string} url - URL or path to PDF file
45
- * @returns {Promise<Object>} Load result with success status and page count
62
+ * @param {string} url
63
+ * @returns {Promise<{success: boolean, pageCount?: number, error?: string}>}
46
64
  */
47
65
  async loadPDF(e) {
48
66
  try {
49
- if (!e || typeof e != "string")
50
- return { success: !1, error: "Invalid PDF URL provided" };
67
+ if (this._isDestroyed()) return r(n);
68
+ if (!e || typeof e != "string") return r("Invalid PDF URL provided");
51
69
  const t = await this.pdfRenderer.loadDocument(e);
52
- return t.success ? (this.pdfUrl = e, this.pageCount = t.pageCount, { success: !0, pageCount: t.pageCount }) : t;
70
+ return t.success ? (this.pdfUrl = e, this.pageCount = t.pageCount, s({ pageCount: t.pageCount })) : t;
53
71
  } catch (t) {
54
- return console.error("AnnotationRenderer.loadPDF: Error loading PDF:", t), { success: !1, error: `Failed to load PDF: ${t.message}` };
72
+ return console.error("AnnotationRenderer.loadPDF: Error loading PDF:", t), r(`Failed to load PDF: ${t.message}`);
55
73
  }
56
74
  }
57
75
  /**
58
- * Navigate to specific page and render it
76
+ * Render a specific page on the PDF canvas and resize the stroke canvas to
77
+ * match the new viewport. Per v0.7.0 (R18) this method NO LONGER touches
78
+ * stroke annotations — adapters must call `setAnnotations` afterwards if
79
+ * the annotation set should be re-applied for the new page.
59
80
  *
60
- * @param {number} pageNum - Page number (1-indexed)
61
- * @returns {Promise<Object>} Render result with viewport information
81
+ * @param {number} pageNum 1-indexed page number
82
+ * @returns {Promise<{success: boolean, viewport?: Object, error?: string}>}
62
83
  */
63
84
  async setPage(e) {
64
85
  try {
65
- if (typeof e != "number" || e < 1)
66
- return { success: !1, error: "Invalid page number" };
86
+ if (this._isDestroyed()) return r(n);
87
+ if (typeof e != "number" || e < 1) return r("Invalid page number");
67
88
  if (this.pageCount > 0 && e > this.pageCount)
68
- return { success: !1, error: `Page ${e} exceeds document page count (${this.pageCount})` };
89
+ return r(`Page ${e} exceeds document page count (${this.pageCount})`);
69
90
  this.pdfRenderer.cancelRender();
70
91
  const t = await this.pdfRenderer.renderPage(
71
92
  e,
72
93
  this.canvasElement,
73
94
  this.currentScale
74
95
  );
75
- if (t.success) {
76
- this.currentPage = e, this.currentViewport = t.viewport, this.strokeRenderer.setViewport(t.viewport.width, t.viewport.height), this.strokeRenderer.setAnnotations(this.annotations, e);
77
- const r = this.timelineSync.getCurrentTime();
78
- return this.strokeRenderer.render(r), { success: !0, viewport: t.viewport };
79
- }
80
- return t;
96
+ return t.success ? (this.currentPage = e, this.currentViewport = t.viewport, this.strokeRenderer.setViewport(t.viewport.width, t.viewport.height), this._renderCurrentFrame(), s({ viewport: t.viewport })) : t;
81
97
  } catch (t) {
82
- return console.error("AnnotationRenderer.setPage: Error rendering page:", t), { success: !1, error: `Failed to render page: ${t.message}` };
98
+ return console.error("AnnotationRenderer.setPage: Error rendering page:", t), r(`Failed to render page: ${t.message}`);
83
99
  }
84
100
  }
85
101
  /**
86
- * Change zoom scale and re-render current page
102
+ * Change the zoom scale and re-render the current page.
87
103
  *
88
- * @param {number} scale - Scale factor (e.g., 1.0, 1.5, 2.0)
89
- * @returns {Promise<Object>} Render result with viewport information
104
+ * @param {number} scale Positive scale factor
105
+ * @returns {Promise<{success: boolean, viewport?: Object, error?: string}>}
90
106
  */
91
107
  async setScale(e) {
92
108
  try {
93
- return typeof e != "number" || e <= 0 ? { success: !1, error: "Invalid scale value (must be positive number)" } : (this.currentScale = e, await this.setPage(this.currentPage));
109
+ return this._isDestroyed() ? r(n) : typeof e != "number" || e <= 0 ? r("Invalid scale value (must be positive number)") : (this.currentScale = e, await this.setPage(this.currentPage));
94
110
  } catch (t) {
95
- return console.error("AnnotationRenderer.setScale: Error changing scale:", t), { success: !1, error: `Failed to change scale: ${t.message}` };
111
+ return console.error("AnnotationRenderer.setScale: Error changing scale:", t), r(`Failed to change scale: ${t.message}`);
96
112
  }
97
113
  }
98
114
  /**
99
- * Update annotation data for rendering
115
+ * Replace the annotation set and re-render at the current time.
100
116
  *
101
- * @param {Array} annotations - Complete annotation array (all pages, all types)
117
+ * @param {Array} annotations
118
+ * @returns {{success: boolean, error?: string}}
102
119
  */
103
120
  setAnnotations(e) {
104
- Array.isArray(e) || (console.warn("AnnotationRenderer.setAnnotations: annotations must be an array"), e = []), this.annotations = e, this.strokeRenderer.setAnnotations(e, this.currentPage);
105
- const t = this.timelineSync.getCurrentTime();
106
- this.strokeRenderer.render(t);
121
+ return this._isDestroyed() ? r(n) : Array.isArray(e) ? (this.annotations = e, this.strokeRenderer.setAnnotations(e, this.currentPage), this._renderCurrentFrame(), s()) : r("annotations must be an array");
107
122
  }
108
123
  /**
109
- * Set pre-converted strokes directly
124
+ * Inject pre-converted strokes (skips annotation conversion).
110
125
  *
111
- * Bypasses annotation conversion, useful for stroke commands from backend.
112
- *
113
- * @param {Array} strokes - Array of stroke command objects
126
+ * @param {Array} strokes
127
+ * @returns {{success: boolean, error?: string}}
114
128
  */
115
129
  setStrokes(e) {
116
- this.strokeRenderer.setStrokes(e);
117
- const t = this.timelineSync.getCurrentTime();
118
- this.strokeRenderer.render(t);
130
+ return this._isDestroyed() ? r(n) : Array.isArray(e) ? (this.strokeRenderer.setStrokes(e), this._renderCurrentFrame(), s()) : r("strokes must be an array");
119
131
  }
120
132
  /**
121
- * Update timeline position for animation
133
+ * Manually set the current timeline position.
122
134
  *
123
- * @param {number} timestamp - Current timeline position in seconds
135
+ * @param {number} timestamp Seconds
136
+ * @returns {{success: boolean, error?: string}}
124
137
  */
125
138
  setTime(e) {
126
- if (typeof e != "number") {
127
- console.warn("AnnotationRenderer.setTime: timestamp must be a number");
128
- return;
129
- }
130
- this.timelineSync.setTime(e);
139
+ return this._isDestroyed() ? r(n) : typeof e != "number" ? r("timestamp must be a number") : (this.timelineSync.setTime(e), s());
131
140
  }
132
141
  /**
133
- * Start continuous timeline synchronization using requestAnimationFrame
142
+ * Start RAF-driven continuous timeline sync. Use this for audio/video
143
+ * playback; the supplied function is polled at ~60fps.
134
144
  *
135
- * Polls the provided function at ~60fps for smooth animation.
136
- * Use this for audio/video synchronization instead of setTime().
145
+ * Silently no-ops if the renderer has been destroyed.
137
146
  *
138
- * @param {Function} getTimeFunction - Function that returns current time in seconds
139
- * @example
140
- * const audio = document.getElementById('audio');
141
- * renderer.startContinuousSync(() => audio.currentTime);
147
+ * @param {() => number} getTimeFunction
142
148
  */
143
149
  startContinuousSync(e) {
144
- this.timelineSync.startContinuousSync(e);
150
+ this._isDestroyed() || this.timelineSync.startContinuousSync(e);
145
151
  }
146
152
  /**
147
- * Stop continuous timeline synchronization
153
+ * Stop the RAF-driven sync started by `startContinuousSync`.
148
154
  *
149
- * Call this when audio/video stops or component unmounts.
155
+ * Silently no-ops if the renderer has been destroyed.
150
156
  */
151
157
  stopContinuousSync() {
152
- this.timelineSync.stopContinuousSync();
158
+ this._isDestroyed() || this.timelineSync.stopContinuousSync();
153
159
  }
154
- /**
155
- * Check if continuous sync is currently active
156
- *
157
- * @returns {boolean} True if continuous sync is running
158
- */
160
+ /** @returns {boolean} whether continuous sync is currently running (false if destroyed) */
159
161
  isContinuousSyncActive() {
160
- return this.timelineSync.isRunning;
162
+ return this._isDestroyed() ? !1 : this.timelineSync.isRunning;
161
163
  }
162
164
  /**
163
- * Update stroke rendering configuration at runtime
165
+ * Update stroke rendering configuration at runtime and re-convert
166
+ * annotations with the new style.
164
167
  *
165
- * Updates config via setConfig and re-renders annotations.
166
- * Used for live preview of pen style changes.
167
- *
168
- * @param {Object} newConfig - New stroke configuration
168
+ * @param {Object} newConfig
169
+ * @returns {{success: boolean, error?: string}}
169
170
  */
170
171
  updateStrokeConfig(e) {
171
- if (!e || typeof e != "object")
172
- return;
173
- this.strokeRenderer.setConfig(e), this.strokeRenderer.setAnnotations(this.annotations, this.currentPage);
174
- const t = this.timelineSync.getCurrentTime();
175
- this.strokeRenderer.render(t);
172
+ return this._isDestroyed() ? r(n) : !e || typeof e != "object" ? r("newConfig must be an object") : (this.strokeRenderer.setConfig(e), this.strokeRenderer.setAnnotations(this.annotations, this.currentPage), this._renderCurrentFrame(), s());
176
173
  }
177
174
  /**
178
- * Get current engine state snapshot
175
+ * Snapshot of the current engine state — useful for debug overlays.
179
176
  *
180
- * @returns {Object} Current state
177
+ * @returns {Object|null} state snapshot, or `null` if the renderer has
178
+ * been destroyed.
181
179
  */
182
180
  getState() {
183
- return {
181
+ return this._isDestroyed() ? null : {
184
182
  page: this.currentPage,
185
183
  scale: this.currentScale,
186
184
  annotations: this.annotations,
@@ -190,14 +188,12 @@ class u {
190
188
  pdfUrl: this.pdfUrl
191
189
  };
192
190
  }
193
- /**
194
- * Clean up all resources and subsystems
195
- */
191
+ /** Tear down all subsystems and release references. */
196
192
  destroy() {
197
193
  this.pdfRenderer && this.pdfRenderer.destroy(), this.strokeRenderer && this.strokeRenderer.destroy(), this.timelineSync && this.timelineSync.destroy(), this.strokeCanvas && this.strokeCanvas.parentNode && this.strokeCanvas.parentNode.removeChild(this.strokeCanvas), this.pdfRenderer = null, this.strokeRenderer = null, this.timelineSync = null, this.strokeCanvas = null, this.config = null, this.canvasElement = null, this.container = null, this.annotations = [], this.currentPage = 0, this.currentScale = 1, this.pageCount = 0, this.currentViewport = null, this.pdfUrl = null;
198
194
  }
199
195
  }
200
196
  export {
201
- u as AnnotationRenderer
197
+ l as AnnotationRenderer
202
198
  };
203
199
  //# sourceMappingURL=index2.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index2.js","sources":["../src/core/AnnotationRenderer.js"],"sourcesContent":["/**\n * AnnotationRenderer - Main facade for PDF annotation rendering engine\n *\n * Orchestrates PDFRenderer, StrokeRenderer, and TimelineSync subsystems.\n * Provides the primary public API for rendering PDFs with timeline-synchronized\n * annotations. Framework-agnostic core that can be wrapped by React/Vue adapters.\n *\n * @module core/AnnotationRenderer\n */\n\nimport { PDFRenderer } from './PDFRenderer.js';\nimport { TimelineSync } from './TimelineSync.js';\nimport { StrokeRenderer } from '../renderer/StrokeRenderer.js';\n\n/**\n * AnnotationRenderer class\n *\n * Main engine that coordinates PDF rendering, stroke-based annotation rendering,\n * and timeline synchronization. Provides simple imperative API for consumers.\n *\n * @class\n * @example\n * const renderer = new AnnotationRenderer({\n * container: document.getElementById('container'),\n * canvasElement: document.getElementById('pdf-canvas')\n * });\n *\n * await renderer.loadPDF('/path/to/doc.pdf');\n * await renderer.setPage(1);\n * renderer.setAnnotations(annotationData);\n * renderer.setTime(3.5);\n */\nexport class AnnotationRenderer {\n /**\n * Create AnnotationRenderer instance\n *\n * @param {Object} config - Configuration object\n * @param {HTMLElement} config.container - DOM element for annotation canvas\n * @param {HTMLCanvasElement} config.canvasElement - Canvas element for PDF rendering\n * @param {string} [config.pdfUrl] - PDF URL to load immediately\n * @param {number} [config.initialPage=1] - Initial page number\n * @param {number} [config.initialScale=1.0] - Initial scale factor\n * @param {Array} [config.annotations=[]] - Initial annotation data\n * @param {Object} [config.strokeConfig] - StrokeRenderer configuration\n * @throws {Error} If config is invalid or required elements are missing\n */\n constructor(config) {\n if (!config || typeof config !== 'object') {\n throw new Error('AnnotationRenderer: config object is required');\n }\n\n if (!config.container || !(config.container instanceof HTMLElement)) {\n throw new Error('AnnotationRenderer: config.container must be a valid DOM element');\n }\n\n if (!config.canvasElement || !(config.canvasElement instanceof HTMLCanvasElement)) {\n throw new Error('AnnotationRenderer: config.canvasElement must be a valid canvas element');\n }\n\n this.config = config;\n this.canvasElement = config.canvasElement;\n this.container = config.container;\n\n // Initialize PDFRenderer\n this.pdfRenderer = new PDFRenderer();\n\n // Create stroke canvas and StrokeRenderer\n this.strokeCanvas = this._createStrokeCanvas();\n this.strokeRenderer = new StrokeRenderer(this.strokeCanvas, config.strokeConfig || {});\n\n // Initialize TimelineSync\n this.timelineSync = new TimelineSync();\n\n // State\n this.currentPage = config.initialPage || 1;\n this.currentScale = config.initialScale || 1.0;\n this.annotations = config.annotations || [];\n this.pageCount = 0;\n this.currentViewport = null;\n this.pdfUrl = null;\n\n // Wire up timeline to render\n this.timelineSync.subscribe((time) => {\n this.strokeRenderer.render(time);\n });\n\n // Auto-load PDF if provided\n if (config.pdfUrl) {\n this.loadPDF(config.pdfUrl).catch(err => {\n console.error('AnnotationRenderer: Failed to auto-load PDF:', err);\n });\n }\n }\n\n /**\n * Create stroke canvas overlay\n *\n * @private\n * @returns {HTMLCanvasElement} Stroke canvas element\n */\n _createStrokeCanvas() {\n const canvas = document.createElement('canvas');\n canvas.className = 'stroke-canvas';\n canvas.style.position = 'absolute';\n canvas.style.top = '0';\n canvas.style.left = '0';\n canvas.style.pointerEvents = 'none';\n canvas.style.zIndex = '10';\n this.container.appendChild(canvas);\n return canvas;\n }\n\n /**\n * Load PDF document from URL\n *\n * @param {string} url - URL or path to PDF file\n * @returns {Promise<Object>} Load result with success status and page count\n */\n async loadPDF(url) {\n try {\n if (!url || typeof url !== 'string') {\n return { success: false, error: 'Invalid PDF URL provided' };\n }\n\n const result = await this.pdfRenderer.loadDocument(url);\n\n if (result.success) {\n this.pdfUrl = url;\n this.pageCount = result.pageCount;\n return { success: true, pageCount: result.pageCount };\n }\n\n return result;\n } catch (err) {\n console.error('AnnotationRenderer.loadPDF: Error loading PDF:', err);\n return { success: false, error: `Failed to load PDF: ${err.message}` };\n }\n }\n\n /**\n * Navigate to specific page and render it\n *\n * @param {number} pageNum - Page number (1-indexed)\n * @returns {Promise<Object>} Render result with viewport information\n */\n async setPage(pageNum) {\n try {\n if (typeof pageNum !== 'number' || pageNum < 1) {\n return { success: false, error: 'Invalid page number' };\n }\n\n if (this.pageCount > 0 && pageNum > this.pageCount) {\n return { success: false, error: `Page ${pageNum} exceeds document page count (${this.pageCount})` };\n }\n\n this.pdfRenderer.cancelRender();\n\n const result = await this.pdfRenderer.renderPage(\n pageNum,\n this.canvasElement,\n this.currentScale\n );\n\n if (result.success) {\n this.currentPage = pageNum;\n this.currentViewport = result.viewport;\n\n // Update StrokeRenderer viewport\n this.strokeRenderer.setViewport(result.viewport.width, result.viewport.height);\n\n // Re-set annotations for new page\n this.strokeRenderer.setAnnotations(this.annotations, pageNum);\n\n // Render at current time\n const currentTime = this.timelineSync.getCurrentTime();\n this.strokeRenderer.render(currentTime);\n\n return { success: true, viewport: result.viewport };\n }\n\n return result;\n } catch (err) {\n console.error('AnnotationRenderer.setPage: Error rendering page:', err);\n return { success: false, error: `Failed to render page: ${err.message}` };\n }\n }\n\n /**\n * Change zoom scale and re-render current page\n *\n * @param {number} scale - Scale factor (e.g., 1.0, 1.5, 2.0)\n * @returns {Promise<Object>} Render result with viewport information\n */\n async setScale(scale) {\n try {\n if (typeof scale !== 'number' || scale <= 0) {\n return { success: false, error: 'Invalid scale value (must be positive number)' };\n }\n\n this.currentScale = scale;\n return await this.setPage(this.currentPage);\n } catch (err) {\n console.error('AnnotationRenderer.setScale: Error changing scale:', err);\n return { success: false, error: `Failed to change scale: ${err.message}` };\n }\n }\n\n /**\n * Update annotation data for rendering\n *\n * @param {Array} annotations - Complete annotation array (all pages, all types)\n */\n setAnnotations(annotations) {\n if (!Array.isArray(annotations)) {\n console.warn('AnnotationRenderer.setAnnotations: annotations must be an array');\n annotations = [];\n }\n\n this.annotations = annotations;\n this.strokeRenderer.setAnnotations(annotations, this.currentPage);\n\n // Render at current time\n const currentTime = this.timelineSync.getCurrentTime();\n this.strokeRenderer.render(currentTime);\n }\n\n /**\n * Set pre-converted strokes directly\n *\n * Bypasses annotation conversion, useful for stroke commands from backend.\n *\n * @param {Array} strokes - Array of stroke command objects\n */\n setStrokes(strokes) {\n this.strokeRenderer.setStrokes(strokes);\n\n const currentTime = this.timelineSync.getCurrentTime();\n this.strokeRenderer.render(currentTime);\n }\n\n /**\n * Update timeline position for animation\n *\n * @param {number} timestamp - Current timeline position in seconds\n */\n setTime(timestamp) {\n if (typeof timestamp !== 'number') {\n console.warn('AnnotationRenderer.setTime: timestamp must be a number');\n return;\n }\n\n this.timelineSync.setTime(timestamp);\n }\n\n /**\n * Start continuous timeline synchronization using requestAnimationFrame\n *\n * Polls the provided function at ~60fps for smooth animation.\n * Use this for audio/video synchronization instead of setTime().\n *\n * @param {Function} getTimeFunction - Function that returns current time in seconds\n * @example\n * const audio = document.getElementById('audio');\n * renderer.startContinuousSync(() => audio.currentTime);\n */\n startContinuousSync(getTimeFunction) {\n this.timelineSync.startContinuousSync(getTimeFunction);\n }\n\n /**\n * Stop continuous timeline synchronization\n *\n * Call this when audio/video stops or component unmounts.\n */\n stopContinuousSync() {\n this.timelineSync.stopContinuousSync();\n }\n\n /**\n * Check if continuous sync is currently active\n *\n * @returns {boolean} True if continuous sync is running\n */\n isContinuousSyncActive() {\n return this.timelineSync.isRunning;\n }\n\n /**\n * Update stroke rendering configuration at runtime\n *\n * Updates config via setConfig and re-renders annotations.\n * Used for live preview of pen style changes.\n *\n * @param {Object} newConfig - New stroke configuration\n */\n updateStrokeConfig(newConfig) {\n if (!newConfig || typeof newConfig !== 'object') {\n return;\n }\n\n // Update config via setConfig\n this.strokeRenderer.setConfig(newConfig);\n\n // Re-convert annotations with new style and render\n this.strokeRenderer.setAnnotations(this.annotations, this.currentPage);\n const currentTime = this.timelineSync.getCurrentTime();\n this.strokeRenderer.render(currentTime);\n }\n\n /**\n * Get current engine state snapshot\n *\n * @returns {Object} Current state\n */\n getState() {\n return {\n page: this.currentPage,\n scale: this.currentScale,\n annotations: this.annotations,\n pageCount: this.pageCount,\n time: this.timelineSync.getCurrentTime(),\n viewport: this.currentViewport,\n pdfUrl: this.pdfUrl\n };\n }\n\n /**\n * Clean up all resources and subsystems\n */\n destroy() {\n if (this.pdfRenderer) {\n this.pdfRenderer.destroy();\n }\n\n if (this.strokeRenderer) {\n this.strokeRenderer.destroy();\n }\n\n if (this.timelineSync) {\n this.timelineSync.destroy();\n }\n\n if (this.strokeCanvas && this.strokeCanvas.parentNode) {\n this.strokeCanvas.parentNode.removeChild(this.strokeCanvas);\n }\n\n this.pdfRenderer = null;\n this.strokeRenderer = null;\n this.timelineSync = null;\n this.strokeCanvas = null;\n this.config = null;\n this.canvasElement = null;\n this.container = null;\n this.annotations = [];\n this.currentPage = 0;\n this.currentScale = 1.0;\n this.pageCount = 0;\n this.currentViewport = null;\n this.pdfUrl = null;\n }\n}\n"],"names":["AnnotationRenderer","config","PDFRenderer","StrokeRenderer","TimelineSync","time","err","canvas","url","result","pageNum","currentTime","scale","annotations","strokes","timestamp","getTimeFunction","newConfig"],"mappings":"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"}
1
+ {"version":3,"file":"index2.js","sources":["../src/core/AnnotationRenderer.js"],"sourcesContent":["/**\n * AnnotationRenderer - Main facade for the PDF annotation rendering engine.\n *\n * Orchestrates PDFRenderer, StrokeRenderer, and TimelineSync. Framework-\n * agnostic core that React/Vue/Svelte adapters wrap.\n *\n * Error-handling contract (v0.7.0): every public method returns\n * `{ success: boolean, error?: string, ...payload }`\n * The constructor is the only entry point that throws.\n *\n * @module core/AnnotationRenderer\n */\n\nimport { PDFRenderer } from './PDFRenderer.js';\nimport { TimelineSync } from './TimelineSync.js';\nimport { StrokeRenderer } from '../renderer/StrokeRenderer.js';\n\n/** Standard failure shape for public methods. */\nconst fail = (error) => ({ success: false, error });\n/** Standard success shape (with optional payload merged in). */\nconst ok = (payload) => ({ success: true, ...(payload || {}) });\n\n/** Sentinel error returned by every public method invoked after `destroy()`. */\nconst DESTROYED_ERROR = 'AnnotationRenderer has been destroyed';\n\nexport class AnnotationRenderer {\n /**\n * Create AnnotationRenderer instance.\n *\n * @param {Object} config\n * @param {HTMLElement} config.container DOM element for the stroke canvas\n * @param {HTMLCanvasElement} config.canvasElement Canvas for PDF page rendering\n * @param {string} [config.pdfUrl] PDF URL to auto-load\n * @param {number} [config.initialPage=1]\n * @param {number} [config.initialScale=1.0]\n * @param {Array} [config.annotations=[]]\n * @param {Object} [config.strokeConfig]\n * @throws {Error} when config is missing required DOM elements\n */\n constructor(config) {\n if (!config || typeof config !== 'object') {\n throw new Error('AnnotationRenderer: config object is required');\n }\n if (!config.container || !(config.container instanceof HTMLElement)) {\n throw new Error('AnnotationRenderer: config.container must be a valid DOM element');\n }\n if (!config.canvasElement || !(config.canvasElement instanceof HTMLCanvasElement)) {\n throw new Error('AnnotationRenderer: config.canvasElement must be a valid canvas element');\n }\n\n this.config = config;\n this.canvasElement = config.canvasElement;\n this.container = config.container;\n\n this.pdfRenderer = new PDFRenderer();\n this.strokeCanvas = this._createStrokeCanvas();\n this.strokeRenderer = new StrokeRenderer(this.strokeCanvas, config.strokeConfig || {});\n this.timelineSync = new TimelineSync();\n\n this.currentPage = config.initialPage || 1;\n this.currentScale = config.initialScale || 1.0;\n this.annotations = config.annotations || [];\n this.pageCount = 0;\n this.currentViewport = null;\n this.pdfUrl = null;\n\n // Wire timeline ticks → renderer.render\n this.timelineSync.subscribe((time) => {\n this.strokeRenderer.render(time);\n });\n\n // Auto-load PDF if provided. Errors are surfaced via console; callers\n // who want explicit error handling should call loadPDF themselves.\n if (config.pdfUrl) {\n this.loadPDF(config.pdfUrl).then((result) => {\n if (!result.success) {\n console.error('AnnotationRenderer: Failed to auto-load PDF:', result.error);\n }\n });\n }\n }\n\n /**\n * Whether the renderer has been torn down via `destroy()`. After destroy,\n * the subsystem references are nulled — every public method consults this\n * predicate first and returns a Result-shaped error instead of throwing\n * a TypeError on null deref. (T17b — DA H1)\n *\n * @private\n * @returns {boolean}\n */\n _isDestroyed() {\n return (\n !this.strokeRenderer || !this.timelineSync || !this.pdfRenderer\n );\n }\n\n /**\n * Create the absolute-positioned overlay canvas used for stroke rendering.\n * @private\n */\n _createStrokeCanvas() {\n const canvas = document.createElement('canvas');\n canvas.className = 'stroke-canvas';\n canvas.style.position = 'absolute';\n canvas.style.top = '0';\n canvas.style.left = '0';\n canvas.style.pointerEvents = 'none';\n canvas.style.zIndex = '10';\n this.container.appendChild(canvas);\n return canvas;\n }\n\n /**\n * Re-render the current frame after a state mutation.\n * @private\n */\n _renderCurrentFrame() {\n this.strokeRenderer.render(this.timelineSync.getCurrentTime());\n }\n\n /**\n * Load a PDF document.\n *\n * @param {string} url\n * @returns {Promise<{success: boolean, pageCount?: number, error?: string}>}\n */\n async loadPDF(url) {\n try {\n if (this._isDestroyed()) return fail(DESTROYED_ERROR);\n if (!url || typeof url !== 'string') return fail('Invalid PDF URL provided');\n\n const result = await this.pdfRenderer.loadDocument(url);\n if (!result.success) return result;\n\n this.pdfUrl = url;\n this.pageCount = result.pageCount;\n return ok({ pageCount: result.pageCount });\n } catch (err) {\n console.error('AnnotationRenderer.loadPDF: Error loading PDF:', err);\n return fail(`Failed to load PDF: ${err.message}`);\n }\n }\n\n /**\n * Render a specific page on the PDF canvas and resize the stroke canvas to\n * match the new viewport. Per v0.7.0 (R18) this method NO LONGER touches\n * stroke annotations — adapters must call `setAnnotations` afterwards if\n * the annotation set should be re-applied for the new page.\n *\n * @param {number} pageNum 1-indexed page number\n * @returns {Promise<{success: boolean, viewport?: Object, error?: string}>}\n */\n async setPage(pageNum) {\n try {\n if (this._isDestroyed()) return fail(DESTROYED_ERROR);\n if (typeof pageNum !== 'number' || pageNum < 1) return fail('Invalid page number');\n if (this.pageCount > 0 && pageNum > this.pageCount) {\n return fail(`Page ${pageNum} exceeds document page count (${this.pageCount})`);\n }\n\n this.pdfRenderer.cancelRender();\n\n const result = await this.pdfRenderer.renderPage(\n pageNum,\n this.canvasElement,\n this.currentScale,\n );\n if (!result.success) return result;\n\n this.currentPage = pageNum;\n this.currentViewport = result.viewport;\n this.strokeRenderer.setViewport(result.viewport.width, result.viewport.height);\n\n // Note: we deliberately do NOT call setAnnotations here. Adapters are\n // responsible for re-binding annotations after a page change so that\n // they can pick the right page filter / validation options.\n this._renderCurrentFrame();\n\n return ok({ viewport: result.viewport });\n } catch (err) {\n console.error('AnnotationRenderer.setPage: Error rendering page:', err);\n return fail(`Failed to render page: ${err.message}`);\n }\n }\n\n /**\n * Change the zoom scale and re-render the current page.\n *\n * @param {number} scale Positive scale factor\n * @returns {Promise<{success: boolean, viewport?: Object, error?: string}>}\n */\n async setScale(scale) {\n try {\n if (this._isDestroyed()) return fail(DESTROYED_ERROR);\n if (typeof scale !== 'number' || scale <= 0) {\n return fail('Invalid scale value (must be positive number)');\n }\n this.currentScale = scale;\n return await this.setPage(this.currentPage);\n } catch (err) {\n console.error('AnnotationRenderer.setScale: Error changing scale:', err);\n return fail(`Failed to change scale: ${err.message}`);\n }\n }\n\n /**\n * Replace the annotation set and re-render at the current time.\n *\n * @param {Array} annotations\n * @returns {{success: boolean, error?: string}}\n */\n setAnnotations(annotations) {\n if (this._isDestroyed()) return fail(DESTROYED_ERROR);\n if (!Array.isArray(annotations)) {\n return fail('annotations must be an array');\n }\n this.annotations = annotations;\n this.strokeRenderer.setAnnotations(annotations, this.currentPage);\n this._renderCurrentFrame();\n return ok();\n }\n\n /**\n * Inject pre-converted strokes (skips annotation conversion).\n *\n * @param {Array} strokes\n * @returns {{success: boolean, error?: string}}\n */\n setStrokes(strokes) {\n if (this._isDestroyed()) return fail(DESTROYED_ERROR);\n if (!Array.isArray(strokes)) return fail('strokes must be an array');\n this.strokeRenderer.setStrokes(strokes);\n this._renderCurrentFrame();\n return ok();\n }\n\n /**\n * Manually set the current timeline position.\n *\n * @param {number} timestamp Seconds\n * @returns {{success: boolean, error?: string}}\n */\n setTime(timestamp) {\n if (this._isDestroyed()) return fail(DESTROYED_ERROR);\n if (typeof timestamp !== 'number') return fail('timestamp must be a number');\n this.timelineSync.setTime(timestamp);\n return ok();\n }\n\n /**\n * Start RAF-driven continuous timeline sync. Use this for audio/video\n * playback; the supplied function is polled at ~60fps.\n *\n * Silently no-ops if the renderer has been destroyed.\n *\n * @param {() => number} getTimeFunction\n */\n startContinuousSync(getTimeFunction) {\n if (this._isDestroyed()) return;\n this.timelineSync.startContinuousSync(getTimeFunction);\n }\n\n /**\n * Stop the RAF-driven sync started by `startContinuousSync`.\n *\n * Silently no-ops if the renderer has been destroyed.\n */\n stopContinuousSync() {\n if (this._isDestroyed()) return;\n this.timelineSync.stopContinuousSync();\n }\n\n /** @returns {boolean} whether continuous sync is currently running (false if destroyed) */\n isContinuousSyncActive() {\n if (this._isDestroyed()) return false;\n return this.timelineSync.isRunning;\n }\n\n /**\n * Update stroke rendering configuration at runtime and re-convert\n * annotations with the new style.\n *\n * @param {Object} newConfig\n * @returns {{success: boolean, error?: string}}\n */\n updateStrokeConfig(newConfig) {\n if (this._isDestroyed()) return fail(DESTROYED_ERROR);\n if (!newConfig || typeof newConfig !== 'object') {\n return fail('newConfig must be an object');\n }\n this.strokeRenderer.setConfig(newConfig);\n this.strokeRenderer.setAnnotations(this.annotations, this.currentPage);\n this._renderCurrentFrame();\n return ok();\n }\n\n /**\n * Snapshot of the current engine state — useful for debug overlays.\n *\n * @returns {Object|null} state snapshot, or `null` if the renderer has\n * been destroyed.\n */\n getState() {\n if (this._isDestroyed()) return null;\n return {\n page: this.currentPage,\n scale: this.currentScale,\n annotations: this.annotations,\n pageCount: this.pageCount,\n time: this.timelineSync.getCurrentTime(),\n viewport: this.currentViewport,\n pdfUrl: this.pdfUrl,\n };\n }\n\n /** Tear down all subsystems and release references. */\n destroy() {\n if (this.pdfRenderer) this.pdfRenderer.destroy();\n if (this.strokeRenderer) this.strokeRenderer.destroy();\n if (this.timelineSync) this.timelineSync.destroy();\n if (this.strokeCanvas && this.strokeCanvas.parentNode) {\n this.strokeCanvas.parentNode.removeChild(this.strokeCanvas);\n }\n\n this.pdfRenderer = null;\n this.strokeRenderer = null;\n this.timelineSync = null;\n this.strokeCanvas = null;\n this.config = null;\n this.canvasElement = null;\n this.container = null;\n this.annotations = [];\n this.currentPage = 0;\n this.currentScale = 1.0;\n this.pageCount = 0;\n this.currentViewport = null;\n this.pdfUrl = null;\n }\n}\n"],"names":["fail","error","ok","payload","DESTROYED_ERROR","AnnotationRenderer","config","PDFRenderer","StrokeRenderer","TimelineSync","time","result","canvas","url","err","pageNum","scale","annotations","strokes","timestamp","getTimeFunction","newConfig"],"mappings":";;;AAkBA,MAAMA,IAAO,CAACC,OAAW,EAAE,SAAS,IAAO,OAAAA,EAAK,IAE1CC,IAAK,CAACC,OAAa,EAAE,SAAS,IAAM,GAAIA,KAAW,CAAA,MAGnDC,IAAkB;AAEjB,MAAMC,EAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAc9B,YAAYC,GAAQ;AAClB,QAAI,CAACA,KAAU,OAAOA,KAAW;AAC/B,YAAM,IAAI,MAAM,+CAA+C;AAEjE,QAAI,CAACA,EAAO,aAAa,EAAEA,EAAO,qBAAqB;AACrD,YAAM,IAAI,MAAM,kEAAkE;AAEpF,QAAI,CAACA,EAAO,iBAAiB,EAAEA,EAAO,yBAAyB;AAC7D,YAAM,IAAI,MAAM,yEAAyE;AAG3F,SAAK,SAASA,GACd,KAAK,gBAAgBA,EAAO,eAC5B,KAAK,YAAYA,EAAO,WAExB,KAAK,cAAc,IAAIC,EAAW,GAClC,KAAK,eAAe,KAAK,oBAAmB,GAC5C,KAAK,iBAAiB,IAAIC,EAAe,KAAK,cAAcF,EAAO,gBAAgB,EAAE,GACrF,KAAK,eAAe,IAAIG,EAAY,GAEpC,KAAK,cAAcH,EAAO,eAAe,GACzC,KAAK,eAAeA,EAAO,gBAAgB,GAC3C,KAAK,cAAcA,EAAO,eAAe,CAAA,GACzC,KAAK,YAAY,GACjB,KAAK,kBAAkB,MACvB,KAAK,SAAS,MAGd,KAAK,aAAa,UAAU,CAACI,MAAS;AACpC,WAAK,eAAe,OAAOA,CAAI;AAAA,IACjC,CAAC,GAIGJ,EAAO,UACT,KAAK,QAAQA,EAAO,MAAM,EAAE,KAAK,CAACK,MAAW;AAC3C,MAAKA,EAAO,WACV,QAAQ,MAAM,gDAAgDA,EAAO,KAAK;AAAA,IAE9E,CAAC;AAAA,EAEL;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,eAAe;AACb,WACE,CAAC,KAAK,kBAAkB,CAAC,KAAK,gBAAgB,CAAC,KAAK;AAAA,EAExD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,sBAAsB;AACpB,UAAMC,IAAS,SAAS,cAAc,QAAQ;AAC9C,WAAAA,EAAO,YAAY,iBACnBA,EAAO,MAAM,WAAW,YACxBA,EAAO,MAAM,MAAM,KACnBA,EAAO,MAAM,OAAO,KACpBA,EAAO,MAAM,gBAAgB,QAC7BA,EAAO,MAAM,SAAS,MACtB,KAAK,UAAU,YAAYA,CAAM,GAC1BA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,sBAAsB;AACpB,SAAK,eAAe,OAAO,KAAK,aAAa,eAAc,CAAE;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,QAAQC,GAAK;AACjB,QAAI;AACF,UAAI,KAAK,aAAY,EAAI,QAAOb,EAAKI,CAAe;AACpD,UAAI,CAACS,KAAO,OAAOA,KAAQ,SAAU,QAAOb,EAAK,0BAA0B;AAE3E,YAAMW,IAAS,MAAM,KAAK,YAAY,aAAaE,CAAG;AACtD,aAAKF,EAAO,WAEZ,KAAK,SAASE,GACd,KAAK,YAAYF,EAAO,WACjBT,EAAG,EAAE,WAAWS,EAAO,UAAS,CAAE,KAJbA;AAAA,IAK9B,SAASG,GAAK;AACZ,qBAAQ,MAAM,kDAAkDA,CAAG,GAC5Dd,EAAK,uBAAuBc,EAAI,OAAO,EAAE;AAAA,IAClD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,QAAQC,GAAS;AACrB,QAAI;AACF,UAAI,KAAK,aAAY,EAAI,QAAOf,EAAKI,CAAe;AACpD,UAAI,OAAOW,KAAY,YAAYA,IAAU,EAAG,QAAOf,EAAK,qBAAqB;AACjF,UAAI,KAAK,YAAY,KAAKe,IAAU,KAAK;AACvC,eAAOf,EAAK,QAAQe,CAAO,iCAAiC,KAAK,SAAS,GAAG;AAG/E,WAAK,YAAY,aAAY;AAE7B,YAAMJ,IAAS,MAAM,KAAK,YAAY;AAAA,QACpCI;AAAA,QACA,KAAK;AAAA,QACL,KAAK;AAAA,MACb;AACM,aAAKJ,EAAO,WAEZ,KAAK,cAAcI,GACnB,KAAK,kBAAkBJ,EAAO,UAC9B,KAAK,eAAe,YAAYA,EAAO,SAAS,OAAOA,EAAO,SAAS,MAAM,GAK7E,KAAK,oBAAmB,GAEjBT,EAAG,EAAE,UAAUS,EAAO,SAAQ,CAAE,KAXXA;AAAA,IAY9B,SAASG,GAAK;AACZ,qBAAQ,MAAM,qDAAqDA,CAAG,GAC/Dd,EAAK,0BAA0Bc,EAAI,OAAO,EAAE;AAAA,IACrD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,SAASE,GAAO;AACpB,QAAI;AACF,aAAI,KAAK,aAAY,IAAWhB,EAAKI,CAAe,IAChD,OAAOY,KAAU,YAAYA,KAAS,IACjChB,EAAK,+CAA+C,KAE7D,KAAK,eAAegB,GACb,MAAM,KAAK,QAAQ,KAAK,WAAW;AAAA,IAC5C,SAASF,GAAK;AACZ,qBAAQ,MAAM,sDAAsDA,CAAG,GAChEd,EAAK,2BAA2Bc,EAAI,OAAO,EAAE;AAAA,IACtD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,eAAeG,GAAa;AAC1B,WAAI,KAAK,aAAY,IAAWjB,EAAKI,CAAe,IAC/C,MAAM,QAAQa,CAAW,KAG9B,KAAK,cAAcA,GACnB,KAAK,eAAe,eAAeA,GAAa,KAAK,WAAW,GAChE,KAAK,oBAAmB,GACjBf,EAAE,KALAF,EAAK,8BAA8B;AAAA,EAM9C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,WAAWkB,GAAS;AAClB,WAAI,KAAK,aAAY,IAAWlB,EAAKI,CAAe,IAC/C,MAAM,QAAQc,CAAO,KAC1B,KAAK,eAAe,WAAWA,CAAO,GACtC,KAAK,oBAAmB,GACjBhB,EAAE,KAH2BF,EAAK,0BAA0B;AAAA,EAIrE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,QAAQmB,GAAW;AACjB,WAAI,KAAK,aAAY,IAAWnB,EAAKI,CAAe,IAChD,OAAOe,KAAc,WAAiBnB,EAAK,4BAA4B,KAC3E,KAAK,aAAa,QAAQmB,CAAS,GAC5BjB,EAAE;AAAA,EACX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,oBAAoBkB,GAAiB;AACnC,IAAI,KAAK,kBACT,KAAK,aAAa,oBAAoBA,CAAe;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,qBAAqB;AACnB,IAAI,KAAK,kBACT,KAAK,aAAa,mBAAkB;AAAA,EACtC;AAAA;AAAA,EAGA,yBAAyB;AACvB,WAAI,KAAK,aAAY,IAAW,KACzB,KAAK,aAAa;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,mBAAmBC,GAAW;AAC5B,WAAI,KAAK,aAAY,IAAWrB,EAAKI,CAAe,IAChD,CAACiB,KAAa,OAAOA,KAAc,WAC9BrB,EAAK,6BAA6B,KAE3C,KAAK,eAAe,UAAUqB,CAAS,GACvC,KAAK,eAAe,eAAe,KAAK,aAAa,KAAK,WAAW,GACrE,KAAK,oBAAmB,GACjBnB,EAAE;AAAA,EACX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,WAAW;AACT,WAAI,KAAK,aAAY,IAAW,OACzB;AAAA,MACL,MAAM,KAAK;AAAA,MACX,OAAO,KAAK;AAAA,MACZ,aAAa,KAAK;AAAA,MAClB,WAAW,KAAK;AAAA,MAChB,MAAM,KAAK,aAAa,eAAc;AAAA,MACtC,UAAU,KAAK;AAAA,MACf,QAAQ,KAAK;AAAA,IACnB;AAAA,EACE;AAAA;AAAA,EAGA,UAAU;AACR,IAAI,KAAK,eAAa,KAAK,YAAY,QAAO,GAC1C,KAAK,kBAAgB,KAAK,eAAe,QAAO,GAChD,KAAK,gBAAc,KAAK,aAAa,QAAO,GAC5C,KAAK,gBAAgB,KAAK,aAAa,cACzC,KAAK,aAAa,WAAW,YAAY,KAAK,YAAY,GAG5D,KAAK,cAAc,MACnB,KAAK,iBAAiB,MACtB,KAAK,eAAe,MACpB,KAAK,eAAe,MACpB,KAAK,SAAS,MACd,KAAK,gBAAgB,MACrB,KAAK,YAAY,MACjB,KAAK,cAAc,CAAA,GACnB,KAAK,cAAc,GACnB,KAAK,eAAe,GACpB,KAAK,YAAY,GACjB,KAAK,kBAAkB,MACvB,KAAK,SAAS;AAAA,EAChB;AACF;"}
package/dist/index20.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});class d{constructor(t,o){this.ctx=t,this.viewport=o}setViewport(t){this.viewport=t}drawStroke(t,o=1){const{points:i,color:e,width:s,pressures:h}=t;if(!i||i.length<2)return;const n=Math.max(2,Math.floor(i.length*o)),r=i.slice(0,n);this.ctx.strokeStyle=e||"rgba(0, 0, 0, 0.5)",this.ctx.lineCap="round",this.ctx.lineJoin="round",h&&h.length>=r.length?this._drawVariableWidthStroke(r,s,h.slice(0,n)):this._drawConstantWidthStroke(r,s)}_drawConstantWidthStroke(t,o){this.ctx.lineWidth=o||2,this.ctx.beginPath();for(let i=0;i<t.length;i++){const[e,s]=this._normalizedToPixel(t[i]);i===0?this.ctx.moveTo(e,s):this.ctx.lineTo(e,s)}this.ctx.stroke()}_drawVariableWidthStroke(t,o,i){for(let e=1;e<t.length;e++){const[s,h]=this._normalizedToPixel(t[e-1]),[n,r]=this._normalizedToPixel(t[e]),l=i[e-1]||1,c=i[e]||1,a=(l+c)/2,x=Math.max(.5,o*a);this.ctx.lineWidth=x,this.ctx.beginPath(),this.ctx.moveTo(s,h),this.ctx.lineTo(n,r),this.ctx.stroke()}}drawPoint(t,o,i,e){const[s,h]=this._normalizedToPixel([t,o]);this.ctx.fillStyle=e||"rgba(0, 0, 0, 0.5)",this.ctx.beginPath(),this.ctx.arc(s,h,i,0,Math.PI*2),this.ctx.fill()}clear(){const t=window.devicePixelRatio||1;this.ctx.clearRect(0,0,this.viewport.width*t,this.viewport.height*t)}_normalizedToPixel(t){const[o,i]=t;return[o*this.viewport.width,i*this.viewport.height]}}exports.default=d;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const n=[{type:"function",function:{name:"create_highlight_annotation",description:"Create a highlight annotation at specified coordinates on the PDF page. Use this to emphasize important text or regions. Highlights are rectangular regions that can span multiple lines.",parameters:{type:"object",properties:{quads:{type:"array",description:"Array of rectangular regions defining the highlight areas. Each quad is an object with normalized coordinates (0-1): {x, y, w, h}. Multiple quads can be used for multi-line highlights.",items:{type:"object",properties:{x:{type:"number",description:"Normalized x position (0 = left, 1 = right)",minimum:0,maximum:1},y:{type:"number",description:"Normalized y position (0 = top, 1 = bottom)",minimum:0,maximum:1},w:{type:"number",description:"Normalized width (0-1)",minimum:0,maximum:1},h:{type:"number",description:"Normalized height (0-1)",minimum:0,maximum:1}},required:["x","y","w","h"]},minItems:1},color:{type:"string",description:"Highlight color in rgba format (e.g., 'rgba(255, 255, 0, 0.3)'). Default is semi-transparent yellow. Use rgba for transparency control.",default:"rgba(255, 255, 0, 0.3)"},page:{type:"integer",description:"Page number (1-indexed) where the annotation appears",minimum:1},sentence_ref:{type:"string",description:"Reference to sentence marker (e.g., 'S1', 'S2') for timing synchronization",pattern:"^S\\d+$"}},required:["quads","page","sentence_ref"]}}},{type:"function",function:{name:"create_text_annotation",description:"Create a text box annotation with explanatory content. Use this to add clarifying notes, definitions, or explanations. Text boxes have background and appear as overlays.",parameters:{type:"object",properties:{content:{type:"string",description:"The text content of the annotation",minLength:1,maxLength:500},x:{type:"number",description:"Normalized x position (0 = left edge, 1 = right edge)",minimum:0,maximum:1},y:{type:"number",description:"Normalized y position (0 = top edge, 1 = bottom edge)",minimum:0,maximum:1},w:{type:"number",description:"Normalized width (0-1) of the text box",minimum:0,maximum:1},h:{type:"number",description:"Normalized height (0-1) of the text box",minimum:0,maximum:1},page:{type:"integer",description:"Page number (1-indexed) where the annotation appears",minimum:1},textColor:{type:"string",description:"Text color in hex format (e.g., '#000000' for black). Default is dark gray.",default:"#1f2937"},bgColor:{type:"string",description:"Background color in rgba format or 'transparent' (e.g., 'rgba(255, 255, 255, 0.9)' or 'transparent'). Default is transparent for better visibility of underlying content. Use rgba format when background is needed.",default:"transparent"},sentence_ref:{type:"string",description:"Reference to sentence marker (e.g., 'S1', 'S2') for timing synchronization",pattern:"^S\\d+$"}},required:["content","x","y","w","h","page","sentence_ref"]}}}];function o(e=[]){if(!Array.isArray(e)||e.length===0)return[];const i={highlight:"create_highlight_annotation",text:"create_text_annotation"},r=e.map(t=>i[t]).filter(Boolean);return n.filter(t=>r.includes(t.function.name))}exports.annotationTools=n;exports.getAnnotationTools=o;
2
2
  //# sourceMappingURL=index20.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index20.cjs","sources":["../src/pen/StrokeDrawer.js"],"sourcesContent":["/**\n * StrokeDrawer - Low-level canvas stroke drawing utilities\n *\n * Handles the actual drawing of strokes on canvas with support for:\n * - Progressive stroke reveal (partial drawing based on progress)\n * - Normalized to pixel coordinate conversion\n * - Variable width based on pressure data\n * - Smooth line rendering with round caps/joins\n */\nclass StrokeDrawer {\n /**\n * Creates a new StrokeDrawer instance\n *\n * @param {CanvasRenderingContext2D} ctx - Canvas 2D rendering context\n * @param {Object} viewport - Viewport dimensions for coordinate conversion\n * @param {number} viewport.width - Viewport width in pixels\n * @param {number} viewport.height - Viewport height in pixels\n */\n constructor(ctx, viewport) {\n this.ctx = ctx;\n this.viewport = viewport;\n }\n\n /**\n * Updates the viewport dimensions\n *\n * @param {Object} viewport - New viewport dimensions\n */\n setViewport(viewport) {\n this.viewport = viewport;\n }\n\n /**\n * Draws a stroke with optional progress for progressive reveal\n *\n * @param {Object} stroke - Stroke command object\n * @param {Array} stroke.points - Array of [x, y] normalized coordinates\n * @param {string} stroke.color - Stroke color (CSS color string)\n * @param {number} stroke.width - Base stroke width in pixels\n * @param {Array} [stroke.pressures] - Optional pressure values for variable width\n * @param {number} [progress=1.0] - Progress from 0 to 1 (for progressive reveal)\n */\n drawStroke(stroke, progress = 1.0) {\n const { points, color, width, pressures } = stroke;\n\n if (!points || points.length < 2) return;\n\n // Calculate how many points to draw based on progress\n const pointCount = Math.max(2, Math.floor(points.length * progress));\n const visiblePoints = points.slice(0, pointCount);\n\n // Configure stroke style\n this.ctx.strokeStyle = color || 'rgba(0, 0, 0, 0.5)';\n this.ctx.lineCap = 'round';\n this.ctx.lineJoin = 'round';\n\n // Draw with variable width if pressures provided\n if (pressures && pressures.length >= visiblePoints.length) {\n this._drawVariableWidthStroke(visiblePoints, width, pressures.slice(0, pointCount));\n } else {\n this._drawConstantWidthStroke(visiblePoints, width);\n }\n }\n\n /**\n * Draws a stroke with constant width\n *\n * @private\n * @param {Array} points - Array of [x, y] normalized coordinates\n * @param {number} width - Stroke width in pixels\n */\n _drawConstantWidthStroke(points, width) {\n this.ctx.lineWidth = width || 2;\n this.ctx.beginPath();\n\n for (let i = 0; i < points.length; i++) {\n const [px, py] = this._normalizedToPixel(points[i]);\n\n if (i === 0) {\n this.ctx.moveTo(px, py);\n } else {\n this.ctx.lineTo(px, py);\n }\n }\n\n this.ctx.stroke();\n }\n\n /**\n * Draws a stroke with variable width based on pressure\n *\n * Uses multiple line segments with varying widths for natural pen feel.\n *\n * @private\n * @param {Array} points - Array of [x, y] normalized coordinates\n * @param {number} baseWidth - Base stroke width in pixels\n * @param {Array} pressures - Pressure values (0-1) for each point\n */\n _drawVariableWidthStroke(points, baseWidth, pressures) {\n for (let i = 1; i < points.length; i++) {\n const [x1, y1] = this._normalizedToPixel(points[i - 1]);\n const [x2, y2] = this._normalizedToPixel(points[i]);\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, max 2x base width)\n const width = Math.max(0.5, baseWidth * avgPressure);\n\n this.ctx.lineWidth = width;\n this.ctx.beginPath();\n this.ctx.moveTo(x1, y1);\n this.ctx.lineTo(x2, y2);\n this.ctx.stroke();\n }\n }\n\n /**\n * Draws a single point (dot)\n *\n * Useful for single-point strokes or debugging.\n *\n * @param {number} x - Normalized x coordinate (0-1)\n * @param {number} y - Normalized y coordinate (0-1)\n * @param {number} radius - Dot radius in pixels\n * @param {string} color - Fill color\n */\n drawPoint(x, y, radius, color) {\n const [px, py] = this._normalizedToPixel([x, y]);\n\n this.ctx.fillStyle = color || 'rgba(0, 0, 0, 0.5)';\n this.ctx.beginPath();\n this.ctx.arc(px, py, radius, 0, Math.PI * 2);\n this.ctx.fill();\n }\n\n /**\n * Clears the entire canvas\n */\n clear() {\n const dpr = window.devicePixelRatio || 1;\n this.ctx.clearRect(0, 0, this.viewport.width * dpr, this.viewport.height * dpr);\n }\n\n /**\n * Converts normalized coordinates (0-1) to pixel coordinates\n *\n * @private\n * @param {Array} point - [x, y] normalized coordinates\n * @returns {Array} [x, y] pixel coordinates\n */\n _normalizedToPixel(point) {\n const [normX, normY] = point;\n return [\n normX * this.viewport.width,\n normY * this.viewport.height\n ];\n }\n}\n\nexport default StrokeDrawer;\n"],"names":["StrokeDrawer","ctx","viewport","stroke","progress","points","color","width","pressures","pointCount","visiblePoints","px","py","baseWidth","i","x1","y1","x2","y2","p1","p2","avgPressure","x","y","radius","dpr","point","normX","normY"],"mappings":"4GASA,MAAMA,CAAa,CASjB,YAAYC,EAAKC,EAAU,CACzB,KAAK,IAAMD,EACX,KAAK,SAAWC,CAClB,CAOA,YAAYA,EAAU,CACpB,KAAK,SAAWA,CAClB,CAYA,WAAWC,EAAQC,EAAW,EAAK,CACjC,KAAM,CAAE,OAAAC,EAAQ,MAAAC,EAAO,MAAAC,EAAO,UAAAC,CAAS,EAAKL,EAE5C,GAAI,CAACE,GAAUA,EAAO,OAAS,EAAG,OAGlC,MAAMI,EAAa,KAAK,IAAI,EAAG,KAAK,MAAMJ,EAAO,OAASD,CAAQ,CAAC,EAC7DM,EAAgBL,EAAO,MAAM,EAAGI,CAAU,EAGhD,KAAK,IAAI,YAAcH,GAAS,qBAChC,KAAK,IAAI,QAAU,QACnB,KAAK,IAAI,SAAW,QAGhBE,GAAaA,EAAU,QAAUE,EAAc,OACjD,KAAK,yBAAyBA,EAAeH,EAAOC,EAAU,MAAM,EAAGC,CAAU,CAAC,EAElF,KAAK,yBAAyBC,EAAeH,CAAK,CAEtD,CASA,yBAAyBF,EAAQE,EAAO,CACtC,KAAK,IAAI,UAAYA,GAAS,EAC9B,KAAK,IAAI,UAAS,EAElB,QAAS,EAAI,EAAG,EAAIF,EAAO,OAAQ,IAAK,CACtC,KAAM,CAACM,EAAIC,CAAE,EAAI,KAAK,mBAAmBP,EAAO,CAAC,CAAC,EAE9C,IAAM,EACR,KAAK,IAAI,OAAOM,EAAIC,CAAE,EAEtB,KAAK,IAAI,OAAOD,EAAIC,CAAE,CAE1B,CAEA,KAAK,IAAI,OAAM,CACjB,CAYA,yBAAyBP,EAAQQ,EAAWL,EAAW,CACrD,QAASM,EAAI,EAAGA,EAAIT,EAAO,OAAQS,IAAK,CACtC,KAAM,CAACC,EAAIC,CAAE,EAAI,KAAK,mBAAmBX,EAAOS,EAAI,CAAC,CAAC,EAChD,CAACG,EAAIC,CAAE,EAAI,KAAK,mBAAmBb,EAAOS,CAAC,CAAC,EAG5CK,EAAKX,EAAUM,EAAI,CAAC,GAAK,EACzBM,EAAKZ,EAAUM,CAAC,GAAK,EACrBO,GAAeF,EAAKC,GAAM,EAG1Bb,EAAQ,KAAK,IAAI,GAAKM,EAAYQ,CAAW,EAEnD,KAAK,IAAI,UAAYd,EACrB,KAAK,IAAI,UAAS,EAClB,KAAK,IAAI,OAAOQ,EAAIC,CAAE,EACtB,KAAK,IAAI,OAAOC,EAAIC,CAAE,EACtB,KAAK,IAAI,OAAM,CACjB,CACF,CAYA,UAAUI,EAAGC,EAAGC,EAAQlB,EAAO,CAC7B,KAAM,CAACK,EAAIC,CAAE,EAAI,KAAK,mBAAmB,CAACU,EAAGC,CAAC,CAAC,EAE/C,KAAK,IAAI,UAAYjB,GAAS,qBAC9B,KAAK,IAAI,UAAS,EAClB,KAAK,IAAI,IAAIK,EAAIC,EAAIY,EAAQ,EAAG,KAAK,GAAK,CAAC,EAC3C,KAAK,IAAI,KAAI,CACf,CAKA,OAAQ,CACN,MAAMC,EAAM,OAAO,kBAAoB,EACvC,KAAK,IAAI,UAAU,EAAG,EAAG,KAAK,SAAS,MAAQA,EAAK,KAAK,SAAS,OAASA,CAAG,CAChF,CASA,mBAAmBC,EAAO,CACxB,KAAM,CAACC,EAAOC,CAAK,EAAIF,EACvB,MAAO,CACLC,EAAQ,KAAK,SAAS,MACtBC,EAAQ,KAAK,SAAS,MAC5B,CACE,CACF"}
1
+ {"version":3,"file":"index20.cjs","sources":["../src/ai-tools/openai/schemas.js"],"sourcesContent":["/**\n * OpenAI Tool Schemas for PDF Annotation Generation\n *\n * Provides OpenAI-compatible function calling schemas for creating PDF annotations.\n * These schemas define the structure and parameters for AI-generated annotations.\n * Compatible with web-annotation-renderer library format.\n *\n * @module ai-tools/openai/schemas\n */\n\n/**\n * Complete set of annotation tools for OpenAI function calling\n *\n * Export this array to the OpenAI API's `tools` parameter to enable\n * AI-generated annotations.\n *\n * @constant {Array<Object>}\n * @example\n * ```javascript\n * import { annotationTools } from 'web-annotation-renderer/ai-tools';\n *\n * const response = await openai.chat.completions.create({\n * model: \"gpt-4\",\n * messages: [...],\n * tools: annotationTools\n * });\n * ```\n */\nexport const annotationTools = [\n {\n type: \"function\",\n function: {\n name: \"create_highlight_annotation\",\n description: \"Create a highlight annotation at specified coordinates on the PDF page. Use this to emphasize important text or regions. Highlights are rectangular regions that can span multiple lines.\",\n parameters: {\n type: \"object\",\n properties: {\n quads: {\n type: \"array\",\n description: \"Array of rectangular regions defining the highlight areas. Each quad is an object with normalized coordinates (0-1): {x, y, w, h}. Multiple quads can be used for multi-line highlights.\",\n items: {\n type: \"object\",\n properties: {\n x: {\n type: \"number\",\n description: \"Normalized x position (0 = left, 1 = right)\",\n minimum: 0,\n maximum: 1\n },\n y: {\n type: \"number\",\n description: \"Normalized y position (0 = top, 1 = bottom)\",\n minimum: 0,\n maximum: 1\n },\n w: {\n type: \"number\",\n description: \"Normalized width (0-1)\",\n minimum: 0,\n maximum: 1\n },\n h: {\n type: \"number\",\n description: \"Normalized height (0-1)\",\n minimum: 0,\n maximum: 1\n }\n },\n required: [\"x\", \"y\", \"w\", \"h\"]\n },\n minItems: 1\n },\n color: {\n type: \"string\",\n description: \"Highlight color in rgba format (e.g., 'rgba(255, 255, 0, 0.3)'). Default is semi-transparent yellow. Use rgba for transparency control.\",\n default: \"rgba(255, 255, 0, 0.3)\"\n },\n page: {\n type: \"integer\",\n description: \"Page number (1-indexed) where the annotation appears\",\n minimum: 1\n },\n sentence_ref: {\n type: \"string\",\n description: \"Reference to sentence marker (e.g., 'S1', 'S2') for timing synchronization\",\n pattern: \"^S\\\\d+$\"\n }\n },\n required: [\"quads\", \"page\", \"sentence_ref\"]\n }\n }\n },\n {\n type: \"function\",\n function: {\n name: \"create_text_annotation\",\n description: \"Create a text box annotation with explanatory content. Use this to add clarifying notes, definitions, or explanations. Text boxes have background and appear as overlays.\",\n parameters: {\n type: \"object\",\n properties: {\n content: {\n type: \"string\",\n description: \"The text content of the annotation\",\n minLength: 1,\n maxLength: 500\n },\n x: {\n type: \"number\",\n description: \"Normalized x position (0 = left edge, 1 = right edge)\",\n minimum: 0,\n maximum: 1\n },\n y: {\n type: \"number\",\n description: \"Normalized y position (0 = top edge, 1 = bottom edge)\",\n minimum: 0,\n maximum: 1\n },\n w: {\n type: \"number\",\n description: \"Normalized width (0-1) of the text box\",\n minimum: 0,\n maximum: 1\n },\n h: {\n type: \"number\",\n description: \"Normalized height (0-1) of the text box\",\n minimum: 0,\n maximum: 1\n },\n page: {\n type: \"integer\",\n description: \"Page number (1-indexed) where the annotation appears\",\n minimum: 1\n },\n textColor: {\n type: \"string\",\n description: \"Text color in hex format (e.g., '#000000' for black). Default is dark gray.\",\n default: \"#1f2937\"\n },\n bgColor: {\n type: \"string\",\n description: \"Background color in rgba format or 'transparent' (e.g., 'rgba(255, 255, 255, 0.9)' or 'transparent'). Default is transparent for better visibility of underlying content. Use rgba format when background is needed.\",\n default: \"transparent\"\n },\n sentence_ref: {\n type: \"string\",\n description: \"Reference to sentence marker (e.g., 'S1', 'S2') for timing synchronization\",\n pattern: \"^S\\\\d+$\"\n }\n },\n required: [\"content\", \"x\", \"y\", \"w\", \"h\", \"page\", \"sentence_ref\"]\n }\n }\n }\n];\n\n/**\n * Get filtered annotation tools based on enabled types\n *\n * @param {Array<string>} enabledTypes - Array of enabled tool types: ['highlight', 'text']\n * @returns {Array<Object>} Filtered annotation tools\n * @example\n * ```javascript\n * // Only enable highlights and text notes\n * const tools = getAnnotationTools(['highlight', 'text']);\n * ```\n */\nexport function getAnnotationTools(enabledTypes = []) {\n if (!Array.isArray(enabledTypes) || enabledTypes.length === 0) {\n return [];\n }\n\n const typeMap = {\n 'highlight': 'create_highlight_annotation',\n 'text': 'create_text_annotation'\n };\n\n const enabledFunctionNames = enabledTypes\n .map(type => typeMap[type])\n .filter(Boolean);\n\n return annotationTools.filter(tool =>\n enabledFunctionNames.includes(tool.function.name)\n );\n}\n"],"names":["annotationTools","getAnnotationTools","enabledTypes","typeMap","enabledFunctionNames","type","tool"],"mappings":"gFA4BY,MAACA,EAAkB,CAC7B,CACE,KAAM,WACN,SAAU,CACR,KAAM,8BACN,YAAa,4LACb,WAAY,CACV,KAAM,SACN,WAAY,CACV,MAAO,CACL,KAAM,QACN,YAAa,2LACb,MAAO,CACL,KAAM,SACN,WAAY,CACV,EAAG,CACD,KAAM,SACN,YAAa,8CACb,QAAS,EACT,QAAS,CAC3B,EACgB,EAAG,CACD,KAAM,SACN,YAAa,8CACb,QAAS,EACT,QAAS,CAC3B,EACgB,EAAG,CACD,KAAM,SACN,YAAa,yBACb,QAAS,EACT,QAAS,CAC3B,EACgB,EAAG,CACD,KAAM,SACN,YAAa,0BACb,QAAS,EACT,QAAS,CAC3B,CACA,EACc,SAAU,CAAC,IAAK,IAAK,IAAK,GAAG,CAC3C,EACY,SAAU,CACtB,EACU,MAAO,CACL,KAAM,SACN,YAAa,0IACb,QAAS,wBACrB,EACU,KAAM,CACJ,KAAM,UACN,YAAa,uDACb,QAAS,CACrB,EACU,aAAc,CACZ,KAAM,SACN,YAAa,6EACb,QAAS,SACrB,CACA,EACQ,SAAU,CAAC,QAAS,OAAQ,cAAc,CAClD,CACA,CACA,EACE,CACE,KAAM,WACN,SAAU,CACR,KAAM,yBACN,YAAa,4KACb,WAAY,CACV,KAAM,SACN,WAAY,CACV,QAAS,CACP,KAAM,SACN,YAAa,qCACb,UAAW,EACX,UAAW,GACvB,EACU,EAAG,CACD,KAAM,SACN,YAAa,wDACb,QAAS,EACT,QAAS,CACrB,EACU,EAAG,CACD,KAAM,SACN,YAAa,wDACb,QAAS,EACT,QAAS,CACrB,EACU,EAAG,CACD,KAAM,SACN,YAAa,yCACb,QAAS,EACT,QAAS,CACrB,EACU,EAAG,CACD,KAAM,SACN,YAAa,0CACb,QAAS,EACT,QAAS,CACrB,EACU,KAAM,CACJ,KAAM,UACN,YAAa,uDACb,QAAS,CACrB,EACU,UAAW,CACT,KAAM,SACN,YAAa,8EACb,QAAS,SACrB,EACU,QAAS,CACP,KAAM,SACN,YAAa,uNACb,QAAS,aACrB,EACU,aAAc,CACZ,KAAM,SACN,YAAa,6EACb,QAAS,SACrB,CACA,EACQ,SAAU,CAAC,UAAW,IAAK,IAAK,IAAK,IAAK,OAAQ,cAAc,CACxE,CACA,CACA,CACA,EAaO,SAASC,EAAmBC,EAAe,GAAI,CACpD,GAAI,CAAC,MAAM,QAAQA,CAAY,GAAKA,EAAa,SAAW,EAC1D,MAAO,CAAA,EAGT,MAAMC,EAAU,CACd,UAAa,8BACb,KAAQ,wBACZ,EAEQC,EAAuBF,EAC1B,IAAIG,GAAQF,EAAQE,CAAI,CAAC,EACzB,OAAO,OAAO,EAEjB,OAAOL,EAAgB,OAAOM,GAC5BF,EAAqB,SAASE,EAAK,SAAS,IAAI,CACpD,CACA"}