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