web-annotation-renderer 0.4.0 → 0.5.1

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.
package/dist/index12.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const P=require("react/jsx-runtime"),n=require("react"),j=require("./index2.cjs"),q=require("./index17.cjs");function C({pdfUrl:d,page:s=1,scale:l=1.5,annotations:g=[],currentTime:f=0,strokeConfig:u,onLoad:p,onError:t,onPageChange:w,className:F,style:R,canvasStyle:b}){const h=n.useRef(null),y=n.useRef(null),r=n.useRef(null),A=n.useRef(Promise.resolve()),i=n.useCallback(e=>{A.current=A.current.then(e).catch(c=>{console.error("AnnotPdf: Queued operation failed:",c)})},[]),a=n.useMemo(()=>{if(!u)return;const e=u.preset||"default",c=q.getPreset(e);return{highlight:{color:c.highlight.color,width:u.highlight?.width??c.highlight.width},text:{color:c.handText.color,width:u.text?.width??c.handText.width}}},[u]);n.useEffect(()=>{if(!(!h.current||!y.current)){try{r.current=new j.AnnotationRenderer({canvasElement:h.current,container:y.current,strokeConfig:a})}catch(e){console.error("AnnotPdf: Failed to initialize renderer:",e),t&&t(e)}return()=>{r.current&&(r.current.destroy(),r.current=null)}}},[]),n.useEffect(()=>{if(!r.current||!d)return;let e=!1;return i(async()=>{try{const o=await r.current.loadPDF(d);if(e)return;if(!o.success){console.error("AnnotPdf: Failed to load PDF:",o.error),t&&t(new Error(o.error));return}p&&p({pageCount:o.pageCount})}catch(o){if(e)return;console.error("AnnotPdf: Failed to load PDF:",o),t&&t(o)}}),()=>{e=!0}},[d,i]),n.useEffect(()=>{!r.current||!s||typeof s!="number"||i(async()=>{try{const e=await r.current.setPage(s);if(!e.success){console.error("AnnotPdf: Failed to set page:",e.error),t&&t(new Error(e.error));return}w&&w(s)}catch(e){console.error("AnnotPdf: Failed to set page:",e),t&&t(e)}})},[s,i]),n.useEffect(()=>{!r.current||!l||typeof l!="number"||i(async()=>{try{const e=await r.current.setScale(l);e.success||(console.error("AnnotPdf: Failed to set scale:",e.error),t&&t(new Error(e.error)))}catch(e){console.error("AnnotPdf: Failed to set scale:",e),t&&t(e)}})},[l,i]),n.useEffect(()=>{if(r.current)try{r.current.setAnnotations(g||[])}catch(e){console.error("AnnotPdf: Failed to set annotations:",e),t&&t(e)}},[g]),n.useEffect(()=>{if(!(!r.current||f===void 0||f===null))try{r.current.setTime(f)}catch(e){console.error("AnnotPdf: Failed to set time:",e),t&&t(e)}},[f]);const v=n.useRef(!0);n.useEffect(()=>{if(v.current){v.current=!1;return}if(r.current&&a)try{r.current.updateStrokeConfig(a)}catch(e){console.error("AnnotPdf: Failed to update stroke config:",e),t&&t(e)}},[a]);const m={position:"relative",display:"inline-block",lineHeight:0,...R},x={position:"absolute",top:0,left:0,width:"100%",height:"100%",pointerEvents:"none",overflow:"hidden"},S={display:"block",...b};return P.jsxs("div",{className:F,style:m,children:[P.jsx("canvas",{ref:h,style:S}),P.jsx("div",{ref:y,style:x})]})}exports.default=C;
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const P=require("react/jsx-runtime"),n=require("react"),j=require("./index2.cjs"),q=require("./index17.cjs");function E({pdfUrl:y,page:o=1,scale:a=1.5,annotations:g=[],currentTime:d=0,audioRef:f,strokeConfig:l,onLoad:m,onError:r,onPageChange:w,className:b,style:x,canvasStyle:C}){const v=n.useRef(null),p=n.useRef(null),t=n.useRef(null),A=n.useRef(Promise.resolve()),[i,F]=n.useState(!1),u=n.useCallback(e=>{A.current=A.current.then(e).catch(c=>{console.error("AnnotPdf: Queued operation failed:",c)})},[]),h=n.useMemo(()=>{if(!l)return;const e=l.preset||"default",c=q.getPreset(e);return{highlight:{color:c.highlight.color,width:l.highlight?.width??c.highlight.width},text:{color:c.handText.color,width:l.text?.width??c.handText.width}}},[l]);n.useEffect(()=>{if(!(!v.current||!p.current)){try{t.current=new j.AnnotationRenderer({canvasElement:v.current,container:p.current,strokeConfig:h}),F(!0)}catch(e){console.error("AnnotPdf: Failed to initialize renderer:",e),r&&r(e)}return()=>{F(!1),t.current&&(t.current.destroy(),t.current=null)}}},[]),n.useEffect(()=>{if(!i||!y)return;let e=!1;return u(async()=>{if(t.current)try{const s=await t.current.loadPDF(y);if(e)return;if(!s.success){console.error("AnnotPdf: Failed to load PDF:",s.error),r&&r(new Error(s.error));return}m&&m({pageCount:s.pageCount})}catch(s){if(e)return;console.error("AnnotPdf: Failed to load PDF:",s),r&&r(s)}}),()=>{e=!0}},[y,i,u]),n.useEffect(()=>{!i||!o||typeof o!="number"||u(async()=>{if(t.current)try{const e=await t.current.setPage(o);if(!e.success){console.error("AnnotPdf: Failed to set page:",e.error),r&&r(new Error(e.error));return}w&&w(o)}catch(e){console.error("AnnotPdf: Failed to set page:",e),r&&r(e)}})},[o,i,u]),n.useEffect(()=>{!i||!a||typeof a!="number"||u(async()=>{if(t.current)try{const e=await t.current.setScale(a);e.success||(console.error("AnnotPdf: Failed to set scale:",e.error),r&&r(new Error(e.error)))}catch(e){console.error("AnnotPdf: Failed to set scale:",e),r&&r(e)}})},[a,i,u]),n.useEffect(()=>{if(!(!i||!t.current))try{t.current.setAnnotations(g||[])}catch(e){console.error("AnnotPdf: Failed to set annotations:",e),r&&r(e)}},[g,i]),n.useEffect(()=>{if(!f&&!(!i||!t.current||d===void 0||d===null))try{t.current.setTime(d)}catch(e){console.error("AnnotPdf: Failed to set time:",e),r&&r(e)}},[d,f,i]),n.useEffect(()=>{if(!f?.current||!i||!t.current)return;const e=f.current,c=()=>{t.current&&t.current.startContinuousSync(()=>e.currentTime)},s=()=>{t.current&&(t.current.stopContinuousSync(),t.current.setTime(e.currentTime))},R=()=>{t.current&&t.current.setTime(e.currentTime)};return e.addEventListener("play",c),e.addEventListener("pause",s),e.addEventListener("ended",s),e.addEventListener("seeked",R),e.paused||c(),()=>{e.removeEventListener("play",c),e.removeEventListener("pause",s),e.removeEventListener("ended",s),e.removeEventListener("seeked",R),t.current&&t.current.stopContinuousSync()}},[f,i]);const S=n.useRef(!0);n.useEffect(()=>{if(S.current){S.current=!1;return}if(t.current&&h)try{t.current.updateStrokeConfig(h)}catch(e){console.error("AnnotPdf: Failed to update stroke config:",e),r&&r(e)}},[h]);const L={position:"relative",display:"inline-block",lineHeight:0,...x},k={position:"absolute",top:0,left:0,width:"100%",height:"100%",pointerEvents:"none",overflow:"hidden"},T={display:"block",...C};return P.jsxs("div",{className:b,style:L,children:[P.jsx("canvas",{ref:v,style:T}),P.jsx("div",{ref:p,style:k})]})}exports.default=E;
2
2
  //# sourceMappingURL=index12.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index12.cjs","sources":["../src/adapters/AnnotPdf.jsx"],"sourcesContent":["// ============================================================================\n// SECTION 1: IMPORTS\n// ============================================================================\n\nimport { useRef, useEffect, useCallback, useMemo } from 'react';\nimport { AnnotationRenderer } from '../core/AnnotationRenderer.js';\nimport { getPreset } from '../pen/presets.js';\n\n// ============================================================================\n// SECTION 2: JSDOC DOCUMENTATION\n// ============================================================================\n\n/**\n * AnnotPdf - Declarative React component for PDF annotation rendering\n *\n * A React wrapper around the AnnotationRenderer core engine that provides\n * a declarative, props-based API for rendering PDF documents with\n * timeline-synchronized annotations.\n *\n * Features:\n * - Automatic lifecycle management (initialization and cleanup)\n * - Declarative prop-to-method synchronization\n * - PDF rendering with pdf.js\n * - Timeline-synchronized annotation display\n * - Support for highlight, text, and ink annotations\n * - Page navigation and zoom control\n *\n * @component\n * @example\n * // Basic usage\n * <AnnotPdf\n * pdfUrl=\"/document.pdf\"\n * page={1}\n * scale={1.5}\n * annotations={[]}\n * currentTime={0}\n * />\n *\n * @example\n * // With audio synchronization\n * const [currentTime, setCurrentTime] = useState(0);\n *\n * <div>\n * <AnnotPdf\n * pdfUrl=\"/lecture.pdf\"\n * page={1}\n * scale={1.5}\n * annotations={annotations}\n * currentTime={currentTime}\n * onLoad={({ pageCount }) => console.log('Loaded:', pageCount)}\n * />\n * <audio\n * src=\"/lecture.mp3\"\n * onTimeUpdate={(e) => setCurrentTime(e.target.currentTime)}\n * controls\n * />\n * </div>\n *\n * @param {Object} props - Component props\n * @param {string} props.pdfUrl - PDF document URL (required)\n * @param {number} [props.page=1] - Current page number (1-indexed)\n * @param {number} [props.scale=1.5] - Zoom scale factor\n * @param {Array} [props.annotations=[]] - Array of annotation objects\n * @param {number} [props.currentTime=0] - Timeline position in seconds\n * @param {Function} [props.onLoad] - Callback when PDF loads: ({pageCount}) => void\n * @param {Function} [props.onError] - Callback on error: (error) => void\n * @param {Function} [props.onPageChange] - Callback when page changes: (page) => void\n * @param {Object} [props.strokeConfig] - Stroke rendering configuration\n * @param {string} [props.strokeConfig.preset] - Preset name: 'default', 'blue', 'minimal'\n * @param {Object} [props.strokeConfig.highlight] - Highlight style overrides\n * @param {number} [props.strokeConfig.highlight.width] - Highlight stroke width (16-32)\n * @param {Object} [props.strokeConfig.text] - Text style overrides\n * @param {number} [props.strokeConfig.text.width] - Text stroke width (1-4)\n * @param {string} [props.className] - CSS class for container div\n * @param {Object} [props.style] - Inline styles for container div\n * @param {Object} [props.canvasStyle] - Inline styles for canvas element\n * @returns {JSX.Element} PDF viewer component with annotation layers\n */\n\n// ============================================================================\n// SECTION 3: COMPONENT DEFINITION\n// ============================================================================\n\nfunction AnnotPdf({\n // Required props\n pdfUrl,\n\n // Optional props with defaults\n page = 1,\n scale = 1.5,\n annotations = [],\n currentTime = 0,\n\n // Stroke configuration\n strokeConfig,\n\n // Callbacks\n onLoad,\n onError,\n onPageChange,\n\n // Styling\n className,\n style,\n canvasStyle\n}) {\n\n // ==========================================================================\n // SECTION 4: REFS INITIALIZATION\n // ==========================================================================\n\n /**\n * Reference to the canvas element for PDF rendering\n * @type {React.RefObject<HTMLCanvasElement>}\n */\n const canvasRef = useRef(null);\n\n /**\n * Reference to the layer container div for annotation layers\n * @type {React.RefObject<HTMLDivElement>}\n */\n const layerContainerRef = useRef(null);\n\n /**\n * Reference to the AnnotationRenderer engine instance\n * Stored in ref to avoid triggering re-renders\n * @type {React.RefObject<AnnotationRenderer|null>}\n */\n const engineRef = useRef(null);\n\n /**\n * Reference to the render operation queue\n * Ensures sequential execution of async canvas operations\n * Prevents PDF.js race condition: \"Cannot use the same canvas during multiple render() operations\"\n * @type {React.RefObject<Promise<void>>}\n */\n const renderQueue = useRef(Promise.resolve());\n\n // ==========================================================================\n // SECTION 4.5: RENDER QUEUE HELPER\n // ==========================================================================\n\n /**\n * Queue a render operation to execute sequentially\n *\n * This helper ensures that async canvas operations (loadPDF, setPage, setScale)\n * execute one at a time, preventing concurrent access to the PDF.js canvas.\n * Uses Promise chaining to maintain operation order.\n *\n * @param {Function} operation - Async function returning a Promise\n * @returns {void}\n */\n const queueOperation = useCallback((operation) => {\n renderQueue.current = renderQueue.current\n .then(operation)\n .catch(error => {\n // Log errors but don't break the queue\n console.error('AnnotPdf: Queued operation failed:', error);\n });\n }, []);\n\n // ==========================================================================\n // SECTION 4.6: STROKE CONFIG BUILDER\n // ==========================================================================\n\n /**\n * Build merged stroke config from preset and overrides\n *\n * If strokeConfig.preset is provided, loads base config from presets.\n * Then merges any override values (highlight.width, text.width).\n */\n const mergedStrokeConfig = useMemo(() => {\n if (!strokeConfig) {\n return undefined;\n }\n\n // Start with preset or empty object\n const presetName = strokeConfig.preset || 'default';\n const presetConfig = getPreset(presetName);\n\n // Build config object matching StrokeRenderer format\n const config = {\n highlight: {\n color: presetConfig.highlight.color,\n width: strokeConfig.highlight?.width ?? presetConfig.highlight.width\n },\n text: {\n color: presetConfig.handText.color,\n width: strokeConfig.text?.width ?? presetConfig.handText.width\n }\n };\n\n return config;\n }, [strokeConfig]);\n\n // ==========================================================================\n // SECTION 5: ENGINE INITIALIZATION AND CLEANUP\n // ==========================================================================\n\n /**\n * Initialize AnnotationRenderer on component mount\n * Cleanup on component unmount\n */\n useEffect(() => {\n // Guard: Wait for DOM elements to be ready\n if (!canvasRef.current || !layerContainerRef.current) {\n return;\n }\n\n // Initialize engine with stroke config if provided\n try {\n engineRef.current = new AnnotationRenderer({\n canvasElement: canvasRef.current,\n container: layerContainerRef.current,\n strokeConfig: mergedStrokeConfig\n });\n } catch (error) {\n console.error('AnnotPdf: Failed to initialize renderer:', error);\n if (onError) {\n onError(error);\n }\n }\n\n // Cleanup on unmount\n return () => {\n if (engineRef.current) {\n engineRef.current.destroy();\n engineRef.current = null;\n }\n };\n }, []); // Empty deps - run once on mount\n\n // ==========================================================================\n // SECTION 6: PDF LOADING SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Load PDF document when pdfUrl prop changes\n * Handles async operation with cancellation support\n * Uses render queue to prevent concurrent canvas operations\n */\n useEffect(() => {\n // Guard: Engine must exist and pdfUrl must be valid\n if (!engineRef.current || !pdfUrl) {\n return;\n }\n\n let cancelled = false;\n\n const loadPdf = async () => {\n try {\n const result = await engineRef.current.loadPDF(pdfUrl);\n\n // Check if component unmounted during async operation\n if (cancelled) return;\n\n // Check if load was successful\n if (!result.success) {\n console.error('AnnotPdf: Failed to load PDF:', result.error);\n if (onError) {\n onError(new Error(result.error));\n }\n return;\n }\n\n // Call onLoad callback with pageCount from result\n if (onLoad) {\n onLoad({ pageCount: result.pageCount });\n }\n } catch (error) {\n if (cancelled) return;\n\n console.error('AnnotPdf: Failed to load PDF:', error);\n if (onError) {\n onError(error);\n }\n }\n };\n\n // Queue the PDF loading operation to prevent race conditions\n queueOperation(loadPdf);\n\n // Cleanup: Prevent state updates if component unmounts during load\n return () => {\n cancelled = true;\n };\n }, [pdfUrl, queueOperation]);\n\n // ==========================================================================\n // SECTION 7: PAGE SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Sync page prop to engine.setPage() method\n * Uses render queue to prevent concurrent canvas operations\n */\n useEffect(() => {\n // Guard: Engine must exist and page must be valid\n if (!engineRef.current || !page || typeof page !== 'number') {\n return;\n }\n\n // Queue the page change operation to prevent race conditions\n queueOperation(async () => {\n try {\n const result = await engineRef.current.setPage(page);\n\n // Check if page change was successful\n if (!result.success) {\n console.error('AnnotPdf: Failed to set page:', result.error);\n if (onError) {\n onError(new Error(result.error));\n }\n return;\n }\n\n // Optional: Notify parent of successful page change\n if (onPageChange) {\n onPageChange(page);\n }\n } catch (error) {\n console.error('AnnotPdf: Failed to set page:', error);\n if (onError) {\n onError(error);\n }\n }\n });\n }, [page, queueOperation]);\n\n // ==========================================================================\n // SECTION 8: SCALE SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Sync scale prop to engine.setScale() method\n * Uses render queue to prevent concurrent canvas operations\n */\n useEffect(() => {\n // Guard: Engine must exist and scale must be valid\n if (!engineRef.current || !scale || typeof scale !== 'number') {\n return;\n }\n\n // Queue the scale change operation to prevent race conditions\n queueOperation(async () => {\n try {\n const result = await engineRef.current.setScale(scale);\n\n // Check if scale change was successful\n if (!result.success) {\n console.error('AnnotPdf: Failed to set scale:', result.error);\n if (onError) {\n onError(new Error(result.error));\n }\n }\n } catch (error) {\n console.error('AnnotPdf: Failed to set scale:', error);\n if (onError) {\n onError(error);\n }\n }\n });\n }, [scale, queueOperation]);\n\n // ==========================================================================\n // SECTION 9: ANNOTATIONS SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Sync annotations prop to engine.setAnnotations() method\n */\n useEffect(() => {\n // Guard: Engine must exist\n if (!engineRef.current) {\n return;\n }\n\n // Sync annotations to engine (default to empty array)\n try {\n engineRef.current.setAnnotations(annotations || []);\n } catch (error) {\n console.error('AnnotPdf: Failed to set annotations:', error);\n if (onError) {\n onError(error);\n }\n }\n }, [annotations]);\n\n // ==========================================================================\n // SECTION 10: TIMELINE SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Sync currentTime prop to engine.setTime() method\n */\n useEffect(() => {\n // Guard: Engine must exist and currentTime must be defined\n if (!engineRef.current || currentTime === undefined || currentTime === null) {\n return;\n }\n\n // Sync timeline to engine\n try {\n engineRef.current.setTime(currentTime);\n } catch (error) {\n console.error('AnnotPdf: Failed to set time:', error);\n if (onError) {\n onError(error);\n }\n }\n }, [currentTime]);\n\n // ==========================================================================\n // SECTION 10.5: STROKE CONFIG SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Sync strokeConfig prop to engine at runtime for live preview\n * Skips initial render (handled by initialization)\n */\n const isFirstRender = useRef(true);\n\n useEffect(() => {\n // Skip first render - config is passed during initialization\n if (isFirstRender.current) {\n isFirstRender.current = false;\n return;\n }\n\n // Guard: Engine must exist\n if (!engineRef.current) {\n return;\n }\n\n // Update stroke config if provided\n if (mergedStrokeConfig) {\n try {\n engineRef.current.updateStrokeConfig(mergedStrokeConfig);\n } catch (error) {\n console.error('AnnotPdf: Failed to update stroke config:', error);\n if (onError) {\n onError(error);\n }\n }\n }\n }, [mergedStrokeConfig]);\n\n // ==========================================================================\n // SECTION 11: STYLING DEFINITIONS\n // ==========================================================================\n\n /**\n * Default container styles\n * Merged with user-provided styles (user styles override defaults)\n */\n const defaultContainerStyle = {\n position: 'relative',\n display: 'inline-block',\n lineHeight: 0, // Remove extra space below canvas\n ...style // User styles override defaults\n };\n\n /**\n * Default layer container styles\n * Positions layer div absolutely over canvas\n */\n const defaultLayerStyle = {\n position: 'absolute',\n top: 0,\n left: 0,\n width: '100%',\n height: '100%',\n pointerEvents: 'none', // Allow clicks to pass through to canvas\n overflow: 'hidden'\n };\n\n /**\n * Default canvas styles\n * Merged with user-provided canvasStyle\n */\n const defaultCanvasStyle = {\n display: 'block',\n ...canvasStyle // User styles override defaults\n };\n\n // ==========================================================================\n // SECTION 12: JSX RETURN\n // ==========================================================================\n\n return (\n <div className={className} style={defaultContainerStyle}>\n <canvas ref={canvasRef} style={defaultCanvasStyle} />\n <div ref={layerContainerRef} style={defaultLayerStyle} />\n </div>\n );\n}\n\n// ============================================================================\n// SECTION 13: EXPORT\n// ============================================================================\n\nexport default AnnotPdf;\n"],"names":["AnnotPdf","pdfUrl","page","scale","annotations","currentTime","strokeConfig","onLoad","onError","onPageChange","className","style","canvasStyle","canvasRef","useRef","layerContainerRef","engineRef","renderQueue","queueOperation","useCallback","operation","error","mergedStrokeConfig","useMemo","presetName","presetConfig","getPreset","useEffect","AnnotationRenderer","cancelled","result","isFirstRender","defaultContainerStyle","defaultLayerStyle","defaultCanvasStyle","jsxs","jsx"],"mappings":"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"}
1
+ {"version":3,"file":"index12.cjs","sources":["../src/adapters/AnnotPdf.jsx"],"sourcesContent":["// ============================================================================\n// SECTION 1: IMPORTS\n// ============================================================================\n\nimport { useRef, useEffect, useCallback, useMemo, useState } from 'react';\nimport { AnnotationRenderer } from '../core/AnnotationRenderer.js';\nimport { getPreset } from '../pen/presets.js';\n\n// ============================================================================\n// SECTION 2: JSDOC DOCUMENTATION\n// ============================================================================\n\n/**\n * AnnotPdf - Declarative React component for PDF annotation rendering\n *\n * A React wrapper around the AnnotationRenderer core engine that provides\n * a declarative, props-based API for rendering PDF documents with\n * timeline-synchronized annotations.\n *\n * Features:\n * - Automatic lifecycle management (initialization and cleanup)\n * - Declarative prop-to-method synchronization\n * - PDF rendering with pdf.js\n * - Timeline-synchronized annotation display\n * - Support for highlight, text, and ink annotations\n * - Page navigation and zoom control\n *\n * @component\n * @example\n * // Basic usage\n * <AnnotPdf\n * pdfUrl=\"/document.pdf\"\n * page={1}\n * scale={1.5}\n * annotations={[]}\n * currentTime={0}\n * />\n *\n * @example\n * // With audio synchronization (discrete mode - ~4fps)\n * const [currentTime, setCurrentTime] = useState(0);\n *\n * <div>\n * <AnnotPdf\n * pdfUrl=\"/lecture.pdf\"\n * page={1}\n * scale={1.5}\n * annotations={annotations}\n * currentTime={currentTime}\n * onLoad={({ pageCount }) => console.log('Loaded:', pageCount)}\n * />\n * <audio\n * src=\"/lecture.mp3\"\n * onTimeUpdate={(e) => setCurrentTime(e.target.currentTime)}\n * controls\n * />\n * </div>\n *\n * @example\n * // With audio synchronization (continuous mode - 60fps, smoother)\n * const audioRef = useRef(null);\n *\n * <div>\n * <AnnotPdf\n * pdfUrl=\"/lecture.pdf\"\n * page={1}\n * scale={1.5}\n * annotations={annotations}\n * audioRef={audioRef}\n * onLoad={({ pageCount }) => console.log('Loaded:', pageCount)}\n * />\n * <audio ref={audioRef} src=\"/lecture.mp3\" controls />\n * </div>\n *\n * @param {Object} props - Component props\n * @param {string} props.pdfUrl - PDF document URL (required)\n * @param {number} [props.page=1] - Current page number (1-indexed)\n * @param {number} [props.scale=1.5] - Zoom scale factor\n * @param {Array} [props.annotations=[]] - Array of annotation objects\n * @param {number} [props.currentTime=0] - Timeline position in seconds (discrete mode)\n * @param {React.RefObject<HTMLAudioElement>} [props.audioRef] - Audio element ref for continuous sync (60fps)\n * @param {Function} [props.onLoad] - Callback when PDF loads: ({pageCount}) => void\n * @param {Function} [props.onError] - Callback on error: (error) => void\n * @param {Function} [props.onPageChange] - Callback when page changes: (page) => void\n * @param {Object} [props.strokeConfig] - Stroke rendering configuration\n * @param {string} [props.strokeConfig.preset] - Preset name: 'default', 'blue', 'minimal'\n * @param {Object} [props.strokeConfig.highlight] - Highlight style overrides\n * @param {number} [props.strokeConfig.highlight.width] - Highlight stroke width (16-32)\n * @param {Object} [props.strokeConfig.text] - Text style overrides\n * @param {number} [props.strokeConfig.text.width] - Text stroke width (1-4)\n * @param {string} [props.className] - CSS class for container div\n * @param {Object} [props.style] - Inline styles for container div\n * @param {Object} [props.canvasStyle] - Inline styles for canvas element\n * @returns {JSX.Element} PDF viewer component with annotation layers\n */\n\n// ============================================================================\n// SECTION 3: COMPONENT DEFINITION\n// ============================================================================\n\nfunction AnnotPdf({\n // Required props\n pdfUrl,\n\n // Optional props with defaults\n page = 1,\n scale = 1.5,\n annotations = [],\n currentTime = 0,\n\n // Continuous sync mode (use instead of currentTime for smoother animation)\n audioRef,\n\n // Stroke configuration\n strokeConfig,\n\n // Callbacks\n onLoad,\n onError,\n onPageChange,\n\n // Styling\n className,\n style,\n canvasStyle\n}) {\n\n // ==========================================================================\n // SECTION 4: REFS INITIALIZATION\n // ==========================================================================\n\n /**\n * Reference to the canvas element for PDF rendering\n * @type {React.RefObject<HTMLCanvasElement>}\n */\n const canvasRef = useRef(null);\n\n /**\n * Reference to the layer container div for annotation layers\n * @type {React.RefObject<HTMLDivElement>}\n */\n const layerContainerRef = useRef(null);\n\n /**\n * Reference to the AnnotationRenderer engine instance\n * Stored in ref to avoid triggering re-renders\n * @type {React.RefObject<AnnotationRenderer|null>}\n */\n const engineRef = useRef(null);\n\n /**\n * Reference to the render operation queue\n * Ensures sequential execution of async canvas operations\n * Prevents PDF.js race condition: \"Cannot use the same canvas during multiple render() operations\"\n * @type {React.RefObject<Promise<void>>}\n */\n const renderQueue = useRef(Promise.resolve());\n\n /**\n * State to track engine initialization\n * Used to trigger dependent effects after engine is ready\n */\n const [isEngineReady, setEngineReady] = useState(false);\n\n // ==========================================================================\n // SECTION 4.5: RENDER QUEUE HELPER\n // ==========================================================================\n\n /**\n * Queue a render operation to execute sequentially\n *\n * This helper ensures that async canvas operations (loadPDF, setPage, setScale)\n * execute one at a time, preventing concurrent access to the PDF.js canvas.\n * Uses Promise chaining to maintain operation order.\n *\n * @param {Function} operation - Async function returning a Promise\n * @returns {void}\n */\n const queueOperation = useCallback((operation) => {\n renderQueue.current = renderQueue.current\n .then(operation)\n .catch(error => {\n // Log errors but don't break the queue\n console.error('AnnotPdf: Queued operation failed:', error);\n });\n }, []);\n\n // ==========================================================================\n // SECTION 4.6: STROKE CONFIG BUILDER\n // ==========================================================================\n\n /**\n * Build merged stroke config from preset and overrides\n *\n * If strokeConfig.preset is provided, loads base config from presets.\n * Then merges any override values (highlight.width, text.width).\n */\n const mergedStrokeConfig = useMemo(() => {\n if (!strokeConfig) {\n return undefined;\n }\n\n // Start with preset or empty object\n const presetName = strokeConfig.preset || 'default';\n const presetConfig = getPreset(presetName);\n\n // Build config object matching StrokeRenderer format\n const config = {\n highlight: {\n color: presetConfig.highlight.color,\n width: strokeConfig.highlight?.width ?? presetConfig.highlight.width\n },\n text: {\n color: presetConfig.handText.color,\n width: strokeConfig.text?.width ?? presetConfig.handText.width\n }\n };\n\n return config;\n }, [strokeConfig]);\n\n // ==========================================================================\n // SECTION 5: ENGINE INITIALIZATION AND CLEANUP\n // ==========================================================================\n\n /**\n * Initialize AnnotationRenderer on component mount\n * Cleanup on component unmount\n */\n useEffect(() => {\n // Guard: Wait for DOM elements to be ready\n if (!canvasRef.current || !layerContainerRef.current) {\n return;\n }\n\n // Initialize engine with stroke config if provided\n try {\n engineRef.current = new AnnotationRenderer({\n canvasElement: canvasRef.current,\n container: layerContainerRef.current,\n strokeConfig: mergedStrokeConfig\n });\n // Signal that engine is ready for dependent effects\n setEngineReady(true);\n } catch (error) {\n console.error('AnnotPdf: Failed to initialize renderer:', error);\n if (onError) {\n onError(error);\n }\n }\n\n // Cleanup on unmount\n return () => {\n setEngineReady(false);\n if (engineRef.current) {\n engineRef.current.destroy();\n engineRef.current = null;\n }\n };\n }, []); // Empty deps - run once on mount\n\n // ==========================================================================\n // SECTION 6: PDF LOADING SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Load PDF document when pdfUrl prop changes\n * Handles async operation with cancellation support\n * Uses render queue to prevent concurrent canvas operations\n */\n useEffect(() => {\n // Guard: Wait for engine and valid pdfUrl\n if (!isEngineReady || !pdfUrl) {\n return;\n }\n\n let cancelled = false;\n\n const loadPdf = async () => {\n // Check engine exists at execution time (not queue time)\n if (!engineRef.current) {\n return;\n }\n\n try {\n const result = await engineRef.current.loadPDF(pdfUrl);\n\n // Check if component unmounted during async operation\n if (cancelled) return;\n\n // Check if load was successful\n if (!result.success) {\n console.error('AnnotPdf: Failed to load PDF:', result.error);\n if (onError) {\n onError(new Error(result.error));\n }\n return;\n }\n\n // Call onLoad callback with pageCount from result\n if (onLoad) {\n onLoad({ pageCount: result.pageCount });\n }\n } catch (error) {\n if (cancelled) return;\n\n console.error('AnnotPdf: Failed to load PDF:', error);\n if (onError) {\n onError(error);\n }\n }\n };\n\n // Queue the PDF loading operation to prevent race conditions\n queueOperation(loadPdf);\n\n // Cleanup: Prevent state updates if component unmounts during load\n return () => {\n cancelled = true;\n };\n }, [pdfUrl, isEngineReady, queueOperation]);\n\n // ==========================================================================\n // SECTION 7: PAGE SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Sync page prop to engine.setPage() method\n * Uses render queue to prevent concurrent canvas operations\n */\n useEffect(() => {\n // Guard: Wait for engine and valid page\n if (!isEngineReady || !page || typeof page !== 'number') {\n return;\n }\n\n // Queue the page change operation to prevent race conditions\n queueOperation(async () => {\n // Check engine exists at execution time (not queue time)\n if (!engineRef.current) {\n return;\n }\n\n try {\n const result = await engineRef.current.setPage(page);\n\n // Check if page change was successful\n if (!result.success) {\n console.error('AnnotPdf: Failed to set page:', result.error);\n if (onError) {\n onError(new Error(result.error));\n }\n return;\n }\n\n // Optional: Notify parent of successful page change\n if (onPageChange) {\n onPageChange(page);\n }\n } catch (error) {\n console.error('AnnotPdf: Failed to set page:', error);\n if (onError) {\n onError(error);\n }\n }\n });\n }, [page, isEngineReady, queueOperation]);\n\n // ==========================================================================\n // SECTION 8: SCALE SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Sync scale prop to engine.setScale() method\n * Uses render queue to prevent concurrent canvas operations\n */\n useEffect(() => {\n // Guard: Wait for engine and valid scale\n if (!isEngineReady || !scale || typeof scale !== 'number') {\n return;\n }\n\n // Queue the scale change operation to prevent race conditions\n queueOperation(async () => {\n // Check engine exists at execution time (not queue time)\n if (!engineRef.current) {\n return;\n }\n\n try {\n const result = await engineRef.current.setScale(scale);\n\n // Check if scale change was successful\n if (!result.success) {\n console.error('AnnotPdf: Failed to set scale:', result.error);\n if (onError) {\n onError(new Error(result.error));\n }\n }\n } catch (error) {\n console.error('AnnotPdf: Failed to set scale:', error);\n if (onError) {\n onError(error);\n }\n }\n });\n }, [scale, isEngineReady, queueOperation]);\n\n // ==========================================================================\n // SECTION 9: ANNOTATIONS SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Sync annotations prop to engine.setAnnotations() method\n */\n useEffect(() => {\n // Guard: Wait for engine\n if (!isEngineReady || !engineRef.current) {\n return;\n }\n\n // Sync annotations to engine (default to empty array)\n try {\n engineRef.current.setAnnotations(annotations || []);\n } catch (error) {\n console.error('AnnotPdf: Failed to set annotations:', error);\n if (onError) {\n onError(error);\n }\n }\n }, [annotations, isEngineReady]);\n\n // ==========================================================================\n // SECTION 10: TIMELINE SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Sync currentTime prop to engine.setTime() method\n * Only used when audioRef is NOT provided (discrete mode)\n */\n useEffect(() => {\n // Skip if using continuous sync mode\n if (audioRef) {\n return;\n }\n\n // Guard: Wait for engine and valid currentTime\n if (!isEngineReady || !engineRef.current || currentTime === undefined || currentTime === null) {\n return;\n }\n\n // Sync timeline to engine\n try {\n engineRef.current.setTime(currentTime);\n } catch (error) {\n console.error('AnnotPdf: Failed to set time:', error);\n if (onError) {\n onError(error);\n }\n }\n }, [currentTime, audioRef, isEngineReady]);\n\n // ==========================================================================\n // SECTION 10.1: CONTINUOUS SYNC MODE (audioRef)\n // ==========================================================================\n\n /**\n * Manage continuous sync mode when audioRef is provided\n *\n * Uses requestAnimationFrame for 60fps smooth animation instead of\n * discrete currentTime updates (~4fps from audio timeupdate events).\n */\n useEffect(() => {\n // Guard: Skip if not using continuous sync mode\n if (!audioRef?.current) {\n return;\n }\n\n // Guard: Wait for engine\n if (!isEngineReady || !engineRef.current) {\n return;\n }\n\n const audio = audioRef.current;\n /**\n * Start continuous sync when audio plays\n */\n const handlePlay = () => {\n if (!engineRef.current) return;\n engineRef.current.startContinuousSync(() => audio.currentTime);\n };\n\n /**\n * Stop continuous sync when audio pauses\n */\n const handlePause = () => {\n if (!engineRef.current) return;\n engineRef.current.stopContinuousSync();\n engineRef.current.setTime(audio.currentTime);\n };\n\n /**\n * Handle seeking - update time immediately\n */\n const handleSeeked = () => {\n if (!engineRef.current) return;\n engineRef.current.setTime(audio.currentTime);\n };\n\n // Add event listeners\n audio.addEventListener('play', handlePlay);\n audio.addEventListener('pause', handlePause);\n audio.addEventListener('ended', handlePause);\n audio.addEventListener('seeked', handleSeeked);\n\n // If audio is already playing, start sync immediately\n if (!audio.paused) {\n handlePlay();\n }\n\n // Cleanup on unmount or audioRef change\n return () => {\n audio.removeEventListener('play', handlePlay);\n audio.removeEventListener('pause', handlePause);\n audio.removeEventListener('ended', handlePause);\n audio.removeEventListener('seeked', handleSeeked);\n // Guard: engine may have been destroyed by initialization effect cleanup\n if (engineRef.current) {\n engineRef.current.stopContinuousSync();\n }\n };\n }, [audioRef, isEngineReady]);\n\n // ==========================================================================\n // SECTION 10.5: STROKE CONFIG SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Sync strokeConfig prop to engine at runtime for live preview\n * Skips initial render (handled by initialization)\n */\n const isFirstRender = useRef(true);\n\n useEffect(() => {\n // Skip first render - config is passed during initialization\n if (isFirstRender.current) {\n isFirstRender.current = false;\n return;\n }\n\n // Guard: Engine must exist\n if (!engineRef.current) {\n return;\n }\n\n // Update stroke config if provided\n if (mergedStrokeConfig) {\n try {\n engineRef.current.updateStrokeConfig(mergedStrokeConfig);\n } catch (error) {\n console.error('AnnotPdf: Failed to update stroke config:', error);\n if (onError) {\n onError(error);\n }\n }\n }\n }, [mergedStrokeConfig]);\n\n // ==========================================================================\n // SECTION 11: STYLING DEFINITIONS\n // ==========================================================================\n\n /**\n * Default container styles\n * Merged with user-provided styles (user styles override defaults)\n */\n const defaultContainerStyle = {\n position: 'relative',\n display: 'inline-block',\n lineHeight: 0, // Remove extra space below canvas\n ...style // User styles override defaults\n };\n\n /**\n * Default layer container styles\n * Positions layer div absolutely over canvas\n */\n const defaultLayerStyle = {\n position: 'absolute',\n top: 0,\n left: 0,\n width: '100%',\n height: '100%',\n pointerEvents: 'none', // Allow clicks to pass through to canvas\n overflow: 'hidden'\n };\n\n /**\n * Default canvas styles\n * Merged with user-provided canvasStyle\n */\n const defaultCanvasStyle = {\n display: 'block',\n ...canvasStyle // User styles override defaults\n };\n\n // ==========================================================================\n // SECTION 12: JSX RETURN\n // ==========================================================================\n\n return (\n <div className={className} style={defaultContainerStyle}>\n <canvas ref={canvasRef} style={defaultCanvasStyle} />\n <div ref={layerContainerRef} style={defaultLayerStyle} />\n </div>\n );\n}\n\n// ============================================================================\n// SECTION 13: EXPORT\n// ============================================================================\n\nexport default AnnotPdf;\n"],"names":["AnnotPdf","pdfUrl","page","scale","annotations","currentTime","audioRef","strokeConfig","onLoad","onError","onPageChange","className","style","canvasStyle","canvasRef","useRef","layerContainerRef","engineRef","renderQueue","isEngineReady","setEngineReady","useState","queueOperation","useCallback","operation","error","mergedStrokeConfig","useMemo","presetName","presetConfig","getPreset","useEffect","AnnotationRenderer","cancelled","result","audio","handlePlay","handlePause","handleSeeked","isFirstRender","defaultContainerStyle","defaultLayerStyle","defaultCanvasStyle","jsxs","jsx"],"mappings":"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"}
package/dist/index12.js CHANGED
@@ -1,138 +1,156 @@
1
- import { jsxs as k, jsx as F } from "react/jsx-runtime";
2
- import { useRef as u, useCallback as D, useMemo as j, useEffect as i } from "react";
3
- import { AnnotationRenderer as Q } from "./index2.js";
4
- import { getPreset as q } from "./index17.js";
5
- function O({
1
+ import { jsxs as j, jsx as L } from "react/jsx-runtime";
2
+ import { useRef as d, useState as Q, useCallback as q, useMemo as z, useEffect as c } from "react";
3
+ import { AnnotationRenderer as H } from "./index2.js";
4
+ import { getPreset as M } from "./index17.js";
5
+ function I({
6
6
  // Required props
7
- pdfUrl: h,
7
+ pdfUrl: p,
8
8
  // Optional props with defaults
9
- page: s = 1,
10
- scale: a = 1.5,
11
- annotations: p = [],
12
- currentTime: f = 0,
9
+ page: u = 1,
10
+ scale: f = 1.5,
11
+ annotations: P = [],
12
+ currentTime: h = 0,
13
+ // Continuous sync mode (use instead of currentTime for smoother animation)
14
+ audioRef: l,
13
15
  // Stroke configuration
14
- strokeConfig: l,
16
+ strokeConfig: a,
15
17
  // Callbacks
16
18
  onLoad: g,
17
- onError: t,
19
+ onError: r,
18
20
  onPageChange: w,
19
21
  // Styling
20
- className: v,
21
- style: x,
22
- canvasStyle: b
22
+ className: x,
23
+ style: b,
24
+ canvasStyle: k
23
25
  }) {
24
- const y = u(null), P = u(null), r = u(null), m = u(Promise.resolve()), c = D((e) => {
25
- m.current = m.current.then(e).catch((o) => {
26
+ const v = d(null), m = d(null), t = d(null), A = d(Promise.resolve()), [i, F] = Q(!1), s = q((e) => {
27
+ A.current = A.current.then(e).catch((o) => {
26
28
  console.error("AnnotPdf: Queued operation failed:", o);
27
29
  });
28
- }, []), d = j(() => {
29
- if (!l)
30
+ }, []), y = z(() => {
31
+ if (!a)
30
32
  return;
31
- const e = l.preset || "default", o = q(e);
33
+ const e = a.preset || "default", o = M(e);
32
34
  return {
33
35
  highlight: {
34
36
  color: o.highlight.color,
35
- width: l.highlight?.width ?? o.highlight.width
37
+ width: a.highlight?.width ?? o.highlight.width
36
38
  },
37
39
  text: {
38
40
  color: o.handText.color,
39
- width: l.text?.width ?? o.handText.width
41
+ width: a.text?.width ?? o.handText.width
40
42
  }
41
43
  };
42
- }, [l]);
43
- i(() => {
44
- if (!(!y.current || !P.current)) {
44
+ }, [a]);
45
+ c(() => {
46
+ if (!(!v.current || !m.current)) {
45
47
  try {
46
- r.current = new Q({
47
- canvasElement: y.current,
48
- container: P.current,
49
- strokeConfig: d
50
- });
48
+ t.current = new H({
49
+ canvasElement: v.current,
50
+ container: m.current,
51
+ strokeConfig: y
52
+ }), F(!0);
51
53
  } catch (e) {
52
- console.error("AnnotPdf: Failed to initialize renderer:", e), t && t(e);
54
+ console.error("AnnotPdf: Failed to initialize renderer:", e), r && r(e);
53
55
  }
54
56
  return () => {
55
- r.current && (r.current.destroy(), r.current = null);
57
+ F(!1), t.current && (t.current.destroy(), t.current = null);
56
58
  };
57
59
  }
58
- }, []), i(() => {
59
- if (!r.current || !h)
60
+ }, []), c(() => {
61
+ if (!i || !p)
60
62
  return;
61
63
  let e = !1;
62
- return c(async () => {
63
- try {
64
- const n = await r.current.loadPDF(h);
65
- if (e) return;
66
- if (!n.success) {
67
- console.error("AnnotPdf: Failed to load PDF:", n.error), t && t(new Error(n.error));
68
- return;
64
+ return s(async () => {
65
+ if (t.current)
66
+ try {
67
+ const n = await t.current.loadPDF(p);
68
+ if (e) return;
69
+ if (!n.success) {
70
+ console.error("AnnotPdf: Failed to load PDF:", n.error), r && r(new Error(n.error));
71
+ return;
72
+ }
73
+ g && g({ pageCount: n.pageCount });
74
+ } catch (n) {
75
+ if (e) return;
76
+ console.error("AnnotPdf: Failed to load PDF:", n), r && r(n);
69
77
  }
70
- g && g({ pageCount: n.pageCount });
71
- } catch (n) {
72
- if (e) return;
73
- console.error("AnnotPdf: Failed to load PDF:", n), t && t(n);
74
- }
75
78
  }), () => {
76
79
  e = !0;
77
80
  };
78
- }, [h, c]), i(() => {
79
- !r.current || !s || typeof s != "number" || c(async () => {
80
- try {
81
- const e = await r.current.setPage(s);
82
- if (!e.success) {
83
- console.error("AnnotPdf: Failed to set page:", e.error), t && t(new Error(e.error));
84
- return;
81
+ }, [p, i, s]), c(() => {
82
+ !i || !u || typeof u != "number" || s(async () => {
83
+ if (t.current)
84
+ try {
85
+ const e = await t.current.setPage(u);
86
+ if (!e.success) {
87
+ console.error("AnnotPdf: Failed to set page:", e.error), r && r(new Error(e.error));
88
+ return;
89
+ }
90
+ w && w(u);
91
+ } catch (e) {
92
+ console.error("AnnotPdf: Failed to set page:", e), r && r(e);
85
93
  }
86
- w && w(s);
87
- } catch (e) {
88
- console.error("AnnotPdf: Failed to set page:", e), t && t(e);
89
- }
90
94
  });
91
- }, [s, c]), i(() => {
92
- !r.current || !a || typeof a != "number" || c(async () => {
93
- try {
94
- const e = await r.current.setScale(a);
95
- e.success || (console.error("AnnotPdf: Failed to set scale:", e.error), t && t(new Error(e.error)));
96
- } catch (e) {
97
- console.error("AnnotPdf: Failed to set scale:", e), t && t(e);
98
- }
95
+ }, [u, i, s]), c(() => {
96
+ !i || !f || typeof f != "number" || s(async () => {
97
+ if (t.current)
98
+ try {
99
+ const e = await t.current.setScale(f);
100
+ e.success || (console.error("AnnotPdf: Failed to set scale:", e.error), r && r(new Error(e.error)));
101
+ } catch (e) {
102
+ console.error("AnnotPdf: Failed to set scale:", e), r && r(e);
103
+ }
99
104
  });
100
- }, [a, c]), i(() => {
101
- if (r.current)
105
+ }, [f, i, s]), c(() => {
106
+ if (!(!i || !t.current))
102
107
  try {
103
- r.current.setAnnotations(p || []);
108
+ t.current.setAnnotations(P || []);
104
109
  } catch (e) {
105
- console.error("AnnotPdf: Failed to set annotations:", e), t && t(e);
110
+ console.error("AnnotPdf: Failed to set annotations:", e), r && r(e);
106
111
  }
107
- }, [p]), i(() => {
108
- if (!(!r.current || f === void 0 || f === null))
112
+ }, [P, i]), c(() => {
113
+ if (!l && !(!i || !t.current || h === void 0 || h === null))
109
114
  try {
110
- r.current.setTime(f);
115
+ t.current.setTime(h);
111
116
  } catch (e) {
112
- console.error("AnnotPdf: Failed to set time:", e), t && t(e);
117
+ console.error("AnnotPdf: Failed to set time:", e), r && r(e);
113
118
  }
114
- }, [f]);
115
- const A = u(!0);
116
- i(() => {
117
- if (A.current) {
118
- A.current = !1;
119
+ }, [h, l, i]), c(() => {
120
+ if (!l?.current || !i || !t.current)
121
+ return;
122
+ const e = l.current, o = () => {
123
+ t.current && t.current.startContinuousSync(() => e.currentTime);
124
+ }, n = () => {
125
+ t.current && (t.current.stopContinuousSync(), t.current.setTime(e.currentTime));
126
+ }, C = () => {
127
+ t.current && t.current.setTime(e.currentTime);
128
+ };
129
+ return e.addEventListener("play", o), e.addEventListener("pause", n), e.addEventListener("ended", n), e.addEventListener("seeked", C), e.paused || o(), () => {
130
+ e.removeEventListener("play", o), e.removeEventListener("pause", n), e.removeEventListener("ended", n), e.removeEventListener("seeked", C), t.current && t.current.stopContinuousSync();
131
+ };
132
+ }, [l, i]);
133
+ const S = d(!0);
134
+ c(() => {
135
+ if (S.current) {
136
+ S.current = !1;
119
137
  return;
120
138
  }
121
- if (r.current && d)
139
+ if (t.current && y)
122
140
  try {
123
- r.current.updateStrokeConfig(d);
141
+ t.current.updateStrokeConfig(y);
124
142
  } catch (e) {
125
- console.error("AnnotPdf: Failed to update stroke config:", e), t && t(e);
143
+ console.error("AnnotPdf: Failed to update stroke config:", e), r && r(e);
126
144
  }
127
- }, [d]);
128
- const R = {
145
+ }, [y]);
146
+ const T = {
129
147
  position: "relative",
130
148
  display: "inline-block",
131
149
  lineHeight: 0,
132
150
  // Remove extra space below canvas
133
- ...x
151
+ ...b
134
152
  // User styles override defaults
135
- }, S = {
153
+ }, R = {
136
154
  position: "absolute",
137
155
  top: 0,
138
156
  left: 0,
@@ -141,17 +159,17 @@ function O({
141
159
  pointerEvents: "none",
142
160
  // Allow clicks to pass through to canvas
143
161
  overflow: "hidden"
144
- }, C = {
162
+ }, D = {
145
163
  display: "block",
146
- ...b
164
+ ...k
147
165
  // User styles override defaults
148
166
  };
149
- return /* @__PURE__ */ k("div", { className: v, style: R, children: [
150
- /* @__PURE__ */ F("canvas", { ref: y, style: C }),
151
- /* @__PURE__ */ F("div", { ref: P, style: S })
167
+ return /* @__PURE__ */ j("div", { className: x, style: T, children: [
168
+ /* @__PURE__ */ L("canvas", { ref: v, style: D }),
169
+ /* @__PURE__ */ L("div", { ref: m, style: R })
152
170
  ] });
153
171
  }
154
172
  export {
155
- O as default
173
+ I as default
156
174
  };
157
175
  //# sourceMappingURL=index12.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index12.js","sources":["../src/adapters/AnnotPdf.jsx"],"sourcesContent":["// ============================================================================\n// SECTION 1: IMPORTS\n// ============================================================================\n\nimport { useRef, useEffect, useCallback, useMemo } from 'react';\nimport { AnnotationRenderer } from '../core/AnnotationRenderer.js';\nimport { getPreset } from '../pen/presets.js';\n\n// ============================================================================\n// SECTION 2: JSDOC DOCUMENTATION\n// ============================================================================\n\n/**\n * AnnotPdf - Declarative React component for PDF annotation rendering\n *\n * A React wrapper around the AnnotationRenderer core engine that provides\n * a declarative, props-based API for rendering PDF documents with\n * timeline-synchronized annotations.\n *\n * Features:\n * - Automatic lifecycle management (initialization and cleanup)\n * - Declarative prop-to-method synchronization\n * - PDF rendering with pdf.js\n * - Timeline-synchronized annotation display\n * - Support for highlight, text, and ink annotations\n * - Page navigation and zoom control\n *\n * @component\n * @example\n * // Basic usage\n * <AnnotPdf\n * pdfUrl=\"/document.pdf\"\n * page={1}\n * scale={1.5}\n * annotations={[]}\n * currentTime={0}\n * />\n *\n * @example\n * // With audio synchronization\n * const [currentTime, setCurrentTime] = useState(0);\n *\n * <div>\n * <AnnotPdf\n * pdfUrl=\"/lecture.pdf\"\n * page={1}\n * scale={1.5}\n * annotations={annotations}\n * currentTime={currentTime}\n * onLoad={({ pageCount }) => console.log('Loaded:', pageCount)}\n * />\n * <audio\n * src=\"/lecture.mp3\"\n * onTimeUpdate={(e) => setCurrentTime(e.target.currentTime)}\n * controls\n * />\n * </div>\n *\n * @param {Object} props - Component props\n * @param {string} props.pdfUrl - PDF document URL (required)\n * @param {number} [props.page=1] - Current page number (1-indexed)\n * @param {number} [props.scale=1.5] - Zoom scale factor\n * @param {Array} [props.annotations=[]] - Array of annotation objects\n * @param {number} [props.currentTime=0] - Timeline position in seconds\n * @param {Function} [props.onLoad] - Callback when PDF loads: ({pageCount}) => void\n * @param {Function} [props.onError] - Callback on error: (error) => void\n * @param {Function} [props.onPageChange] - Callback when page changes: (page) => void\n * @param {Object} [props.strokeConfig] - Stroke rendering configuration\n * @param {string} [props.strokeConfig.preset] - Preset name: 'default', 'blue', 'minimal'\n * @param {Object} [props.strokeConfig.highlight] - Highlight style overrides\n * @param {number} [props.strokeConfig.highlight.width] - Highlight stroke width (16-32)\n * @param {Object} [props.strokeConfig.text] - Text style overrides\n * @param {number} [props.strokeConfig.text.width] - Text stroke width (1-4)\n * @param {string} [props.className] - CSS class for container div\n * @param {Object} [props.style] - Inline styles for container div\n * @param {Object} [props.canvasStyle] - Inline styles for canvas element\n * @returns {JSX.Element} PDF viewer component with annotation layers\n */\n\n// ============================================================================\n// SECTION 3: COMPONENT DEFINITION\n// ============================================================================\n\nfunction AnnotPdf({\n // Required props\n pdfUrl,\n\n // Optional props with defaults\n page = 1,\n scale = 1.5,\n annotations = [],\n currentTime = 0,\n\n // Stroke configuration\n strokeConfig,\n\n // Callbacks\n onLoad,\n onError,\n onPageChange,\n\n // Styling\n className,\n style,\n canvasStyle\n}) {\n\n // ==========================================================================\n // SECTION 4: REFS INITIALIZATION\n // ==========================================================================\n\n /**\n * Reference to the canvas element for PDF rendering\n * @type {React.RefObject<HTMLCanvasElement>}\n */\n const canvasRef = useRef(null);\n\n /**\n * Reference to the layer container div for annotation layers\n * @type {React.RefObject<HTMLDivElement>}\n */\n const layerContainerRef = useRef(null);\n\n /**\n * Reference to the AnnotationRenderer engine instance\n * Stored in ref to avoid triggering re-renders\n * @type {React.RefObject<AnnotationRenderer|null>}\n */\n const engineRef = useRef(null);\n\n /**\n * Reference to the render operation queue\n * Ensures sequential execution of async canvas operations\n * Prevents PDF.js race condition: \"Cannot use the same canvas during multiple render() operations\"\n * @type {React.RefObject<Promise<void>>}\n */\n const renderQueue = useRef(Promise.resolve());\n\n // ==========================================================================\n // SECTION 4.5: RENDER QUEUE HELPER\n // ==========================================================================\n\n /**\n * Queue a render operation to execute sequentially\n *\n * This helper ensures that async canvas operations (loadPDF, setPage, setScale)\n * execute one at a time, preventing concurrent access to the PDF.js canvas.\n * Uses Promise chaining to maintain operation order.\n *\n * @param {Function} operation - Async function returning a Promise\n * @returns {void}\n */\n const queueOperation = useCallback((operation) => {\n renderQueue.current = renderQueue.current\n .then(operation)\n .catch(error => {\n // Log errors but don't break the queue\n console.error('AnnotPdf: Queued operation failed:', error);\n });\n }, []);\n\n // ==========================================================================\n // SECTION 4.6: STROKE CONFIG BUILDER\n // ==========================================================================\n\n /**\n * Build merged stroke config from preset and overrides\n *\n * If strokeConfig.preset is provided, loads base config from presets.\n * Then merges any override values (highlight.width, text.width).\n */\n const mergedStrokeConfig = useMemo(() => {\n if (!strokeConfig) {\n return undefined;\n }\n\n // Start with preset or empty object\n const presetName = strokeConfig.preset || 'default';\n const presetConfig = getPreset(presetName);\n\n // Build config object matching StrokeRenderer format\n const config = {\n highlight: {\n color: presetConfig.highlight.color,\n width: strokeConfig.highlight?.width ?? presetConfig.highlight.width\n },\n text: {\n color: presetConfig.handText.color,\n width: strokeConfig.text?.width ?? presetConfig.handText.width\n }\n };\n\n return config;\n }, [strokeConfig]);\n\n // ==========================================================================\n // SECTION 5: ENGINE INITIALIZATION AND CLEANUP\n // ==========================================================================\n\n /**\n * Initialize AnnotationRenderer on component mount\n * Cleanup on component unmount\n */\n useEffect(() => {\n // Guard: Wait for DOM elements to be ready\n if (!canvasRef.current || !layerContainerRef.current) {\n return;\n }\n\n // Initialize engine with stroke config if provided\n try {\n engineRef.current = new AnnotationRenderer({\n canvasElement: canvasRef.current,\n container: layerContainerRef.current,\n strokeConfig: mergedStrokeConfig\n });\n } catch (error) {\n console.error('AnnotPdf: Failed to initialize renderer:', error);\n if (onError) {\n onError(error);\n }\n }\n\n // Cleanup on unmount\n return () => {\n if (engineRef.current) {\n engineRef.current.destroy();\n engineRef.current = null;\n }\n };\n }, []); // Empty deps - run once on mount\n\n // ==========================================================================\n // SECTION 6: PDF LOADING SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Load PDF document when pdfUrl prop changes\n * Handles async operation with cancellation support\n * Uses render queue to prevent concurrent canvas operations\n */\n useEffect(() => {\n // Guard: Engine must exist and pdfUrl must be valid\n if (!engineRef.current || !pdfUrl) {\n return;\n }\n\n let cancelled = false;\n\n const loadPdf = async () => {\n try {\n const result = await engineRef.current.loadPDF(pdfUrl);\n\n // Check if component unmounted during async operation\n if (cancelled) return;\n\n // Check if load was successful\n if (!result.success) {\n console.error('AnnotPdf: Failed to load PDF:', result.error);\n if (onError) {\n onError(new Error(result.error));\n }\n return;\n }\n\n // Call onLoad callback with pageCount from result\n if (onLoad) {\n onLoad({ pageCount: result.pageCount });\n }\n } catch (error) {\n if (cancelled) return;\n\n console.error('AnnotPdf: Failed to load PDF:', error);\n if (onError) {\n onError(error);\n }\n }\n };\n\n // Queue the PDF loading operation to prevent race conditions\n queueOperation(loadPdf);\n\n // Cleanup: Prevent state updates if component unmounts during load\n return () => {\n cancelled = true;\n };\n }, [pdfUrl, queueOperation]);\n\n // ==========================================================================\n // SECTION 7: PAGE SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Sync page prop to engine.setPage() method\n * Uses render queue to prevent concurrent canvas operations\n */\n useEffect(() => {\n // Guard: Engine must exist and page must be valid\n if (!engineRef.current || !page || typeof page !== 'number') {\n return;\n }\n\n // Queue the page change operation to prevent race conditions\n queueOperation(async () => {\n try {\n const result = await engineRef.current.setPage(page);\n\n // Check if page change was successful\n if (!result.success) {\n console.error('AnnotPdf: Failed to set page:', result.error);\n if (onError) {\n onError(new Error(result.error));\n }\n return;\n }\n\n // Optional: Notify parent of successful page change\n if (onPageChange) {\n onPageChange(page);\n }\n } catch (error) {\n console.error('AnnotPdf: Failed to set page:', error);\n if (onError) {\n onError(error);\n }\n }\n });\n }, [page, queueOperation]);\n\n // ==========================================================================\n // SECTION 8: SCALE SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Sync scale prop to engine.setScale() method\n * Uses render queue to prevent concurrent canvas operations\n */\n useEffect(() => {\n // Guard: Engine must exist and scale must be valid\n if (!engineRef.current || !scale || typeof scale !== 'number') {\n return;\n }\n\n // Queue the scale change operation to prevent race conditions\n queueOperation(async () => {\n try {\n const result = await engineRef.current.setScale(scale);\n\n // Check if scale change was successful\n if (!result.success) {\n console.error('AnnotPdf: Failed to set scale:', result.error);\n if (onError) {\n onError(new Error(result.error));\n }\n }\n } catch (error) {\n console.error('AnnotPdf: Failed to set scale:', error);\n if (onError) {\n onError(error);\n }\n }\n });\n }, [scale, queueOperation]);\n\n // ==========================================================================\n // SECTION 9: ANNOTATIONS SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Sync annotations prop to engine.setAnnotations() method\n */\n useEffect(() => {\n // Guard: Engine must exist\n if (!engineRef.current) {\n return;\n }\n\n // Sync annotations to engine (default to empty array)\n try {\n engineRef.current.setAnnotations(annotations || []);\n } catch (error) {\n console.error('AnnotPdf: Failed to set annotations:', error);\n if (onError) {\n onError(error);\n }\n }\n }, [annotations]);\n\n // ==========================================================================\n // SECTION 10: TIMELINE SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Sync currentTime prop to engine.setTime() method\n */\n useEffect(() => {\n // Guard: Engine must exist and currentTime must be defined\n if (!engineRef.current || currentTime === undefined || currentTime === null) {\n return;\n }\n\n // Sync timeline to engine\n try {\n engineRef.current.setTime(currentTime);\n } catch (error) {\n console.error('AnnotPdf: Failed to set time:', error);\n if (onError) {\n onError(error);\n }\n }\n }, [currentTime]);\n\n // ==========================================================================\n // SECTION 10.5: STROKE CONFIG SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Sync strokeConfig prop to engine at runtime for live preview\n * Skips initial render (handled by initialization)\n */\n const isFirstRender = useRef(true);\n\n useEffect(() => {\n // Skip first render - config is passed during initialization\n if (isFirstRender.current) {\n isFirstRender.current = false;\n return;\n }\n\n // Guard: Engine must exist\n if (!engineRef.current) {\n return;\n }\n\n // Update stroke config if provided\n if (mergedStrokeConfig) {\n try {\n engineRef.current.updateStrokeConfig(mergedStrokeConfig);\n } catch (error) {\n console.error('AnnotPdf: Failed to update stroke config:', error);\n if (onError) {\n onError(error);\n }\n }\n }\n }, [mergedStrokeConfig]);\n\n // ==========================================================================\n // SECTION 11: STYLING DEFINITIONS\n // ==========================================================================\n\n /**\n * Default container styles\n * Merged with user-provided styles (user styles override defaults)\n */\n const defaultContainerStyle = {\n position: 'relative',\n display: 'inline-block',\n lineHeight: 0, // Remove extra space below canvas\n ...style // User styles override defaults\n };\n\n /**\n * Default layer container styles\n * Positions layer div absolutely over canvas\n */\n const defaultLayerStyle = {\n position: 'absolute',\n top: 0,\n left: 0,\n width: '100%',\n height: '100%',\n pointerEvents: 'none', // Allow clicks to pass through to canvas\n overflow: 'hidden'\n };\n\n /**\n * Default canvas styles\n * Merged with user-provided canvasStyle\n */\n const defaultCanvasStyle = {\n display: 'block',\n ...canvasStyle // User styles override defaults\n };\n\n // ==========================================================================\n // SECTION 12: JSX RETURN\n // ==========================================================================\n\n return (\n <div className={className} style={defaultContainerStyle}>\n <canvas ref={canvasRef} style={defaultCanvasStyle} />\n <div ref={layerContainerRef} style={defaultLayerStyle} />\n </div>\n );\n}\n\n// ============================================================================\n// SECTION 13: EXPORT\n// ============================================================================\n\nexport default AnnotPdf;\n"],"names":["AnnotPdf","pdfUrl","page","scale","annotations","currentTime","strokeConfig","onLoad","onError","onPageChange","className","style","canvasStyle","canvasRef","useRef","layerContainerRef","engineRef","renderQueue","queueOperation","useCallback","operation","error","mergedStrokeConfig","useMemo","presetName","presetConfig","getPreset","useEffect","AnnotationRenderer","cancelled","result","isFirstRender","defaultContainerStyle","defaultLayerStyle","defaultCanvasStyle","jsxs","jsx"],"mappings":"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"}
1
+ {"version":3,"file":"index12.js","sources":["../src/adapters/AnnotPdf.jsx"],"sourcesContent":["// ============================================================================\n// SECTION 1: IMPORTS\n// ============================================================================\n\nimport { useRef, useEffect, useCallback, useMemo, useState } from 'react';\nimport { AnnotationRenderer } from '../core/AnnotationRenderer.js';\nimport { getPreset } from '../pen/presets.js';\n\n// ============================================================================\n// SECTION 2: JSDOC DOCUMENTATION\n// ============================================================================\n\n/**\n * AnnotPdf - Declarative React component for PDF annotation rendering\n *\n * A React wrapper around the AnnotationRenderer core engine that provides\n * a declarative, props-based API for rendering PDF documents with\n * timeline-synchronized annotations.\n *\n * Features:\n * - Automatic lifecycle management (initialization and cleanup)\n * - Declarative prop-to-method synchronization\n * - PDF rendering with pdf.js\n * - Timeline-synchronized annotation display\n * - Support for highlight, text, and ink annotations\n * - Page navigation and zoom control\n *\n * @component\n * @example\n * // Basic usage\n * <AnnotPdf\n * pdfUrl=\"/document.pdf\"\n * page={1}\n * scale={1.5}\n * annotations={[]}\n * currentTime={0}\n * />\n *\n * @example\n * // With audio synchronization (discrete mode - ~4fps)\n * const [currentTime, setCurrentTime] = useState(0);\n *\n * <div>\n * <AnnotPdf\n * pdfUrl=\"/lecture.pdf\"\n * page={1}\n * scale={1.5}\n * annotations={annotations}\n * currentTime={currentTime}\n * onLoad={({ pageCount }) => console.log('Loaded:', pageCount)}\n * />\n * <audio\n * src=\"/lecture.mp3\"\n * onTimeUpdate={(e) => setCurrentTime(e.target.currentTime)}\n * controls\n * />\n * </div>\n *\n * @example\n * // With audio synchronization (continuous mode - 60fps, smoother)\n * const audioRef = useRef(null);\n *\n * <div>\n * <AnnotPdf\n * pdfUrl=\"/lecture.pdf\"\n * page={1}\n * scale={1.5}\n * annotations={annotations}\n * audioRef={audioRef}\n * onLoad={({ pageCount }) => console.log('Loaded:', pageCount)}\n * />\n * <audio ref={audioRef} src=\"/lecture.mp3\" controls />\n * </div>\n *\n * @param {Object} props - Component props\n * @param {string} props.pdfUrl - PDF document URL (required)\n * @param {number} [props.page=1] - Current page number (1-indexed)\n * @param {number} [props.scale=1.5] - Zoom scale factor\n * @param {Array} [props.annotations=[]] - Array of annotation objects\n * @param {number} [props.currentTime=0] - Timeline position in seconds (discrete mode)\n * @param {React.RefObject<HTMLAudioElement>} [props.audioRef] - Audio element ref for continuous sync (60fps)\n * @param {Function} [props.onLoad] - Callback when PDF loads: ({pageCount}) => void\n * @param {Function} [props.onError] - Callback on error: (error) => void\n * @param {Function} [props.onPageChange] - Callback when page changes: (page) => void\n * @param {Object} [props.strokeConfig] - Stroke rendering configuration\n * @param {string} [props.strokeConfig.preset] - Preset name: 'default', 'blue', 'minimal'\n * @param {Object} [props.strokeConfig.highlight] - Highlight style overrides\n * @param {number} [props.strokeConfig.highlight.width] - Highlight stroke width (16-32)\n * @param {Object} [props.strokeConfig.text] - Text style overrides\n * @param {number} [props.strokeConfig.text.width] - Text stroke width (1-4)\n * @param {string} [props.className] - CSS class for container div\n * @param {Object} [props.style] - Inline styles for container div\n * @param {Object} [props.canvasStyle] - Inline styles for canvas element\n * @returns {JSX.Element} PDF viewer component with annotation layers\n */\n\n// ============================================================================\n// SECTION 3: COMPONENT DEFINITION\n// ============================================================================\n\nfunction AnnotPdf({\n // Required props\n pdfUrl,\n\n // Optional props with defaults\n page = 1,\n scale = 1.5,\n annotations = [],\n currentTime = 0,\n\n // Continuous sync mode (use instead of currentTime for smoother animation)\n audioRef,\n\n // Stroke configuration\n strokeConfig,\n\n // Callbacks\n onLoad,\n onError,\n onPageChange,\n\n // Styling\n className,\n style,\n canvasStyle\n}) {\n\n // ==========================================================================\n // SECTION 4: REFS INITIALIZATION\n // ==========================================================================\n\n /**\n * Reference to the canvas element for PDF rendering\n * @type {React.RefObject<HTMLCanvasElement>}\n */\n const canvasRef = useRef(null);\n\n /**\n * Reference to the layer container div for annotation layers\n * @type {React.RefObject<HTMLDivElement>}\n */\n const layerContainerRef = useRef(null);\n\n /**\n * Reference to the AnnotationRenderer engine instance\n * Stored in ref to avoid triggering re-renders\n * @type {React.RefObject<AnnotationRenderer|null>}\n */\n const engineRef = useRef(null);\n\n /**\n * Reference to the render operation queue\n * Ensures sequential execution of async canvas operations\n * Prevents PDF.js race condition: \"Cannot use the same canvas during multiple render() operations\"\n * @type {React.RefObject<Promise<void>>}\n */\n const renderQueue = useRef(Promise.resolve());\n\n /**\n * State to track engine initialization\n * Used to trigger dependent effects after engine is ready\n */\n const [isEngineReady, setEngineReady] = useState(false);\n\n // ==========================================================================\n // SECTION 4.5: RENDER QUEUE HELPER\n // ==========================================================================\n\n /**\n * Queue a render operation to execute sequentially\n *\n * This helper ensures that async canvas operations (loadPDF, setPage, setScale)\n * execute one at a time, preventing concurrent access to the PDF.js canvas.\n * Uses Promise chaining to maintain operation order.\n *\n * @param {Function} operation - Async function returning a Promise\n * @returns {void}\n */\n const queueOperation = useCallback((operation) => {\n renderQueue.current = renderQueue.current\n .then(operation)\n .catch(error => {\n // Log errors but don't break the queue\n console.error('AnnotPdf: Queued operation failed:', error);\n });\n }, []);\n\n // ==========================================================================\n // SECTION 4.6: STROKE CONFIG BUILDER\n // ==========================================================================\n\n /**\n * Build merged stroke config from preset and overrides\n *\n * If strokeConfig.preset is provided, loads base config from presets.\n * Then merges any override values (highlight.width, text.width).\n */\n const mergedStrokeConfig = useMemo(() => {\n if (!strokeConfig) {\n return undefined;\n }\n\n // Start with preset or empty object\n const presetName = strokeConfig.preset || 'default';\n const presetConfig = getPreset(presetName);\n\n // Build config object matching StrokeRenderer format\n const config = {\n highlight: {\n color: presetConfig.highlight.color,\n width: strokeConfig.highlight?.width ?? presetConfig.highlight.width\n },\n text: {\n color: presetConfig.handText.color,\n width: strokeConfig.text?.width ?? presetConfig.handText.width\n }\n };\n\n return config;\n }, [strokeConfig]);\n\n // ==========================================================================\n // SECTION 5: ENGINE INITIALIZATION AND CLEANUP\n // ==========================================================================\n\n /**\n * Initialize AnnotationRenderer on component mount\n * Cleanup on component unmount\n */\n useEffect(() => {\n // Guard: Wait for DOM elements to be ready\n if (!canvasRef.current || !layerContainerRef.current) {\n return;\n }\n\n // Initialize engine with stroke config if provided\n try {\n engineRef.current = new AnnotationRenderer({\n canvasElement: canvasRef.current,\n container: layerContainerRef.current,\n strokeConfig: mergedStrokeConfig\n });\n // Signal that engine is ready for dependent effects\n setEngineReady(true);\n } catch (error) {\n console.error('AnnotPdf: Failed to initialize renderer:', error);\n if (onError) {\n onError(error);\n }\n }\n\n // Cleanup on unmount\n return () => {\n setEngineReady(false);\n if (engineRef.current) {\n engineRef.current.destroy();\n engineRef.current = null;\n }\n };\n }, []); // Empty deps - run once on mount\n\n // ==========================================================================\n // SECTION 6: PDF LOADING SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Load PDF document when pdfUrl prop changes\n * Handles async operation with cancellation support\n * Uses render queue to prevent concurrent canvas operations\n */\n useEffect(() => {\n // Guard: Wait for engine and valid pdfUrl\n if (!isEngineReady || !pdfUrl) {\n return;\n }\n\n let cancelled = false;\n\n const loadPdf = async () => {\n // Check engine exists at execution time (not queue time)\n if (!engineRef.current) {\n return;\n }\n\n try {\n const result = await engineRef.current.loadPDF(pdfUrl);\n\n // Check if component unmounted during async operation\n if (cancelled) return;\n\n // Check if load was successful\n if (!result.success) {\n console.error('AnnotPdf: Failed to load PDF:', result.error);\n if (onError) {\n onError(new Error(result.error));\n }\n return;\n }\n\n // Call onLoad callback with pageCount from result\n if (onLoad) {\n onLoad({ pageCount: result.pageCount });\n }\n } catch (error) {\n if (cancelled) return;\n\n console.error('AnnotPdf: Failed to load PDF:', error);\n if (onError) {\n onError(error);\n }\n }\n };\n\n // Queue the PDF loading operation to prevent race conditions\n queueOperation(loadPdf);\n\n // Cleanup: Prevent state updates if component unmounts during load\n return () => {\n cancelled = true;\n };\n }, [pdfUrl, isEngineReady, queueOperation]);\n\n // ==========================================================================\n // SECTION 7: PAGE SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Sync page prop to engine.setPage() method\n * Uses render queue to prevent concurrent canvas operations\n */\n useEffect(() => {\n // Guard: Wait for engine and valid page\n if (!isEngineReady || !page || typeof page !== 'number') {\n return;\n }\n\n // Queue the page change operation to prevent race conditions\n queueOperation(async () => {\n // Check engine exists at execution time (not queue time)\n if (!engineRef.current) {\n return;\n }\n\n try {\n const result = await engineRef.current.setPage(page);\n\n // Check if page change was successful\n if (!result.success) {\n console.error('AnnotPdf: Failed to set page:', result.error);\n if (onError) {\n onError(new Error(result.error));\n }\n return;\n }\n\n // Optional: Notify parent of successful page change\n if (onPageChange) {\n onPageChange(page);\n }\n } catch (error) {\n console.error('AnnotPdf: Failed to set page:', error);\n if (onError) {\n onError(error);\n }\n }\n });\n }, [page, isEngineReady, queueOperation]);\n\n // ==========================================================================\n // SECTION 8: SCALE SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Sync scale prop to engine.setScale() method\n * Uses render queue to prevent concurrent canvas operations\n */\n useEffect(() => {\n // Guard: Wait for engine and valid scale\n if (!isEngineReady || !scale || typeof scale !== 'number') {\n return;\n }\n\n // Queue the scale change operation to prevent race conditions\n queueOperation(async () => {\n // Check engine exists at execution time (not queue time)\n if (!engineRef.current) {\n return;\n }\n\n try {\n const result = await engineRef.current.setScale(scale);\n\n // Check if scale change was successful\n if (!result.success) {\n console.error('AnnotPdf: Failed to set scale:', result.error);\n if (onError) {\n onError(new Error(result.error));\n }\n }\n } catch (error) {\n console.error('AnnotPdf: Failed to set scale:', error);\n if (onError) {\n onError(error);\n }\n }\n });\n }, [scale, isEngineReady, queueOperation]);\n\n // ==========================================================================\n // SECTION 9: ANNOTATIONS SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Sync annotations prop to engine.setAnnotations() method\n */\n useEffect(() => {\n // Guard: Wait for engine\n if (!isEngineReady || !engineRef.current) {\n return;\n }\n\n // Sync annotations to engine (default to empty array)\n try {\n engineRef.current.setAnnotations(annotations || []);\n } catch (error) {\n console.error('AnnotPdf: Failed to set annotations:', error);\n if (onError) {\n onError(error);\n }\n }\n }, [annotations, isEngineReady]);\n\n // ==========================================================================\n // SECTION 10: TIMELINE SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Sync currentTime prop to engine.setTime() method\n * Only used when audioRef is NOT provided (discrete mode)\n */\n useEffect(() => {\n // Skip if using continuous sync mode\n if (audioRef) {\n return;\n }\n\n // Guard: Wait for engine and valid currentTime\n if (!isEngineReady || !engineRef.current || currentTime === undefined || currentTime === null) {\n return;\n }\n\n // Sync timeline to engine\n try {\n engineRef.current.setTime(currentTime);\n } catch (error) {\n console.error('AnnotPdf: Failed to set time:', error);\n if (onError) {\n onError(error);\n }\n }\n }, [currentTime, audioRef, isEngineReady]);\n\n // ==========================================================================\n // SECTION 10.1: CONTINUOUS SYNC MODE (audioRef)\n // ==========================================================================\n\n /**\n * Manage continuous sync mode when audioRef is provided\n *\n * Uses requestAnimationFrame for 60fps smooth animation instead of\n * discrete currentTime updates (~4fps from audio timeupdate events).\n */\n useEffect(() => {\n // Guard: Skip if not using continuous sync mode\n if (!audioRef?.current) {\n return;\n }\n\n // Guard: Wait for engine\n if (!isEngineReady || !engineRef.current) {\n return;\n }\n\n const audio = audioRef.current;\n /**\n * Start continuous sync when audio plays\n */\n const handlePlay = () => {\n if (!engineRef.current) return;\n engineRef.current.startContinuousSync(() => audio.currentTime);\n };\n\n /**\n * Stop continuous sync when audio pauses\n */\n const handlePause = () => {\n if (!engineRef.current) return;\n engineRef.current.stopContinuousSync();\n engineRef.current.setTime(audio.currentTime);\n };\n\n /**\n * Handle seeking - update time immediately\n */\n const handleSeeked = () => {\n if (!engineRef.current) return;\n engineRef.current.setTime(audio.currentTime);\n };\n\n // Add event listeners\n audio.addEventListener('play', handlePlay);\n audio.addEventListener('pause', handlePause);\n audio.addEventListener('ended', handlePause);\n audio.addEventListener('seeked', handleSeeked);\n\n // If audio is already playing, start sync immediately\n if (!audio.paused) {\n handlePlay();\n }\n\n // Cleanup on unmount or audioRef change\n return () => {\n audio.removeEventListener('play', handlePlay);\n audio.removeEventListener('pause', handlePause);\n audio.removeEventListener('ended', handlePause);\n audio.removeEventListener('seeked', handleSeeked);\n // Guard: engine may have been destroyed by initialization effect cleanup\n if (engineRef.current) {\n engineRef.current.stopContinuousSync();\n }\n };\n }, [audioRef, isEngineReady]);\n\n // ==========================================================================\n // SECTION 10.5: STROKE CONFIG SYNCHRONIZATION\n // ==========================================================================\n\n /**\n * Sync strokeConfig prop to engine at runtime for live preview\n * Skips initial render (handled by initialization)\n */\n const isFirstRender = useRef(true);\n\n useEffect(() => {\n // Skip first render - config is passed during initialization\n if (isFirstRender.current) {\n isFirstRender.current = false;\n return;\n }\n\n // Guard: Engine must exist\n if (!engineRef.current) {\n return;\n }\n\n // Update stroke config if provided\n if (mergedStrokeConfig) {\n try {\n engineRef.current.updateStrokeConfig(mergedStrokeConfig);\n } catch (error) {\n console.error('AnnotPdf: Failed to update stroke config:', error);\n if (onError) {\n onError(error);\n }\n }\n }\n }, [mergedStrokeConfig]);\n\n // ==========================================================================\n // SECTION 11: STYLING DEFINITIONS\n // ==========================================================================\n\n /**\n * Default container styles\n * Merged with user-provided styles (user styles override defaults)\n */\n const defaultContainerStyle = {\n position: 'relative',\n display: 'inline-block',\n lineHeight: 0, // Remove extra space below canvas\n ...style // User styles override defaults\n };\n\n /**\n * Default layer container styles\n * Positions layer div absolutely over canvas\n */\n const defaultLayerStyle = {\n position: 'absolute',\n top: 0,\n left: 0,\n width: '100%',\n height: '100%',\n pointerEvents: 'none', // Allow clicks to pass through to canvas\n overflow: 'hidden'\n };\n\n /**\n * Default canvas styles\n * Merged with user-provided canvasStyle\n */\n const defaultCanvasStyle = {\n display: 'block',\n ...canvasStyle // User styles override defaults\n };\n\n // ==========================================================================\n // SECTION 12: JSX RETURN\n // ==========================================================================\n\n return (\n <div className={className} style={defaultContainerStyle}>\n <canvas ref={canvasRef} style={defaultCanvasStyle} />\n <div ref={layerContainerRef} style={defaultLayerStyle} />\n </div>\n );\n}\n\n// ============================================================================\n// SECTION 13: EXPORT\n// ============================================================================\n\nexport default AnnotPdf;\n"],"names":["AnnotPdf","pdfUrl","page","scale","annotations","currentTime","audioRef","strokeConfig","onLoad","onError","onPageChange","className","style","canvasStyle","canvasRef","useRef","layerContainerRef","engineRef","renderQueue","isEngineReady","setEngineReady","useState","queueOperation","useCallback","operation","error","mergedStrokeConfig","useMemo","presetName","presetConfig","getPreset","useEffect","AnnotationRenderer","cancelled","result","audio","handlePlay","handlePause","handleSeeked","isFirstRender","defaultContainerStyle","defaultLayerStyle","defaultCanvasStyle","jsxs","jsx"],"mappings":"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"}
package/dist/index14.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const z=require("./index6.cjs"),p=require("./index23.cjs");function T(e){return p.default.glyphs&&p.default.glyphs[e]?p.default.glyphs[e]:e===" "?{strokes:[],width:.3}:null}function q(e,t,i,o,c){return e.map(([n,l])=>[t+n*o*c,i+l*o])}function D(e,t){const{id:i,start:o,end:c,content:n,x:l,y:m,fontSize:C}=e;if(!n||n.length===0)return[];const d=[],P=c-o,a=(C||t.fontSize||16)/1e3;let f=l;const b=n.length,g=P/b;for(let r=0;r<n.length;r++){const j=n[r],h=T(j);if(!h){f+=a*.5;continue}const S=h.width||.8,k=h.strokes||[],$=o+r*g,I=$+g;for(let s=0;s<k.length;s++){const w=k[s].points||[];if(w.length<2)continue;let u=q(w,f,m,a,S);t.jitter?.amplitude>0&&(u=z.applyJitter(u,t.jitter,`${i}-${r}-${s}`));let x=null;(t.pressure?.taperIn>0||t.pressure?.taperOut>0)&&(x=z.applyPressure(u,t.pressure)),d.push({id:`${i}-${r}-${s}`,points:u,start:$,end:I,color:t.color||"rgba(220, 20, 60, 1.0)",width:t.width||2,lineCap:t.lineCap||"round",pressures:x})}f+=a*S+a*.1}return d}exports.default=D;exports.textToStrokes=D;
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const M=require("./index6.cjs"),P=require("./index23.cjs");function X(t,e=2){if(t.length<2||e<1)return t;const n=[];for(let r=0;r<t.length-1;r++){const[a,o]=t[r],[g,S]=t[r+1];n.push([a,o]);for(let s=1;s<=e;s++){const u=s/(e+1);n.push([a+(g-a)*u,o+(S-o)*u])}}return n.push(t[t.length-1]),n}function _(t){return P.default.glyphs&&P.default.glyphs[t]?P.default.glyphs[t]:t===" "?{paths:[],width:.35}:null}function F(t,e,n){return t.map(([r,a])=>[(e+r)*n,a*n])}function z(t,e){const{id:n,start:r,end:a,content:o,x:g,y:S,fontSize:s}=t;if(!o||o.length===0)return[];const u=[],q=a-r,v=s||e.fontSize||16,k=e.subdivisions??2,I=v/1e3;let h=0;const O=o.length,x=q/O;for(let c=0;c<o.length;c++){const $=o[c],l=_($);if(!l){h+=.5+.18;continue}const m=l.width||.5,f=l.paths||[],j=l.pathTiming||"sequential";if(f.length===0){h+=m+.18;continue}const b=r+c*x,y=b+x,E=f.length;for(let i=0;i<f.length;i++){let p=f[i].points||[];if(p.length<2)continue;k>0&&(p=X(p,k));let w=F(p,h,I),C=null;(e.pressure?.taperIn>0||e.pressure?.taperOut>0)&&(C=M.applyPressure(w,e.pressure));let d,D;if(j==="parallel")d=b,D=y;else{const T=x/E;d=b+i*T,D=d+T}u.push({id:`${n}-${c}-${i}`,points:w,baseX:g,baseY:S,start:d,end:D,color:e.color||"rgba(220, 20, 60, 1.0)",width:e.width||2,lineCap:e.lineCap||"round",pressures:C,uniformScale:!0})}h+=m+.18}return u}exports.default=z;exports.textToStrokes=z;
2
2
  //# sourceMappingURL=index14.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index14.cjs","sources":["../src/converters/text.js"],"sourcesContent":["/**\n * Text Converter - Converts text annotations to stroke commands\n *\n * Transforms text content into stroke paths for each character,\n * looking up glyphs from the stroke library.\n *\n * @module converters/text\n */\n\nimport { applyJitter, applyPressure } from '../pen/effects.js';\nimport latinStrokes from '../strokes/latin.json';\n\n/**\n * Looks up character strokes from the stroke library\n *\n * @param {string} char - Single character to look up\n * @returns {Object|null} Character data with strokes and width, or null if not found\n */\nfunction lookupCharacter(char) {\n // Check Latin library\n if (latinStrokes.glyphs && latinStrokes.glyphs[char]) {\n return latinStrokes.glyphs[char];\n }\n\n // Space character - return empty with width\n if (char === ' ') {\n return { strokes: [], width: 0.3 };\n }\n\n // Fallback: return null (character will be skipped)\n return null;\n}\n\n/**\n * Transforms stroke points from glyph space to target position\n *\n * Glyph points are in 0-1 normalized space within the glyph's bounding box.\n * This transforms them to the target position on the page.\n *\n * @param {Array<[number, number]>} points - Glyph points in 0-1 space\n * @param {number} x - Target X position (normalized page coordinates)\n * @param {number} y - Target Y position (normalized page coordinates)\n * @param {number} scale - Scale factor based on fontSize\n * @param {number} charWidth - Character width for positioning\n * @returns {Array<[number, number]>} Transformed points\n */\nfunction transformPoints(points, x, y, scale, charWidth) {\n return points.map(([px, py]) => [\n x + px * scale * charWidth,\n y + py * scale\n ]);\n}\n\n/**\n * Converts a text annotation to stroke commands\n *\n * Each character in the content becomes one or more strokes.\n * Characters are staggered in timing to create a writing effect.\n *\n * @param {Object} annotation - Text annotation object\n * @param {string} annotation.id - Unique identifier\n * @param {number} annotation.start - Start time in seconds\n * @param {number} annotation.end - End time in seconds\n * @param {string} annotation.content - Text content to render\n * @param {number} annotation.x - X position (normalized 0-1)\n * @param {number} annotation.y - Y position (normalized 0-1)\n * @param {number} [annotation.fontSize=16] - Font size in pixels\n * @param {Object} [annotation.style] - Optional style overrides\n * @param {Object} style - Resolved style configuration\n * @param {string} style.color - Stroke color\n * @param {number} style.width - Stroke width in pixels\n * @param {number} [style.fontSize=16] - Default font size\n * @param {string} [style.lineCap='round'] - Line cap style\n * @param {Object} [style.jitter] - Jitter configuration\n * @param {Object} [style.pressure] - Pressure configuration\n * @returns {Array<Object>} Array of stroke commands\n */\nexport function textToStrokes(annotation, style) {\n const {\n id,\n start,\n end,\n content,\n x,\n y,\n fontSize: annotFontSize\n } = annotation;\n\n if (!content || content.length === 0) {\n return [];\n }\n\n const strokes = [];\n const totalDuration = end - start;\n const fontSize = annotFontSize || style.fontSize || 16;\n\n // Scale factor: convert fontSize (pixels) to normalized coordinates\n // Assuming a reference viewport of ~1000px, adjust as needed\n const scale = fontSize / 1000;\n\n // Track current X position for character placement\n let currentX = x;\n\n // Calculate timing per character\n const charCount = content.length;\n const charDuration = totalDuration / charCount;\n\n for (let charIndex = 0; charIndex < content.length; charIndex++) {\n const char = content[charIndex];\n const charData = lookupCharacter(char);\n\n if (!charData) {\n // Skip unknown characters, but advance position\n currentX += scale * 0.5;\n continue;\n }\n\n const charWidth = charData.width || 0.8;\n const charStrokes = charData.strokes || [];\n\n // Calculate timing for this character\n const charStart = start + charIndex * charDuration;\n const charEnd = charStart + charDuration;\n\n // Process each stroke in the character\n for (let strokeIndex = 0; strokeIndex < charStrokes.length; strokeIndex++) {\n const strokeData = charStrokes[strokeIndex];\n const rawPoints = strokeData.points || [];\n\n if (rawPoints.length < 2) continue;\n\n // Transform points to target position\n let points = transformPoints(rawPoints, currentX, y, scale, charWidth);\n\n // Apply jitter if configured\n if (style.jitter?.amplitude > 0) {\n points = applyJitter(points, style.jitter, `${id}-${charIndex}-${strokeIndex}`);\n }\n\n // Calculate pressure values if configured\n let pressures = null;\n if (style.pressure?.taperIn > 0 || style.pressure?.taperOut > 0) {\n pressures = applyPressure(points, style.pressure);\n }\n\n strokes.push({\n id: `${id}-${charIndex}-${strokeIndex}`,\n points,\n start: charStart,\n end: charEnd,\n color: style.color || 'rgba(220, 20, 60, 1.0)',\n width: style.width || 2,\n lineCap: style.lineCap || 'round',\n pressures\n });\n }\n\n // Advance X position for next character\n currentX += scale * charWidth + scale * 0.1; // Add small spacing\n }\n\n return strokes;\n}\n\nexport default textToStrokes;\n"],"names":["lookupCharacter","char","latinStrokes","transformPoints","points","x","y","scale","charWidth","px","py","textToStrokes","annotation","style","id","start","end","content","annotFontSize","strokes","totalDuration","currentX","charCount","charDuration","charIndex","charData","charStrokes","charStart","charEnd","strokeIndex","rawPoints","applyJitter","pressures","applyPressure"],"mappings":"uKAkBA,SAASA,EAAgBC,EAAM,CAE7B,OAAIC,EAAAA,QAAa,QAAUA,EAAAA,QAAa,OAAOD,CAAI,EAC1CC,EAAAA,QAAa,OAAOD,CAAI,EAI7BA,IAAS,IACJ,CAAE,QAAS,GAAI,MAAO,EAAG,EAI3B,IACT,CAeA,SAASE,EAAgBC,EAAQC,EAAGC,EAAGC,EAAOC,EAAW,CACvD,OAAOJ,EAAO,IAAI,CAAC,CAACK,EAAIC,CAAE,IAAM,CAC9BL,EAAII,EAAKF,EAAQC,EACjBF,EAAII,EAAKH,CACb,CAAG,CACH,CA0BO,SAASI,EAAcC,EAAYC,EAAO,CAC/C,KAAM,CACJ,GAAAC,EACA,MAAAC,EACA,IAAAC,EACA,QAAAC,EACA,EAAAZ,EACA,EAAAC,EACA,SAAUY,CACd,EAAMN,EAEJ,GAAI,CAACK,GAAWA,EAAQ,SAAW,EACjC,MAAO,CAAA,EAGT,MAAME,EAAU,CAAA,EACVC,EAAgBJ,EAAMD,EAKtBR,GAJWW,GAAiBL,EAAM,UAAY,IAI3B,IAGzB,IAAIQ,EAAWhB,EAGf,MAAMiB,EAAYL,EAAQ,OACpBM,EAAeH,EAAgBE,EAErC,QAASE,EAAY,EAAGA,EAAYP,EAAQ,OAAQO,IAAa,CAC/D,MAAMvB,EAAOgB,EAAQO,CAAS,EACxBC,EAAWzB,EAAgBC,CAAI,EAErC,GAAI,CAACwB,EAAU,CAEbJ,GAAYd,EAAQ,GACpB,QACF,CAEA,MAAMC,EAAYiB,EAAS,OAAS,GAC9BC,EAAcD,EAAS,SAAW,CAAA,EAGlCE,EAAYZ,EAAQS,EAAYD,EAChCK,EAAUD,EAAYJ,EAG5B,QAASM,EAAc,EAAGA,EAAcH,EAAY,OAAQG,IAAe,CAEzE,MAAMC,EADaJ,EAAYG,CAAW,EACb,QAAU,CAAA,EAEvC,GAAIC,EAAU,OAAS,EAAG,SAG1B,IAAI1B,EAASD,EAAgB2B,EAAWT,EAAUf,EAAGC,EAAOC,CAAS,EAGjEK,EAAM,QAAQ,UAAY,IAC5BT,EAAS2B,EAAAA,YAAY3B,EAAQS,EAAM,OAAQ,GAAGC,CAAE,IAAIU,CAAS,IAAIK,CAAW,EAAE,GAIhF,IAAIG,EAAY,MACZnB,EAAM,UAAU,QAAU,GAAKA,EAAM,UAAU,SAAW,KAC5DmB,EAAYC,EAAAA,cAAc7B,EAAQS,EAAM,QAAQ,GAGlDM,EAAQ,KAAK,CACX,GAAI,GAAGL,CAAE,IAAIU,CAAS,IAAIK,CAAW,GACrC,OAAAzB,EACA,MAAOuB,EACP,IAAKC,EACL,MAAOf,EAAM,OAAS,yBACtB,MAAOA,EAAM,OAAS,EACtB,QAASA,EAAM,SAAW,QAC1B,UAAAmB,CACR,CAAO,CACH,CAGAX,GAAYd,EAAQC,EAAYD,EAAQ,EAC1C,CAEA,OAAOY,CACT"}
1
+ {"version":3,"file":"index14.cjs","sources":["../src/converters/text.js"],"sourcesContent":["/**\n * Text Converter - Converts text annotations to stroke commands\n *\n * Transforms text content into stroke paths for each character,\n * using single-stroke font glyphs for smooth writing animation.\n *\n * @module converters/text\n */\n\nimport { applyPressure } from \"../pen/effects.js\";\nimport singleStrokeFont from \"../strokes/single-stroke.json\";\n\n/**\n * Subdivide a path by adding intermediate points between existing points.\n * This creates smoother interpolation during progressive rendering.\n *\n * @param {Array<[number, number]>} points - Original path points\n * @param {number} subdivisions - Number of points to add between each pair (default: 2)\n * @returns {Array<[number, number]>} Subdivided path with more points\n */\nfunction subdividePath(points, subdivisions = 2) {\n if (points.length < 2 || subdivisions < 1) {\n return points;\n }\n\n const result = [];\n\n for (let i = 0; i < points.length - 1; i++) {\n const [x1, y1] = points[i];\n const [x2, y2] = points[i + 1];\n\n // Add the start point\n result.push([x1, y1]);\n\n // Add intermediate points\n for (let j = 1; j <= subdivisions; j++) {\n const t = j / (subdivisions + 1);\n result.push([x1 + (x2 - x1) * t, y1 + (y2 - y1) * t]);\n }\n }\n\n // Add the final point\n result.push(points[points.length - 1]);\n\n return result;\n}\n\n/**\n * Looks up character glyph from the single-stroke font library\n *\n * @param {string} char - Single character to look up\n * @returns {Object|null} Character data with paths and width, or null if not found\n */\nfunction lookupCharacter(char) {\n // Check single-stroke font library\n if (singleStrokeFont.glyphs && singleStrokeFont.glyphs[char]) {\n return singleStrokeFont.glyphs[char];\n }\n\n // Space character - return empty with width\n if (char === \" \") {\n return { paths: [], width: 0.35 };\n }\n\n // Fallback: return null (character will be skipped)\n return null;\n}\n\n/**\n * Scales glyph points by fontSize scale factor\n *\n * Glyph points are in normalized space where:\n * - X is 0 to charWidth (already scaled within the glyph)\n * - Y is 0 to ~1.0 (baseline at 0.8, cap height at 0)\n *\n * This scales them to the target size, but does NOT add the base position.\n * The base position is stored separately to allow correct rendering with\n * mixed coordinate systems (position in page coords, offsets in uniform space).\n *\n * @param {Array<[number, number]>} points - Glyph points in normalized space\n * @param {number} offsetX - X offset from base position (character offset within text)\n * @param {number} scale - Scale factor based on fontSize\n * @returns {Array<[number, number]>} Scaled offset points (not absolute positions)\n */\nfunction scaleGlyphPoints(points, offsetX, scale) {\n // Scale points and add character offset (in uniform space)\n return points.map(([px, py]) => [(offsetX + px) * scale, py * scale]);\n}\n\n/**\n * Converts a text annotation to stroke commands\n *\n * Each character in the content becomes one or more strokes.\n * Characters are staggered in timing to create a writing effect.\n * Multi-path characters (like 'i', 'j', '!') use pathTiming to\n * control whether paths are drawn sequentially or in parallel.\n *\n * @param {Object} annotation - Text annotation object\n * @param {string} annotation.id - Unique identifier\n * @param {number} annotation.start - Start time in seconds\n * @param {number} annotation.end - End time in seconds\n * @param {string} annotation.content - Text content to render\n * @param {number} annotation.x - X position (normalized 0-1)\n * @param {number} annotation.y - Y position (normalized 0-1)\n * @param {number} [annotation.fontSize=16] - Font size in pixels\n * @param {Object} [annotation.style] - Optional style overrides\n * @param {Object} style - Resolved style configuration\n * @param {string} style.color - Stroke color\n * @param {number} style.width - Stroke width in pixels\n * @param {number} [style.fontSize=16] - Default font size\n * @param {string} [style.lineCap='round'] - Line cap style\n * @param {Object} [style.jitter] - Jitter configuration\n * @param {Object} [style.pressure] - Pressure configuration\n * @param {number} [style.subdivisions=2] - Path subdivision level\n * @returns {Array<Object>} Array of stroke commands\n */\nexport function textToStrokes(annotation, style) {\n const { id, start, end, content, x, y, fontSize: annotFontSize } = annotation;\n\n if (!content || content.length === 0) {\n return [];\n }\n\n const strokes = [];\n const totalDuration = end - start;\n const fontSize = annotFontSize || style.fontSize || 16;\n const subdivisions = style.subdivisions ?? 2;\n\n // Scale factor: convert fontSize (pixels) to normalized coordinates\n // Assuming a reference viewport of ~1000px\n const scale = fontSize / 1000;\n\n // Track character offset from base position (in glyph units, not page coords)\n let charOffsetX = 0;\n\n // Calculate timing per character\n const charCount = content.length;\n const charDuration = totalDuration / charCount;\n\n for (let charIndex = 0; charIndex < content.length; charIndex++) {\n const char = content[charIndex];\n const charData = lookupCharacter(char);\n\n if (!charData) {\n // Skip unknown characters, but advance position with default width\n charOffsetX += 0.5 + 0.18; // default width + spacing (in glyph units)\n continue;\n }\n\n const charWidth = charData.width || 0.5;\n const charPaths = charData.paths || [];\n const pathTiming = charData.pathTiming || \"sequential\";\n\n // Skip if no paths (e.g., space character)\n if (charPaths.length === 0) {\n charOffsetX += charWidth + 0.18;\n continue;\n }\n\n // Calculate timing for this character's paths\n const charStart = start + charIndex * charDuration;\n const charEnd = charStart + charDuration;\n\n // Determine path timing based on pathTiming mode\n const pathCount = charPaths.length;\n\n for (let pathIndex = 0; pathIndex < charPaths.length; pathIndex++) {\n const pathData = charPaths[pathIndex];\n let rawPoints = pathData.points || [];\n\n if (rawPoints.length < 2) continue;\n\n // Apply path subdivision for smoother interpolation\n if (subdivisions > 0) {\n rawPoints = subdividePath(rawPoints, subdivisions);\n }\n\n // Scale glyph points (offset coordinates, not absolute)\n let points = scaleGlyphPoints(rawPoints, charOffsetX, scale);\n\n // Calculate pressure values if configured\n let pressures = null;\n if (style.pressure?.taperIn > 0 || style.pressure?.taperOut > 0) {\n pressures = applyPressure(points, style.pressure);\n }\n\n // Calculate path timing based on pathTiming mode\n let pathStart, pathEnd;\n\n if (pathTiming === \"parallel\") {\n // All paths drawn simultaneously during character duration\n pathStart = charStart;\n pathEnd = charEnd;\n } else {\n // Sequential: each path gets a portion of character duration\n const pathDuration = charDuration / pathCount;\n pathStart = charStart + pathIndex * pathDuration;\n pathEnd = pathStart + pathDuration;\n }\n\n strokes.push({\n id: `${id}-${charIndex}-${pathIndex}`,\n points, // Offset coordinates from baseX/baseY (uniform space)\n baseX: x, // Base X position (width-normalized page coords)\n baseY: y, // Base Y position (height-normalized page coords)\n start: pathStart,\n end: pathEnd,\n color: style.color || \"rgba(220, 20, 60, 1.0)\",\n width: style.width || 2,\n lineCap: style.lineCap || \"round\",\n pressures,\n uniformScale: true, // Preserve aspect ratio when rendering\n });\n }\n\n // Advance offset for next character (in glyph units)\n charOffsetX += charWidth + 0.18; // Add small spacing\n }\n\n return strokes;\n}\n\nexport default textToStrokes;\n"],"names":["subdividePath","points","subdivisions","result","i","x1","y1","x2","y2","j","t","lookupCharacter","char","singleStrokeFont","scaleGlyphPoints","offsetX","scale","px","py","textToStrokes","annotation","style","id","start","end","content","x","y","annotFontSize","strokes","totalDuration","fontSize","charOffsetX","charCount","charDuration","charIndex","charData","charWidth","charPaths","pathTiming","charStart","charEnd","pathCount","pathIndex","rawPoints","pressures","applyPressure","pathStart","pathEnd","pathDuration"],"mappings":"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"}