@embedpdf/plugin-selection 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/index.cjs +1 -1
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.js +233 -45
  4. package/dist/index.js.map +1 -1
  5. package/dist/lib/handlers/index.d.ts +2 -0
  6. package/dist/lib/handlers/marquee-selection.handler.d.ts +28 -0
  7. package/dist/lib/handlers/text-selection.handler.d.ts +27 -0
  8. package/dist/lib/selection-plugin.d.ts +23 -2
  9. package/dist/lib/types.d.ts +54 -1
  10. package/dist/preact/index.cjs +1 -1
  11. package/dist/preact/index.cjs.map +1 -1
  12. package/dist/preact/index.js +45 -0
  13. package/dist/preact/index.js.map +1 -1
  14. package/dist/react/index.cjs +1 -1
  15. package/dist/react/index.cjs.map +1 -1
  16. package/dist/react/index.js +45 -0
  17. package/dist/react/index.js.map +1 -1
  18. package/dist/shared/components/index.d.ts +1 -0
  19. package/dist/shared/components/marquee-selection.d.ts +23 -0
  20. package/dist/shared-preact/components/index.d.ts +1 -0
  21. package/dist/shared-preact/components/marquee-selection.d.ts +23 -0
  22. package/dist/shared-react/components/index.d.ts +1 -0
  23. package/dist/shared-react/components/marquee-selection.d.ts +23 -0
  24. package/dist/svelte/components/MarqueeSelection.svelte.d.ts +17 -0
  25. package/dist/svelte/components/index.d.ts +1 -0
  26. package/dist/svelte/index.cjs +1 -1
  27. package/dist/svelte/index.cjs.map +1 -1
  28. package/dist/svelte/index.js +58 -2
  29. package/dist/svelte/index.js.map +1 -1
  30. package/dist/vue/components/index.d.ts +1 -0
  31. package/dist/vue/components/marquee-selection.vue.d.ts +20 -0
  32. package/dist/vue/index.cjs +1 -1
  33. package/dist/vue/index.cjs.map +1 -1
  34. package/dist/vue/index.js +68 -6
  35. package/dist/vue/index.js.map +1 -1
  36. package/package.json +10 -10
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("@embedpdf/core"),t=require("@embedpdf/models"),i="selection",n={id:i,name:"Selection Plugin",version:"1.0.0",provides:["selection"],requires:["interaction-manager"],optional:["viewport","scroll"],defaultConfig:{menuHeight:40}},o="SELECTION/INIT_STATE",s="SELECTION/CLEANUP_STATE",c="SELECTION/CACHE_PAGE_GEOMETRY",r="SELECTION/SET_SELECTION",a="SELECTION/START_SELECTION",l="SELECTION/END_SELECTION",d="SELECTION/CLEAR_SELECTION",g="SELECTION/SET_RECTS",h="SELECTION/SET_SLICES",u={geometry:{},rects:{},slices:{},selection:null,active:!1,selecting:!1},p={documents:{}},m=(e,t,i)=>({...e,documents:{...e.documents,[t]:i}});function S(e,t){return e.rects[t]??[]}function y(e,i){return t.boundingRect(S(e,i))}function f(e){const i=[],n=e.rects;for(const o in n){const e=Number(o),s=t.boundingRect(n[e]);s&&i.push({page:e,rect:s})}return i}function C(e,t){const i=e.rects[t]||[];if(0===i.length)return null;const n=y(e,t);return n?{pageIndex:t,rect:n,segmentRects:i}:null}function x(e){const t=[],i=Object.keys(e.rects).map(Number);for(const n of i){const i=e.rects[n]||[];if(0===i.length)continue;const o=y(e,n);o&&t.push({pageIndex:n,rect:o,segmentRects:i})}return t}function b(e,t){for(const i of e.runs){if(!(t.y>=i.rect.y&&t.y<=i.rect.y+i.rect.height&&t.x>=i.rect.x&&t.x<=i.rect.x+i.rect.width))continue;const e=i.glyphs.findIndex(e=>t.x>=e.x&&t.x<=e.x+e.width&&t.y>=e.y&&t.y<=e.y+e.height);if(-1!==e)return i.charStart+e}return-1}function P(e,t,i){if(!e||!t)return null;if(i<e.start.page||i>e.end.page)return null;const n=i===e.start.page?e.start.index:0,o=t.runs[t.runs.length-1],s=o.charStart+o.glyphs.length-1;return{from:n,to:i===e.end.page?e.end.index:s}}function E(e,t,i,n=!0){const o=[];for(const s of e.runs){const e=s.charStart,n=e+s.glyphs.length-1;if(n<t||e>i)continue;const c=Math.max(t,e)-e,r=Math.min(i,n)-e;let a=1/0,l=-1/0,d=1/0,g=-1/0,h=0;for(let t=c;t<=r;t++){const e=s.glyphs[t];2!==e.flags&&(a=Math.min(a,e.x),l=Math.max(l,e.x+e.width),d=Math.min(d,e.y),g=Math.max(g,e.y+e.height),h++)}a!==1/0&&h>0&&o.push({rect:{origin:{x:a,y:d},size:{width:l-a,height:g-d}},charCount:h})}return n?$(o):o.map(e=>e.rect)}function T(e,t){const i=Math.min(e.origin.x,t.origin.x),n=Math.min(e.origin.y,t.origin.y);return{origin:{x:i,y:n},size:{width:Math.max(e.origin.x+e.size.width,t.origin.x+t.size.width)-i,height:Math.max(e.origin.y+e.size.height,t.origin.y+t.size.height)-n}}}function v(e,t){const i=Math.max(e.origin.x,t.origin.x),n=Math.max(e.origin.y,t.origin.y),o=Math.min(e.origin.x+e.size.width,t.origin.x+t.size.width),s=Math.min(e.origin.y+e.size.height,t.origin.y+t.size.height);return{origin:{x:i,y:n},size:{width:Math.max(0,o-i),height:Math.max(0,s-n)}}}function I(e){return e.size.width<=0||e.size.height<=0}function M(e,t){if(I(e)||I(t))return 0;const i=T(e,t);if(i.size.height===e.size.height||i.size.height===t.size.height)return 1;return v(e,t).size.height/i.size.height}function w(e,t){const i=e.rect,n=t.rect;if(M(i,n)<.8)return!1;const o=1*i.size.width/e.charCount,s=1*n.size.width/t.charCount,c=i.origin.x-o,r=i.origin.x+i.size.width+o,a=n.origin.x-s;return c<n.origin.x+n.size.width+s&&r>a}function $(e){const t=[];let i=null,n=null;for(const o of e)i&&n?w(i,o)?n=T(n,o.rect):(t.push(n),n=o.rect):n=o.rect,i=o;return n&&!I(n)&&t.push(n),t}const D=class extends e.BasePlugin{constructor(i,n,o){var s,c,r;super(i,n),this.enabledModesPerDoc=new Map,this.selecting=new Map,this.anchor=new Map,this.pageCallbacks=new Map,this.menuPlacement$=e.createScopedEmitter((e,t)=>({documentId:e,placement:t})),this.selChange$=e.createScopedEmitter((e,t)=>({documentId:e,selection:t})),this.textRetrieved$=e.createScopedEmitter((e,t)=>({documentId:e,text:t})),this.copyToClipboard$=e.createScopedEmitter((e,t)=>({documentId:e,text:t}),{cache:!1}),this.beginSelection$=e.createScopedEmitter((e,t)=>({documentId:e,page:t.page,index:t.index}),{cache:!1}),this.endSelection$=e.createScopedEmitter(e=>({documentId:e}),{cache:!1}),this.viewportCapability=null,this.scrollCapability=null,this.menuHeight=o.menuHeight??40;const a=n.getPlugin("interaction-manager");if(!a)throw new Error("SelectionPlugin: InteractionManagerPlugin is required.");this.interactionManagerCapability=a.provides(),this.viewportCapability=(null==(s=n.getPlugin("viewport"))?void 0:s.provides())??null,this.scrollCapability=(null==(c=n.getPlugin("scroll"))?void 0:c.provides())??null,this.coreStore.onAction(e.REFRESH_PAGES,e=>{const{documentId:i,pageIndexes:n}=e.payload,o=n.map(e=>this.getNewPageGeometryAndCache(i,e));t.Task.all(o).wait(()=>{n.forEach(e=>{this.notifyPage(i,e)})},t.ignore)}),null==(r=this.viewportCapability)||r.onViewportChange(e=>{this.recalculateMenuPlacement(e.documentId)},{mode:"throttle",wait:100})}onDocumentLoadingStarted(e){this.dispatch(((e,t)=>({type:o,payload:{documentId:e,state:t}}))(e,u)),this.enabledModesPerDoc.set(e,new Set(["pointerMode"])),this.pageCallbacks.set(e,new Map),this.selecting.set(e,!1),this.anchor.set(e,void 0)}onDocumentClosed(e){this.dispatch((e=>({type:s,payload:e}))(e)),this.enabledModesPerDoc.delete(e),this.pageCallbacks.delete(e),this.selecting.delete(e),this.anchor.delete(e),this.selChange$.clearScope(e),this.textRetrieved$.clearScope(e),this.copyToClipboard$.clearScope(e),this.beginSelection$.clearScope(e),this.endSelection$.clearScope(e),this.menuPlacement$.clearScope(e)}async initialize(){}async destroy(){this.selChange$.clear(),this.textRetrieved$.clear(),this.copyToClipboard$.clear(),this.beginSelection$.clear(),this.endSelection$.clear(),this.menuPlacement$.clear(),super.destroy()}buildCapability(){const e=e=>e??this.getActiveDocumentId();return{getFormattedSelection:t=>x(this.getDocumentState(e(t))),getFormattedSelectionForPage:(t,i)=>C(this.getDocumentState(e(i)),t),getHighlightRectsForPage:(t,i)=>S(this.getDocumentState(e(i)),t),getHighlightRects:t=>this.getDocumentState(e(t)).rects,getBoundingRectForPage:(t,i)=>y(this.getDocumentState(e(i)),t),getBoundingRects:t=>f(this.getDocumentState(e(t))),getSelectedText:t=>this.getSelectedText(e(t)),clear:t=>this.clearSelection(e(t)),copyToClipboard:t=>this.copyToClipboard(e(t)),getState:t=>this.getDocumentState(e(t)),enableForMode:(t,i)=>{var n;return null==(n=this.enabledModesPerDoc.get(e(i)))?void 0:n.add(t)},isEnabledForMode:(t,i)=>{var n;return(null==(n=this.enabledModesPerDoc.get(e(i)))?void 0:n.has(t))??!1},forDocument:this.createSelectionScope.bind(this),onCopyToClipboard:this.copyToClipboard$.onGlobal,onSelectionChange:this.selChange$.onGlobal,onTextRetrieved:this.textRetrieved$.onGlobal,onBeginSelection:this.beginSelection$.onGlobal,onEndSelection:this.endSelection$.onGlobal}}createSelectionScope(e){return{getFormattedSelection:()=>x(this.getDocumentState(e)),getFormattedSelectionForPage:t=>C(this.getDocumentState(e),t),getHighlightRectsForPage:t=>S(this.getDocumentState(e),t),getHighlightRects:()=>this.getDocumentState(e).rects,getBoundingRectForPage:t=>y(this.getDocumentState(e),t),getBoundingRects:()=>f(this.getDocumentState(e)),getSelectedText:()=>this.getSelectedText(e),clear:()=>this.clearSelection(e),copyToClipboard:()=>this.copyToClipboard(e),getState:()=>this.getDocumentState(e),onSelectionChange:this.selChange$.forScope(e),onTextRetrieved:this.textRetrieved$.forScope(e),onCopyToClipboard:this.copyToClipboard$.forScope(e),onBeginSelection:this.beginSelection$.forScope(e),onEndSelection:this.endSelection$.forScope(e)}}getDocumentState(e){const t=this.state.documents[e];if(!t)throw new Error(`Selection state not found for document: ${e}`);return t}onMenuPlacement(e,t){return this.menuPlacement$.forScope(e)(t)}registerSelectionOnPage(e){var i;const{documentId:n,pageIndex:o,onRectsChange:s}=e,c=this.state.documents[n];if(!c)return this.logger.warn("SelectionPlugin","RegisterFailed",`Cannot register selection on page ${o} for document ${n}: document state not initialized.`),()=>{};null==(i=this.pageCallbacks.get(n))||i.set(o,s);const r=this.getOrLoadGeometry(n,o),a=this.interactionManagerCapability.forDocument(n),l=this.enabledModesPerDoc.get(n);s({rects:S(c,o),boundingRect:y(c,o)});const d={onPointerDown:(e,t,i)=>{if(!(null==l?void 0:l.has(i)))return;this.clearSelection(n);const s=this.getDocumentState(n).geometry[o];if(s){const t=b(s,e);-1!==t&&this.beginSelection(n,o,t)}},onPointerMove:(e,t,i)=>{if(!(null==l?void 0:l.has(i)))return;const s=this.getDocumentState(n).geometry[o];if(s){const t=b(s,e);-1!==t?a.setCursor("selection-text","text",10):a.removeCursor("selection-text"),this.selecting.get(n)&&-1!==t&&this.updateSelection(n,o,t)}},onPointerUp:(e,t,i)=>{(null==l?void 0:l.has(i))&&this.endSelection(n)},onHandlerActiveEnd:e=>{(null==l?void 0:l.has(e))&&this.clearSelection(n)}},g=this.interactionManagerCapability.registerAlways({scope:{type:"page",documentId:n,pageIndex:o},handlers:d});return()=>{var e;g(),null==(e=this.pageCallbacks.get(n))||e.delete(o),r.abort({code:t.PdfErrorCode.Cancelled,message:"Cleanup"})}}getPlacementMetrics(e,t,i,n){var o;const s=null==(o=this.scrollCapability)?void 0:o.forDocument(e),c=null==s?void 0:s.getRectPositionForPage(t,i);if(!c)return null;const r=c.origin.y-n.scrollTop,a=c.origin.y+c.size.height-n.scrollTop;return{pageIndex:t,rect:i,spaceAbove:r,spaceBelow:n.clientHeight-a,isBottomVisible:a>0&&a<=n.clientHeight,isTopVisible:r>=0&&r<n.clientHeight}}recalculateMenuPlacement(e){const t=this.state.documents[e];if(!t)return;if(t.selecting||null===t.selection)return void this.menuPlacement$.emit(e,null);const i=f(t);if(0===i.length)return void this.menuPlacement$.emit(e,null);const n=i[i.length-1];if(!this.viewportCapability||!this.scrollCapability)return void this.menuPlacement$.emit(e,{pageIndex:n.page,rect:n.rect,spaceAbove:0,spaceBelow:1/0,suggestTop:!1,isVisible:!0});const o=this.viewportCapability.forDocument(e).getMetrics(),s=i[0],c=this.getPlacementMetrics(e,n.page,n.rect,o),r=this.getPlacementMetrics(e,s.page,s.rect,o);c&&c.isBottomVisible&&c.spaceBelow>this.menuHeight?this.menuPlacement$.emit(e,{...c,suggestTop:!1,isVisible:!0}):r&&r.isTopVisible?this.menuPlacement$.emit(e,{...r,suggestTop:!0,isVisible:!0}):c&&c.isBottomVisible?this.menuPlacement$.emit(e,{...c,suggestTop:!1,isVisible:!0}):this.menuPlacement$.emit(e,null)}notifyPage(e,t){var i;const n=null==(i=this.pageCallbacks.get(e))?void 0:i.get(t);if(n){const i=this.getDocumentState(e);n("pointerMode"===this.interactionManagerCapability.forDocument(e).getActiveMode()?{rects:S(i,t),boundingRect:y(i,t)}:{rects:[],boundingRect:null})}}notifyAllPages(e){var t;null==(t=this.pageCallbacks.get(e))||t.forEach((t,i)=>{this.notifyPage(e,i)})}getNewPageGeometryAndCache(e,i){const n=this.getCoreDocument(e);if(!n||!n.document)return t.PdfTaskHelper.reject({code:t.PdfErrorCode.NotFound,message:"Doc Not Found"});const o=n.document.pages.find(e=>e.index===i),s=this.engine.getPageGeometry(n.document,o);return s.wait(t=>{this.dispatch(((e,t,i)=>({type:c,payload:{documentId:e,page:t,geo:i}}))(e,i,t))},t.ignore),s}getOrLoadGeometry(e,i){const n=this.getDocumentState(e).geometry[i];return n?t.PdfTaskHelper.resolve(n):this.getNewPageGeometryAndCache(e,i)}beginSelection(e,t,i){this.selecting.set(e,!0),this.anchor.set(e,{page:t,index:i}),this.dispatch((e=>({type:a,payload:{documentId:e}}))(e)),this.beginSelection$.emit(e,{page:t,index:i}),this.recalculateMenuPlacement(e)}endSelection(e){this.selecting.set(e,!1),this.anchor.set(e,void 0),this.dispatch((e=>({type:l,payload:{documentId:e}}))(e)),this.endSelection$.emit(e),this.recalculateMenuPlacement(e)}clearSelection(e){this.selecting.set(e,!1),this.anchor.set(e,void 0),this.dispatch((e=>({type:d,payload:{documentId:e}}))(e)),this.selChange$.emit(e,null),this.menuPlacement$.emit(e,null),this.notifyAllPages(e)}updateSelection(e,t,i){if(!this.selecting.get(e)||!this.anchor.get(e))return;const n=this.anchor.get(e),o=t>n.page||t===n.page&&i>=n.index,s={start:o?n:{page:t,index:i},end:o?{page:t,index:i}:n};this.dispatch(((e,t)=>({type:r,payload:{documentId:e,selection:t}}))(e,s)),this.updateRectsAndSlices(e,s),this.selChange$.emit(e,s);for(let c=s.start.page;c<=s.end.page;c++)this.notifyPage(e,c)}updateRectsAndSlices(e,t){const i=this.getDocumentState(e),n={},o={};for(let s=t.start.page;s<=t.end.page;s++){const e=i.geometry[s],c=P(t,e,s);c&&(n[s]=E(e,c.from,c.to),o[s]={start:c.from,count:c.to-c.from+1})}this.dispatch(((e,t)=>({type:g,payload:{documentId:e,rects:t}}))(e,n)),this.dispatch(((e,t)=>({type:h,payload:{documentId:e,slices:t}}))(e,o))}getSelectedText(e){if(!this.checkPermission(e,t.PdfPermissionFlag.CopyContents))return this.logger.debug("SelectionPlugin","GetSelectedText",`Cannot get selected text: document ${e} lacks CopyContents permission`),t.PdfTaskHelper.reject({code:t.PdfErrorCode.Security,message:"Document lacks CopyContents permission"});const i=this.getCoreDocument(e),n=this.getDocumentState(e);if(!(null==i?void 0:i.document)||!n.selection)return t.PdfTaskHelper.reject({code:t.PdfErrorCode.NotFound,message:"Doc Not Found or No Selection"});const o=n.selection,s=[];for(let t=o.start.page;t<=o.end.page;t++){const e=n.slices[t];e&&s.push({pageIndex:t,charIndex:e.start,charCount:e.count})}if(0===s.length)return t.PdfTaskHelper.resolve([]);const c=this.engine.getTextSlices(i.document,s);return c.wait(t=>{this.textRetrieved$.emit(e,t)},t.ignore),c}copyToClipboard(e){if(!this.checkPermission(e,t.PdfPermissionFlag.CopyContents))return void this.logger.debug("SelectionPlugin","CopyToClipboard",`Cannot copy to clipboard: document ${e} lacks CopyContents permission`);this.getSelectedText(e).wait(t=>{this.copyToClipboard$.emit(e,t.join("\n"))},t.ignore)}};D.id="selection";let R=D;const N={manifest:n,create:(e,t)=>new R(i,e,t),reducer:(e=p,t)=>{switch(t.type){case o:{const{documentId:i,state:n}=t.payload;return m(e,i,n)}case s:{const i=t.payload,{[i]:n,...o}=e.documents;return{...e,documents:o}}case c:{const{documentId:i,page:n,geo:o}=t.payload,s=e.documents[i];return s?m(e,i,{...s,geometry:{...s.geometry,[n]:o}}):e}case r:{const{documentId:i,selection:n}=t.payload,o=e.documents[i];return o?m(e,i,{...o,selection:n,active:!0}):e}case a:{const{documentId:i}=t.payload,n=e.documents[i];return n?m(e,i,{...n,selecting:!0,selection:null,rects:{}}):e}case l:{const{documentId:i}=t.payload,n=e.documents[i];return n?m(e,i,{...n,selecting:!1}):e}case d:{const{documentId:i}=t.payload,n=e.documents[i];return n?m(e,i,{...n,selecting:!1,selection:null,rects:{},active:!1}):e}case g:{const{documentId:i,rects:n}=t.payload,o=e.documents[i];return o?m(e,i,{...o,rects:n}):e}case h:{const{documentId:i,slices:n}=t.payload,o=e.documents[i];return o?m(e,i,{...o,slices:n}):e}case"SELECTION/RESET":{const{documentId:i}=t.payload;return e.documents[i]?m(e,i,u):e}default:return e}},initialState:p};exports.SELECTION_PLUGIN_ID=i,exports.SelectionPlugin=R,exports.SelectionPluginPackage=N,exports.getVerticalOverlap=M,exports.glyphAt=b,exports.manifest=n,exports.mergeAdjacentRects=$,exports.rectIntersect=v,exports.rectIsEmpty=I,exports.rectUnion=T,exports.rectsWithinSlice=E,exports.shouldMergeHorizontalRects=w,exports.sliceBounds=P;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("@embedpdf/core"),t=require("@embedpdf/models"),n="selection",i={id:n,name:"Selection Plugin",version:"1.0.0",provides:["selection"],requires:["interaction-manager"],optional:["viewport","scroll"],defaultConfig:{menuHeight:40}},o="SELECTION/INIT_STATE",s="SELECTION/CLEANUP_STATE",a="SELECTION/CACHE_PAGE_GEOMETRY",r="SELECTION/SET_SELECTION",c="SELECTION/START_SELECTION",l="SELECTION/END_SELECTION",d="SELECTION/CLEAR_SELECTION",g="SELECTION/SET_RECTS",u="SELECTION/SET_SLICES",h={geometry:{},rects:{},slices:{},selection:null,active:!1,selecting:!1},m={documents:{}},p=(e,t,n)=>({...e,documents:{...e.documents,[t]:n}});function S(e,t){return e.rects[t]??[]}function y(e,n){return t.boundingRect(S(e,n))}function C(e){const n=[],i=e.rects;for(const o in i){const e=Number(o),s=t.boundingRect(i[e]);s&&n.push({page:e,rect:s})}return n}function b(e,t){const n=e.rects[t]||[];if(0===n.length)return null;const i=y(e,t);return i?{pageIndex:t,rect:i,segmentRects:n}:null}function f(e){const t=[],n=Object.keys(e.rects).map(Number);for(const i of n){const n=e.rects[i]||[];if(0===n.length)continue;const o=y(e,i);o&&t.push({pageIndex:i,rect:o,segmentRects:n})}return t}function x(e,t){for(const n of e.runs){if(!(t.y>=n.rect.y&&t.y<=n.rect.y+n.rect.height&&t.x>=n.rect.x&&t.x<=n.rect.x+n.rect.width))continue;const e=n.glyphs.findIndex(e=>t.x>=e.x&&t.x<=e.x+e.width&&t.y>=e.y&&t.y<=e.y+e.height);if(-1!==e)return n.charStart+e}return-1}function E(e,t,n){if(!e||!t)return null;if(n<e.start.page||n>e.end.page)return null;const i=n===e.start.page?e.start.index:0,o=t.runs[t.runs.length-1],s=o.charStart+o.glyphs.length-1;return{from:i,to:n===e.end.page?e.end.index:s}}function P(e,t,n,i=!0){const o=[];for(const s of e.runs){const e=s.charStart,i=e+s.glyphs.length-1;if(i<t||e>n)continue;const a=Math.max(t,e)-e,r=Math.min(n,i)-e;let c=1/0,l=-1/0,d=1/0,g=-1/0,u=0;for(let t=a;t<=r;t++){const e=s.glyphs[t];2!==e.flags&&(c=Math.min(c,e.x),l=Math.max(l,e.x+e.width),d=Math.min(d,e.y),g=Math.max(g,e.y+e.height),u++)}c!==1/0&&u>0&&o.push({rect:{origin:{x:c,y:d},size:{width:l-c,height:g-d}},charCount:u})}return i?w(o):o.map(e=>e.rect)}function M(e,t){const n=Math.min(e.origin.x,t.origin.x),i=Math.min(e.origin.y,t.origin.y);return{origin:{x:n,y:i},size:{width:Math.max(e.origin.x+e.size.width,t.origin.x+t.size.width)-n,height:Math.max(e.origin.y+e.size.height,t.origin.y+t.size.height)-i}}}function I(e,t){const n=Math.max(e.origin.x,t.origin.x),i=Math.max(e.origin.y,t.origin.y),o=Math.min(e.origin.x+e.size.width,t.origin.x+t.size.width),s=Math.min(e.origin.y+e.size.height,t.origin.y+t.size.height);return{origin:{x:n,y:i},size:{width:Math.max(0,o-n),height:Math.max(0,s-i)}}}function v(e){return e.size.width<=0||e.size.height<=0}function T(e,t){if(v(e)||v(t))return 0;const n=M(e,t);if(n.size.height===e.size.height||n.size.height===t.size.height)return 1;return I(e,t).size.height/n.size.height}function $(e,t){const n=e.rect,i=t.rect;if(T(n,i)<.8)return!1;const o=1*n.size.width/e.charCount,s=1*i.size.width/t.charCount,a=n.origin.x-o,r=n.origin.x+n.size.width+o,c=i.origin.x-s;return a<i.origin.x+i.size.width+s&&r>c}function w(e){const t=[];let n=null,i=null;for(const o of e)n&&i?$(n,o)?i=M(i,o.rect):(t.push(i),i=o.rect):i=o.rect,n=o;return i&&!v(i)&&t.push(i),t}const q=class extends e.BasePlugin{constructor(n,i,o){var s,a,r;super(n,i),this.enabledModesPerDoc=new Map,this.selecting=new Map,this.anchor=new Map,this.marqueeEnabled=new Map,this.marqueePage=new Map,this.pageCallbacks=new Map,this.menuPlacement$=e.createScopedEmitter((e,t)=>({documentId:e,placement:t})),this.selChange$=e.createScopedEmitter((e,t)=>({documentId:e,selection:t})),this.textRetrieved$=e.createScopedEmitter((e,t)=>({documentId:e,text:t})),this.copyToClipboard$=e.createScopedEmitter((e,t)=>({documentId:e,text:t}),{cache:!1}),this.beginSelection$=e.createScopedEmitter((e,t)=>({documentId:e,page:t.page,index:t.index}),{cache:!1}),this.endSelection$=e.createScopedEmitter(e=>({documentId:e}),{cache:!1}),this.marqueeChange$=e.createScopedEmitter((e,t)=>({documentId:e,pageIndex:t.pageIndex,rect:t.rect}),{cache:!1}),this.marqueeEnd$=e.createScopedEmitter((e,t)=>({documentId:e,pageIndex:t.pageIndex,rect:t.rect}),{cache:!1}),this.viewportCapability=null,this.scrollCapability=null,this.config=o,this.menuHeight=o.menuHeight??40;const c=i.getPlugin("interaction-manager");if(!c)throw new Error("SelectionPlugin: InteractionManagerPlugin is required.");this.interactionManagerCapability=c.provides(),this.viewportCapability=(null==(s=i.getPlugin("viewport"))?void 0:s.provides())??null,this.scrollCapability=(null==(a=i.getPlugin("scroll"))?void 0:a.provides())??null,this.coreStore.onAction(e.REFRESH_PAGES,e=>{const{documentId:n,pageIndexes:i}=e.payload,o=i.map(e=>this.getNewPageGeometryAndCache(n,e));t.Task.all(o).wait(()=>{i.forEach(e=>{this.notifyPage(n,e)})},t.ignore)}),null==(r=this.viewportCapability)||r.onViewportChange(e=>{this.recalculateMenuPlacement(e.documentId)},{mode:"throttle",wait:100})}onDocumentLoadingStarted(e){this.dispatch(((e,t)=>({type:o,payload:{documentId:e,state:t}}))(e,h)),this.enabledModesPerDoc.set(e,new Map([["pointerMode",{showRects:!0}]])),this.pageCallbacks.set(e,new Map),this.selecting.set(e,!1),this.anchor.set(e,void 0);const t=this.config.marquee;this.marqueeEnabled.set(e,!1!==(null==t?void 0:t.enabled))}onDocumentClosed(e){this.dispatch((e=>({type:s,payload:e}))(e)),this.enabledModesPerDoc.delete(e),this.pageCallbacks.delete(e),this.selecting.delete(e),this.anchor.delete(e),this.marqueeEnabled.delete(e),this.marqueePage.delete(e),this.selChange$.clearScope(e),this.textRetrieved$.clearScope(e),this.copyToClipboard$.clearScope(e),this.beginSelection$.clearScope(e),this.endSelection$.clearScope(e),this.menuPlacement$.clearScope(e),this.marqueeChange$.clearScope(e),this.marqueeEnd$.clearScope(e)}async initialize(){}async destroy(){this.selChange$.clear(),this.textRetrieved$.clear(),this.copyToClipboard$.clear(),this.beginSelection$.clear(),this.endSelection$.clear(),this.menuPlacement$.clear(),this.marqueeChange$.clear(),this.marqueeEnd$.clear(),super.destroy()}buildCapability(){const e=e=>e??this.getActiveDocumentId();return{getFormattedSelection:t=>f(this.getDocumentState(e(t))),getFormattedSelectionForPage:(t,n)=>b(this.getDocumentState(e(n)),t),getHighlightRectsForPage:(t,n)=>S(this.getDocumentState(e(n)),t),getHighlightRects:t=>this.getDocumentState(e(t)).rects,getBoundingRectForPage:(t,n)=>y(this.getDocumentState(e(n)),t),getBoundingRects:t=>C(this.getDocumentState(e(t))),getSelectedText:t=>this.getSelectedText(e(t)),clear:t=>this.clearSelection(e(t)),copyToClipboard:t=>this.copyToClipboard(e(t)),getState:t=>this.getDocumentState(e(t)),enableForMode:(t,n,i)=>{var o;return null==(o=this.enabledModesPerDoc.get(e(i)))?void 0:o.set(t,{showRects:!0,...n})},isEnabledForMode:(t,n)=>{var i;return(null==(i=this.enabledModesPerDoc.get(e(n)))?void 0:i.has(t))??!1},setMarqueeEnabled:(t,n)=>this.setMarqueeEnabled(e(n),t),isMarqueeEnabled:t=>this.isMarqueeEnabled(e(t)),forDocument:this.createSelectionScope.bind(this),onCopyToClipboard:this.copyToClipboard$.onGlobal,onSelectionChange:this.selChange$.onGlobal,onTextRetrieved:this.textRetrieved$.onGlobal,onBeginSelection:this.beginSelection$.onGlobal,onEndSelection:this.endSelection$.onGlobal,onMarqueeChange:this.marqueeChange$.onGlobal,onMarqueeEnd:this.marqueeEnd$.onGlobal}}createSelectionScope(e){return{getFormattedSelection:()=>f(this.getDocumentState(e)),getFormattedSelectionForPage:t=>b(this.getDocumentState(e),t),getHighlightRectsForPage:t=>S(this.getDocumentState(e),t),getHighlightRects:()=>this.getDocumentState(e).rects,getBoundingRectForPage:t=>y(this.getDocumentState(e),t),getBoundingRects:()=>C(this.getDocumentState(e)),getSelectedText:()=>this.getSelectedText(e),clear:()=>this.clearSelection(e),copyToClipboard:()=>this.copyToClipboard(e),getState:()=>this.getDocumentState(e),setMarqueeEnabled:t=>this.setMarqueeEnabled(e,t),isMarqueeEnabled:()=>this.isMarqueeEnabled(e),onSelectionChange:this.selChange$.forScope(e),onTextRetrieved:this.textRetrieved$.forScope(e),onCopyToClipboard:this.copyToClipboard$.forScope(e),onBeginSelection:this.beginSelection$.forScope(e),onEndSelection:this.endSelection$.forScope(e),onMarqueeChange:this.marqueeChange$.forScope(e),onMarqueeEnd:this.marqueeEnd$.forScope(e)}}getDocumentState(e){const t=this.state.documents[e];if(!t)throw new Error(`Selection state not found for document: ${e}`);return t}onMenuPlacement(e,t){return this.menuPlacement$.forScope(e)(t)}registerSelectionOnPage(e){var n;const{documentId:i,pageIndex:o,onRectsChange:s}=e,a=this.state.documents[i];if(!a)return this.logger.warn("SelectionPlugin","RegisterFailed",`Cannot register selection on page ${o} for document ${i}: document state not initialized.`),()=>{};null==(n=this.pageCallbacks.get(i))||n.set(o,s);const r=this.getOrLoadGeometry(i,o),c=this.interactionManagerCapability.forDocument(i),l=this.enabledModesPerDoc.get(i);s({rects:S(a,o),boundingRect:y(a,o)});const d=function(e){return{onPointerDown:(t,n,i)=>{if(!e.isEnabled(i))return;e.onClear();const o=e.getGeometry();if(o){const i=x(o,t);-1!==i&&(e.onBegin(i),n.stopImmediatePropagation())}},onPointerMove:(t,n,i)=>{if(!e.isEnabled(i))return;const o=e.getGeometry();if(o){const i=x(o,t);e.setCursor(-1!==i?"text":null),e.isSelecting()&&-1!==i&&(e.onUpdate(i),n.stopImmediatePropagation())}},onPointerUp:(t,n,i)=>{e.isEnabled(i)&&e.onEnd()},onHandlerActiveEnd:t=>{e.isEnabled(t)&&e.onClear()}}}({getGeometry:()=>this.getDocumentState(i).geometry[o],isEnabled:e=>(null==l?void 0:l.has(e))??!1,onBegin:e=>this.beginSelection(i,o,e),onUpdate:e=>this.updateSelection(i,o,e),onEnd:()=>this.endSelection(i),onClear:()=>this.clearSelection(i),isSelecting:()=>this.selecting.get(i)??!1,setCursor:e=>e?c.setCursor("selection-text",e,10):c.removeCursor("selection-text")}),g=this.interactionManagerCapability.registerAlways({scope:{type:"page",documentId:i,pageIndex:o},handlers:d});return()=>{var e;g(),null==(e=this.pageCallbacks.get(i))||e.delete(o),r.abort({code:t.PdfErrorCode.Cancelled,message:"Cleanup"})}}registerMarqueeOnPage(t){var n;const{documentId:i,pageIndex:o,scale:s,onRectChange:a}=t;if(!this.state.documents[i])return this.logger.warn("SelectionPlugin","RegisterMarqueeFailed",`Cannot register marquee on page ${o} for document ${i}: document state not initialized.`),()=>{};const r=this.coreState.core.documents[i];if(!r||!r.document)return this.logger.warn("SelectionPlugin","DocumentNotFound",`Cannot register marquee on page ${o}: document not found`),()=>{};const c=r.document.pages[o];if(!c)return this.logger.warn("SelectionPlugin","PageNotFound",`Cannot register marquee on page ${o}: page not found`),()=>{};const l=function(t){const{pageSize:n,scale:i,minDragPx:o=5}=t;let s=null,a=null;return{onPointerDown:(e,n,i)=>{var o;t.isEnabled(i)&&(s=e,a={origin:{x:e.x,y:e.y},size:{width:0,height:0}},t.onBegin(e),null==(o=n.setPointerCapture)||o.call(n))},onPointerMove:(i,o,r)=>{if(!s||!t.isEnabled(r))return;const c=e.clamp(i.x,0,n.width),l=e.clamp(i.y,0,n.height);a={origin:{x:Math.min(s.x,c),y:Math.min(s.y,l)},size:{width:Math.abs(c-s.x),height:Math.abs(l-s.y)}},t.onChange(a)},onPointerUp:(e,n,r)=>{var c;t.isEnabled(r)&&(a&&s&&(Math.max(a.size.width,a.size.height)*i>o?t.onEnd(a):t.onCancel()),s=null,a=null,null==(c=n.releasePointerCapture)||c.call(n))},onPointerCancel:(e,n,i)=>{var o;t.isEnabled(i)&&(s=null,a=null,t.onCancel(),null==(o=n.releasePointerCapture)||o.call(n))}}}({pageSize:c.size,scale:s,minDragPx:(null==(n=this.config.marquee)?void 0:n.minDragPx)??5,isEnabled:()=>!1!==this.marqueeEnabled.get(i),onBegin:e=>this.beginMarquee(i,o,e),onChange:e=>{this.updateMarquee(i,o,e),a(e)},onEnd:e=>{this.endMarquee(i,o,e),a(null)},onCancel:()=>{this.cancelMarquee(i),a(null)}});return this.interactionManagerCapability.registerHandlers({documentId:i,pageIndex:o,modeId:"pointerMode",handlers:l})}getPlacementMetrics(e,t,n,i){var o;const s=null==(o=this.scrollCapability)?void 0:o.forDocument(e),a=null==s?void 0:s.getRectPositionForPage(t,n);if(!a)return null;const r=a.origin.y-i.scrollTop,c=a.origin.y+a.size.height-i.scrollTop;return{pageIndex:t,rect:n,spaceAbove:r,spaceBelow:i.clientHeight-c,isBottomVisible:c>0&&c<=i.clientHeight,isTopVisible:r>=0&&r<i.clientHeight}}recalculateMenuPlacement(e){const t=this.state.documents[e];if(!t)return;if(t.selecting||null===t.selection)return void this.menuPlacement$.emit(e,null);const n=C(t);if(0===n.length)return void this.menuPlacement$.emit(e,null);const i=n[n.length-1];if(!this.viewportCapability||!this.scrollCapability)return void this.menuPlacement$.emit(e,{pageIndex:i.page,rect:i.rect,spaceAbove:0,spaceBelow:1/0,suggestTop:!1,isVisible:!0});const o=this.viewportCapability.forDocument(e).getMetrics(),s=n[0],a=this.getPlacementMetrics(e,i.page,i.rect,o),r=this.getPlacementMetrics(e,s.page,s.rect,o);a&&a.isBottomVisible&&a.spaceBelow>this.menuHeight?this.menuPlacement$.emit(e,{...a,suggestTop:!1,isVisible:!0}):r&&r.isTopVisible?this.menuPlacement$.emit(e,{...r,suggestTop:!0,isVisible:!0}):a&&a.isBottomVisible?this.menuPlacement$.emit(e,{...a,suggestTop:!1,isVisible:!0}):this.menuPlacement$.emit(e,null)}notifyPage(e,t){var n,i;const o=null==(n=this.pageCallbacks.get(e))?void 0:n.get(t);if(o){const n=this.getDocumentState(e),s=this.interactionManagerCapability.forDocument(e).getActiveMode(),a=null==(i=this.enabledModesPerDoc.get(e))?void 0:i.get(s);o(a&&!1!==a.showRects?{rects:S(n,t),boundingRect:y(n,t)}:{rects:[],boundingRect:null})}}notifyAllPages(e){var t;null==(t=this.pageCallbacks.get(e))||t.forEach((t,n)=>{this.notifyPage(e,n)})}getNewPageGeometryAndCache(e,n){const i=this.getCoreDocument(e);if(!i||!i.document)return t.PdfTaskHelper.reject({code:t.PdfErrorCode.NotFound,message:"Doc Not Found"});const o=i.document.pages.find(e=>e.index===n),s=this.engine.getPageGeometry(i.document,o);return s.wait(t=>{this.dispatch(((e,t,n)=>({type:a,payload:{documentId:e,page:t,geo:n}}))(e,n,t))},t.ignore),s}getOrLoadGeometry(e,n){const i=this.getDocumentState(e).geometry[n];return i?t.PdfTaskHelper.resolve(i):this.getNewPageGeometryAndCache(e,n)}beginSelection(e,t,n){this.selecting.set(e,!0),this.anchor.set(e,{page:t,index:n}),this.dispatch((e=>({type:c,payload:{documentId:e}}))(e)),this.beginSelection$.emit(e,{page:t,index:n}),this.recalculateMenuPlacement(e)}endSelection(e){this.selecting.set(e,!1),this.anchor.set(e,void 0),this.dispatch((e=>({type:l,payload:{documentId:e}}))(e)),this.endSelection$.emit(e),this.recalculateMenuPlacement(e)}clearSelection(e){this.selecting.set(e,!1),this.anchor.set(e,void 0),this.dispatch((e=>({type:d,payload:{documentId:e}}))(e)),this.selChange$.emit(e,null),this.menuPlacement$.emit(e,null),this.notifyAllPages(e)}updateSelection(e,t,n){if(!this.selecting.get(e)||!this.anchor.get(e))return;const i=this.anchor.get(e),o=t>i.page||t===i.page&&n>=i.index,s={start:o?i:{page:t,index:n},end:o?{page:t,index:n}:i};this.dispatch(((e,t)=>({type:r,payload:{documentId:e,selection:t}}))(e,s)),this.updateRectsAndSlices(e,s),this.selChange$.emit(e,s);for(let a=s.start.page;a<=s.end.page;a++)this.notifyPage(e,a)}updateRectsAndSlices(e,t){const n=this.getDocumentState(e),i={},o={};for(let s=t.start.page;s<=t.end.page;s++){const e=n.geometry[s],a=E(t,e,s);a&&(i[s]=P(e,a.from,a.to),o[s]={start:a.from,count:a.to-a.from+1})}this.dispatch(((e,t)=>({type:g,payload:{documentId:e,rects:t}}))(e,i)),this.dispatch(((e,t)=>({type:u,payload:{documentId:e,slices:t}}))(e,o))}getSelectedText(e){if(!this.checkPermission(e,t.PdfPermissionFlag.CopyContents))return this.logger.debug("SelectionPlugin","GetSelectedText",`Cannot get selected text: document ${e} lacks CopyContents permission`),t.PdfTaskHelper.reject({code:t.PdfErrorCode.Security,message:"Document lacks CopyContents permission"});const n=this.getCoreDocument(e),i=this.getDocumentState(e);if(!(null==n?void 0:n.document)||!i.selection)return t.PdfTaskHelper.reject({code:t.PdfErrorCode.NotFound,message:"Doc Not Found or No Selection"});const o=i.selection,s=[];for(let t=o.start.page;t<=o.end.page;t++){const e=i.slices[t];e&&s.push({pageIndex:t,charIndex:e.start,charCount:e.count})}if(0===s.length)return t.PdfTaskHelper.resolve([]);const a=this.engine.getTextSlices(n.document,s);return a.wait(t=>{this.textRetrieved$.emit(e,t)},t.ignore),a}copyToClipboard(e){if(!this.checkPermission(e,t.PdfPermissionFlag.CopyContents))return void this.logger.debug("SelectionPlugin","CopyToClipboard",`Cannot copy to clipboard: document ${e} lacks CopyContents permission`);this.getSelectedText(e).wait(t=>{this.copyToClipboard$.emit(e,t.join("\n"))},t.ignore)}beginMarquee(e,t,n){this.marqueePage.set(e,t)}updateMarquee(e,t,n){this.marqueeChange$.emit(e,{pageIndex:t,rect:n})}endMarquee(e,t,n){this.marqueeEnd$.emit(e,{pageIndex:t,rect:n}),this.marqueeChange$.emit(e,{pageIndex:t,rect:null}),this.marqueePage.delete(e)}cancelMarquee(e){const t=this.marqueePage.get(e);void 0!==t&&(this.marqueeChange$.emit(e,{pageIndex:t,rect:null}),this.marqueePage.delete(e))}setMarqueeEnabled(e,t){this.marqueeEnabled.set(e,t)}isMarqueeEnabled(e){return!1!==this.marqueeEnabled.get(e)}};q.id="selection";let D=q;const R={manifest:i,create:(e,t)=>new D(n,e,t),reducer:(e=m,t)=>{switch(t.type){case o:{const{documentId:n,state:i}=t.payload;return p(e,n,i)}case s:{const n=t.payload,{[n]:i,...o}=e.documents;return{...e,documents:o}}case a:{const{documentId:n,page:i,geo:o}=t.payload,s=e.documents[n];return s?p(e,n,{...s,geometry:{...s.geometry,[i]:o}}):e}case r:{const{documentId:n,selection:i}=t.payload,o=e.documents[n];return o?p(e,n,{...o,selection:i,active:!0}):e}case c:{const{documentId:n}=t.payload,i=e.documents[n];return i?p(e,n,{...i,selecting:!0,selection:null,rects:{}}):e}case l:{const{documentId:n}=t.payload,i=e.documents[n];return i?p(e,n,{...i,selecting:!1}):e}case d:{const{documentId:n}=t.payload,i=e.documents[n];return i?p(e,n,{...i,selecting:!1,selection:null,rects:{},active:!1}):e}case g:{const{documentId:n,rects:i}=t.payload,o=e.documents[n];return o?p(e,n,{...o,rects:i}):e}case u:{const{documentId:n,slices:i}=t.payload,o=e.documents[n];return o?p(e,n,{...o,slices:i}):e}case"SELECTION/RESET":{const{documentId:n}=t.payload;return e.documents[n]?p(e,n,h):e}default:return e}},initialState:m};exports.SELECTION_PLUGIN_ID=n,exports.SelectionPlugin=D,exports.SelectionPluginPackage=R,exports.getVerticalOverlap=T,exports.glyphAt=x,exports.manifest=i,exports.mergeAdjacentRects=w,exports.rectIntersect=I,exports.rectIsEmpty=v,exports.rectUnion=M,exports.rectsWithinSlice=P,exports.shouldMergeHorizontalRects=$,exports.sliceBounds=E;
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../src/lib/manifest.ts","../src/lib/actions.ts","../src/lib/reducer.ts","../src/lib/selectors.ts","../src/lib/utils.ts","../src/lib/selection-plugin.ts","../src/lib/index.ts"],"sourcesContent":["import { PluginManifest } from '@embedpdf/core';\nimport { SelectionPluginConfig } from './types';\n\nexport const SELECTION_PLUGIN_ID = 'selection';\n\nexport const manifest: PluginManifest<SelectionPluginConfig> = {\n id: SELECTION_PLUGIN_ID,\n name: 'Selection Plugin',\n version: '1.0.0',\n provides: ['selection'],\n requires: ['interaction-manager'],\n optional: ['viewport', 'scroll'],\n defaultConfig: {\n menuHeight: 40,\n },\n};\n","import { Action } from '@embedpdf/core';\nimport { PdfPageGeometry, Rect } from '@embedpdf/models';\nimport { SelectionDocumentState, SelectionRangeX } from './types';\n\nexport const INIT_SELECTION_STATE = 'SELECTION/INIT_STATE';\nexport const CLEANUP_SELECTION_STATE = 'SELECTION/CLEANUP_STATE';\nexport const CACHE_PAGE_GEOMETRY = 'SELECTION/CACHE_PAGE_GEOMETRY';\nexport const SET_SELECTION = 'SELECTION/SET_SELECTION';\nexport const START_SELECTION = 'SELECTION/START_SELECTION';\nexport const END_SELECTION = 'SELECTION/END_SELECTION';\nexport const CLEAR_SELECTION = 'SELECTION/CLEAR_SELECTION';\nexport const SET_RECTS = 'SELECTION/SET_RECTS';\nexport const SET_SLICES = 'SELECTION/SET_SLICES';\nexport const RESET = 'SELECTION/RESET'; // This might be obsolete, but we'll keep it for now\n\nexport interface InitSelectionStateAction extends Action {\n type: typeof INIT_SELECTION_STATE;\n payload: {\n documentId: string;\n state: SelectionDocumentState;\n };\n}\n\nexport interface CleanupSelectionStateAction extends Action {\n type: typeof CLEANUP_SELECTION_STATE;\n payload: string; // documentId\n}\n\nexport interface CachePageGeometryAction extends Action {\n type: typeof CACHE_PAGE_GEOMETRY;\n payload: { documentId: string; page: number; geo: PdfPageGeometry };\n}\nexport interface SetSelectionAction extends Action {\n type: typeof SET_SELECTION;\n payload: { documentId: string; selection: SelectionRangeX | null };\n}\n\nexport interface StartSelectionAction extends Action {\n type: typeof START_SELECTION;\n payload: { documentId: string };\n}\n\nexport interface EndSelectionAction extends Action {\n type: typeof END_SELECTION;\n payload: { documentId: string };\n}\n\nexport interface ClearSelectionAction extends Action {\n type: typeof CLEAR_SELECTION;\n payload: { documentId: string };\n}\n\nexport interface SetRectsAction extends Action {\n type: typeof SET_RECTS;\n payload: { documentId: string; rects: Record<number, Rect[]> };\n}\n\nexport interface SetSlicesAction extends Action {\n type: typeof SET_SLICES;\n payload: { documentId: string; slices: Record<number, { start: number; count: number }> };\n}\n\nexport interface ResetAction extends Action {\n type: typeof RESET;\n payload: { documentId: string };\n}\n\nexport type SelectionAction =\n | InitSelectionStateAction\n | CleanupSelectionStateAction\n | CachePageGeometryAction\n | SetSelectionAction\n | StartSelectionAction\n | EndSelectionAction\n | ClearSelectionAction\n | SetRectsAction\n | SetSlicesAction\n | ResetAction;\n\nexport const initSelectionState = (\n documentId: string,\n state: SelectionDocumentState,\n): InitSelectionStateAction => ({\n type: INIT_SELECTION_STATE,\n payload: { documentId, state },\n});\n\nexport const cleanupSelectionState = (documentId: string): CleanupSelectionStateAction => ({\n type: CLEANUP_SELECTION_STATE,\n payload: documentId,\n});\n\nexport const cachePageGeometry = (\n documentId: string,\n page: number,\n geo: PdfPageGeometry,\n): CachePageGeometryAction => ({\n type: CACHE_PAGE_GEOMETRY,\n payload: { documentId, page, geo },\n});\n\nexport const setSelection = (\n documentId: string,\n sel: SelectionRangeX | null,\n): SetSelectionAction => ({\n type: SET_SELECTION,\n payload: { documentId, selection: sel },\n});\n\nexport const startSelection = (documentId: string): StartSelectionAction => ({\n type: START_SELECTION,\n payload: { documentId },\n});\n\nexport const endSelection = (documentId: string): EndSelectionAction => ({\n type: END_SELECTION,\n payload: { documentId },\n});\n\nexport const clearSelection = (documentId: string): ClearSelectionAction => ({\n type: CLEAR_SELECTION,\n payload: { documentId },\n});\n\nexport const setRects = (documentId: string, allRects: Record<number, Rect[]>): SetRectsAction => ({\n type: SET_RECTS,\n payload: { documentId, rects: allRects },\n});\n\nexport const setSlices = (\n documentId: string,\n slices: Record<number, { start: number; count: number }>,\n): SetSlicesAction => ({ type: SET_SLICES, payload: { documentId, slices } });\n\nexport const reset = (documentId: string): ResetAction => ({\n type: RESET,\n payload: { documentId },\n});\n","import { SelectionDocumentState, SelectionState } from './types';\nimport {\n SelectionAction,\n CACHE_PAGE_GEOMETRY,\n SET_SELECTION,\n START_SELECTION,\n END_SELECTION,\n CLEAR_SELECTION,\n RESET,\n SET_SLICES,\n SET_RECTS,\n INIT_SELECTION_STATE,\n CLEANUP_SELECTION_STATE,\n} from './actions';\n\nexport const initialSelectionDocumentState: SelectionDocumentState = {\n geometry: {},\n rects: {},\n slices: {},\n selection: null,\n active: false,\n selecting: false,\n};\n\nexport const initialState: SelectionState = {\n documents: {},\n};\n\nconst updateDocState = (\n state: SelectionState,\n documentId: string,\n newDocState: SelectionDocumentState,\n): SelectionState => ({\n ...state,\n documents: {\n ...state.documents,\n [documentId]: newDocState,\n },\n});\n\nexport const selectionReducer = (state = initialState, action: SelectionAction): SelectionState => {\n switch (action.type) {\n case INIT_SELECTION_STATE: {\n const { documentId, state: docState } = action.payload;\n return updateDocState(state, documentId, docState);\n }\n\n case CLEANUP_SELECTION_STATE: {\n const documentId = action.payload;\n const { [documentId]: removed, ...remaining } = state.documents;\n return {\n ...state,\n documents: remaining,\n };\n }\n\n case CACHE_PAGE_GEOMETRY: {\n const { documentId, page, geo } = action.payload;\n const docState = state.documents[documentId];\n if (!docState) return state;\n return updateDocState(state, documentId, {\n ...docState,\n geometry: { ...docState.geometry, [page]: geo },\n });\n }\n\n case SET_SELECTION: {\n const { documentId, selection } = action.payload;\n const docState = state.documents[documentId];\n if (!docState) return state;\n return updateDocState(state, documentId, {\n ...docState,\n selection,\n active: true,\n });\n }\n\n case START_SELECTION: {\n const { documentId } = action.payload;\n const docState = state.documents[documentId];\n if (!docState) return state;\n return updateDocState(state, documentId, {\n ...docState,\n selecting: true,\n selection: null,\n rects: {},\n });\n }\n\n case END_SELECTION: {\n const { documentId } = action.payload;\n const docState = state.documents[documentId];\n if (!docState) return state;\n return updateDocState(state, documentId, { ...docState, selecting: false });\n }\n\n case CLEAR_SELECTION: {\n const { documentId } = action.payload;\n const docState = state.documents[documentId];\n if (!docState) return state;\n return updateDocState(state, documentId, {\n ...docState,\n selecting: false,\n selection: null,\n rects: {},\n active: false,\n });\n }\n\n case SET_RECTS: {\n const { documentId, rects } = action.payload;\n const docState = state.documents[documentId];\n if (!docState) return state;\n return updateDocState(state, documentId, { ...docState, rects });\n }\n\n case SET_SLICES: {\n const { documentId, slices } = action.payload;\n const docState = state.documents[documentId];\n if (!docState) return state;\n return updateDocState(state, documentId, { ...docState, slices });\n }\n\n case RESET: {\n const { documentId } = action.payload;\n const docState = state.documents[documentId];\n if (!docState) return state;\n return updateDocState(state, documentId, initialSelectionDocumentState);\n }\n\n default:\n return state;\n }\n};\n","import { Rect, boundingRect } from '@embedpdf/models';\nimport { FormattedSelection, SelectionDocumentState } from './types';\n\nexport function selectRectsForPage(state: SelectionDocumentState, page: number) {\n return state.rects[page] ?? [];\n}\n\nexport function selectBoundingRectForPage(state: SelectionDocumentState, page: number) {\n return boundingRect(selectRectsForPage(state, page));\n}\n\nexport function selectRectsAndBoundingRectForPage(state: SelectionDocumentState, page: number) {\n return {\n rects: selectRectsForPage(state, page),\n boundingRect: selectBoundingRectForPage(state, page),\n };\n}\n\nexport function selectBoundingRectsForAllPages(state: SelectionDocumentState) {\n const out: { page: number; rect: Rect }[] = [];\n const rectMap = state.rects;\n\n for (const key in rectMap) {\n const page = Number(key);\n const bRect = boundingRect(rectMap[page]);\n if (bRect) out.push({ page, rect: bRect });\n }\n return out;\n}\n\nexport function getFormattedSelectionForPage(\n state: SelectionDocumentState,\n page: number,\n): FormattedSelection | null {\n const segmentRects = state.rects[page] || [];\n if (segmentRects.length === 0) return null;\n const boundingRect = selectBoundingRectForPage(state, page);\n if (!boundingRect) return null;\n return { pageIndex: page, rect: boundingRect, segmentRects };\n}\n\nexport function getFormattedSelection(state: SelectionDocumentState) {\n const result: FormattedSelection[] = [];\n\n // Get all pages that have rects\n const pages = Object.keys(state.rects).map(Number);\n\n for (const pageIndex of pages) {\n const segmentRects = state.rects[pageIndex] || [];\n\n if (segmentRects.length === 0) continue;\n\n // Calculate bounding rect for this page\n const boundingRect = selectBoundingRectForPage(state, pageIndex);\n\n if (boundingRect) {\n result.push({\n pageIndex,\n rect: boundingRect,\n segmentRects,\n });\n }\n }\n\n return result;\n}\n","import { PdfPageGeometry, Position, Rect } from '@embedpdf/models';\nimport { SelectionRangeX } from './types';\n\n/**\n * Hit-test helper using runs\n * @param geo - page geometry\n * @param pt - point\n * @returns glyph index\n */\nexport function glyphAt(geo: PdfPageGeometry, pt: Position) {\n for (const run of geo.runs) {\n const inRun =\n pt.y >= run.rect.y &&\n pt.y <= run.rect.y + run.rect.height &&\n pt.x >= run.rect.x &&\n pt.x <= run.rect.x + run.rect.width;\n\n if (!inRun) continue;\n\n // Simply check if the point is within any glyph's bounding box\n const rel = run.glyphs.findIndex(\n (g) => pt.x >= g.x && pt.x <= g.x + g.width && pt.y >= g.y && pt.y <= g.y + g.height,\n );\n\n if (rel !== -1) {\n return run.charStart + rel;\n }\n }\n return -1;\n}\n\n/**\n * Helper: min/max glyph indices on `page` for current sel\n * @param sel - selection range\n * @param geo - page geometry\n * @param page - page index\n * @returns { from: number; to: number } | null\n */\nexport function sliceBounds(\n sel: SelectionRangeX | null,\n geo: PdfPageGeometry | undefined,\n page: number,\n): { from: number; to: number } | null {\n if (!sel || !geo) return null;\n if (page < sel.start.page || page > sel.end.page) return null;\n\n const from = page === sel.start.page ? sel.start.index : 0;\n\n const lastRun = geo.runs[geo.runs.length - 1];\n const lastCharOnPage = lastRun.charStart + lastRun.glyphs.length - 1;\n\n const to = page === sel.end.page ? sel.end.index : lastCharOnPage;\n\n return { from, to };\n}\n\n/**\n * Helper: build rects for a slice of the page\n * @param geo - page geometry\n * @param from - from index\n * @param to - to index\n * @param merge - whether to merge adjacent rects (default: true)\n * @returns rects\n */\nexport function rectsWithinSlice(\n geo: PdfPageGeometry,\n from: number,\n to: number,\n merge: boolean = true,\n): Rect[] {\n const textRuns: TextRunInfo[] = [];\n\n for (const run of geo.runs) {\n const runStart = run.charStart;\n const runEnd = runStart + run.glyphs.length - 1;\n if (runEnd < from || runStart > to) continue;\n\n const sIdx = Math.max(from, runStart) - runStart;\n const eIdx = Math.min(to, runEnd) - runStart;\n\n let minX = Infinity,\n maxX = -Infinity;\n let minY = Infinity,\n maxY = -Infinity;\n let charCount = 0;\n\n for (let i = sIdx; i <= eIdx; i++) {\n const g = run.glyphs[i];\n if (g.flags === 2) continue; // empty glyph\n\n minX = Math.min(minX, g.x);\n maxX = Math.max(maxX, g.x + g.width);\n minY = Math.min(minY, g.y);\n maxY = Math.max(maxY, g.y + g.height);\n charCount++;\n }\n\n if (minX !== Infinity && charCount > 0) {\n textRuns.push({\n rect: {\n origin: { x: minX, y: minY },\n size: { width: maxX - minX, height: maxY - minY },\n },\n charCount,\n });\n }\n }\n\n // If merge is false, just return the individual rects\n if (!merge) {\n return textRuns.map((run) => run.rect);\n }\n\n // Otherwise merge adjacent rects\n return mergeAdjacentRects(textRuns);\n}\n\n/**\n * ============================================================================\n * Rectangle Merging Algorithm\n * ============================================================================\n *\n * The following code is adapted from Chromium's PDF text selection implementation.\n *\n * Copyright 2010 The Chromium Authors\n * Use of this source code is governed by a BSD-style license that can be\n * found in the LICENSE file: https://source.chromium.org/chromium/chromium/src/+/main:LICENSE\n *\n * Original source:\n * https://source.chromium.org/chromium/chromium/src/+/main:pdf/pdfium/pdfium_range.cc\n *\n * Adapted for TypeScript and this project's Rect/geometry types.\n */\n\n/**\n * Text run info for rect merging (similar to Chromium's ScreenRectTextRunInfo)\n */\nexport interface TextRunInfo {\n rect: Rect;\n charCount: number;\n}\n\n/**\n * Helper functions for Rect operations\n */\nexport function rectUnion(rect1: Rect, rect2: Rect): Rect {\n const left = Math.min(rect1.origin.x, rect2.origin.x);\n const top = Math.min(rect1.origin.y, rect2.origin.y);\n const right = Math.max(rect1.origin.x + rect1.size.width, rect2.origin.x + rect2.size.width);\n const bottom = Math.max(rect1.origin.y + rect1.size.height, rect2.origin.y + rect2.size.height);\n\n return {\n origin: { x: left, y: top },\n size: { width: right - left, height: bottom - top },\n };\n}\n\nexport function rectIntersect(rect1: Rect, rect2: Rect): Rect {\n const left = Math.max(rect1.origin.x, rect2.origin.x);\n const top = Math.max(rect1.origin.y, rect2.origin.y);\n const right = Math.min(rect1.origin.x + rect1.size.width, rect2.origin.x + rect2.size.width);\n const bottom = Math.min(rect1.origin.y + rect1.size.height, rect2.origin.y + rect2.size.height);\n\n const width = Math.max(0, right - left);\n const height = Math.max(0, bottom - top);\n\n return {\n origin: { x: left, y: top },\n size: { width, height },\n };\n}\n\nexport function rectIsEmpty(rect: Rect): boolean {\n return rect.size.width <= 0 || rect.size.height <= 0;\n}\n\n/**\n * Returns a ratio between [0, 1] representing vertical overlap\n */\nexport function getVerticalOverlap(rect1: Rect, rect2: Rect): number {\n if (rectIsEmpty(rect1) || rectIsEmpty(rect2)) return 0;\n\n const unionRect = rectUnion(rect1, rect2);\n\n if (unionRect.size.height === rect1.size.height || unionRect.size.height === rect2.size.height) {\n return 1.0;\n }\n\n const intersectRect = rectIntersect(rect1, rect2);\n return intersectRect.size.height / unionRect.size.height;\n}\n\n/**\n * Returns true if there is sufficient horizontal and vertical overlap\n */\nexport function shouldMergeHorizontalRects(textRun1: TextRunInfo, textRun2: TextRunInfo): boolean {\n const VERTICAL_OVERLAP_THRESHOLD = 0.8;\n const rect1 = textRun1.rect;\n const rect2 = textRun2.rect;\n\n if (getVerticalOverlap(rect1, rect2) < VERTICAL_OVERLAP_THRESHOLD) {\n return false;\n }\n\n const HORIZONTAL_WIDTH_FACTOR = 1.0;\n const averageWidth1 = (HORIZONTAL_WIDTH_FACTOR * rect1.size.width) / textRun1.charCount;\n const averageWidth2 = (HORIZONTAL_WIDTH_FACTOR * rect2.size.width) / textRun2.charCount;\n\n const rect1Left = rect1.origin.x - averageWidth1;\n const rect1Right = rect1.origin.x + rect1.size.width + averageWidth1;\n const rect2Left = rect2.origin.x - averageWidth2;\n const rect2Right = rect2.origin.x + rect2.size.width + averageWidth2;\n\n return rect1Left < rect2Right && rect1Right > rect2Left;\n}\n\n/**\n * Merge adjacent rectangles based on proximity and overlap (similar to Chromium's algorithm)\n */\nexport function mergeAdjacentRects(textRuns: TextRunInfo[]): Rect[] {\n const results: Rect[] = [];\n let previousTextRun: TextRunInfo | null = null;\n let currentRect: Rect | null = null;\n\n for (const textRun of textRuns) {\n if (previousTextRun && currentRect) {\n if (shouldMergeHorizontalRects(previousTextRun, textRun)) {\n currentRect = rectUnion(currentRect, textRun.rect);\n } else {\n results.push(currentRect);\n currentRect = textRun.rect;\n }\n } else {\n currentRect = textRun.rect;\n }\n previousTextRun = textRun;\n }\n\n if (currentRect && !rectIsEmpty(currentRect)) {\n results.push(currentRect);\n }\n\n return results;\n}\n","import {\n BasePlugin,\n Listener,\n PluginRegistry,\n REFRESH_PAGES,\n createScopedEmitter,\n} from '@embedpdf/core';\nimport {\n PdfPageGeometry,\n Rect,\n PdfTask,\n PdfTaskHelper,\n PdfErrorCode,\n PdfPermissionFlag,\n ignore,\n PageTextSlice,\n Task,\n Position,\n} from '@embedpdf/models';\nimport {\n InteractionManagerCapability,\n InteractionManagerPlugin,\n PointerEventHandlersWithLifecycle,\n} from '@embedpdf/plugin-interaction-manager';\nimport { ViewportCapability, ViewportMetrics, ViewportPlugin } from '@embedpdf/plugin-viewport';\nimport { ScrollCapability, ScrollPlugin } from '@embedpdf/plugin-scroll';\n\nimport {\n cachePageGeometry,\n setSelection,\n SelectionAction,\n endSelection,\n startSelection,\n clearSelection,\n setRects,\n setSlices,\n initSelectionState,\n cleanupSelectionState,\n} from './actions';\nimport { initialSelectionDocumentState } from './reducer';\nimport * as selector from './selectors';\nimport {\n SelectionCapability,\n SelectionPluginConfig,\n SelectionRangeX,\n SelectionState,\n RegisterSelectionOnPageOptions,\n SelectionRectsCallback,\n SelectionScope,\n SelectionChangeEvent,\n TextRetrievedEvent,\n CopyToClipboardEvent,\n BeginSelectionEvent,\n EndSelectionEvent,\n SelectionDocumentState,\n SelectionMenuPlacement,\n SelectionMenuPlacementEvent,\n} from './types';\nimport { sliceBounds, rectsWithinSlice, glyphAt } from './utils';\n\nexport class SelectionPlugin extends BasePlugin<\n SelectionPluginConfig,\n SelectionCapability,\n SelectionState,\n SelectionAction\n> {\n static readonly id = 'selection' as const;\n\n /** Modes that should trigger text-selection logic, per document */\n private enabledModesPerDoc = new Map<string, Set<string>>();\n\n /* interactive state, per document */\n private selecting = new Map<string, boolean>();\n private anchor = new Map<string, { page: number; index: number } | undefined>();\n\n /** Page callbacks for rect updates, per document */\n private pageCallbacks = new Map<string, Map<number, (data: SelectionRectsCallback) => void>>();\n\n private readonly menuPlacement$ = createScopedEmitter<\n SelectionMenuPlacement | null,\n SelectionMenuPlacementEvent,\n string\n >((documentId, placement) => ({ documentId, placement }));\n private readonly selChange$ = createScopedEmitter<\n SelectionRangeX | null,\n SelectionChangeEvent,\n string\n >((documentId, selection) => ({ documentId, selection }));\n private readonly textRetrieved$ = createScopedEmitter<string[], TextRetrievedEvent, string>(\n (documentId, text) => ({ documentId, text }),\n );\n private readonly copyToClipboard$ = createScopedEmitter<string, CopyToClipboardEvent, string>(\n (documentId, text) => ({ documentId, text }),\n { cache: false },\n );\n private readonly beginSelection$ = createScopedEmitter<\n { page: number; index: number },\n BeginSelectionEvent,\n string\n >((documentId, data) => ({ documentId, page: data.page, index: data.index }), { cache: false });\n private readonly endSelection$ = createScopedEmitter<void, EndSelectionEvent, string>(\n (documentId) => ({ documentId }),\n { cache: false },\n );\n\n private interactionManagerCapability: InteractionManagerCapability;\n private viewportCapability: ViewportCapability | null = null;\n private scrollCapability: ScrollCapability | null = null;\n\n private readonly menuHeight: number;\n\n constructor(id: string, registry: PluginRegistry, config: SelectionPluginConfig) {\n super(id, registry);\n this.menuHeight = config.menuHeight ?? 40;\n\n const imPlugin = registry.getPlugin<InteractionManagerPlugin>('interaction-manager');\n if (!imPlugin) {\n throw new Error('SelectionPlugin: InteractionManagerPlugin is required.');\n }\n this.interactionManagerCapability = imPlugin.provides();\n this.viewportCapability = registry.getPlugin<ViewportPlugin>('viewport')?.provides() ?? null;\n this.scrollCapability = registry.getPlugin<ScrollPlugin>('scroll')?.provides() ?? null;\n\n this.coreStore.onAction(REFRESH_PAGES, (action) => {\n const { documentId, pageIndexes } = action.payload;\n const tasks = pageIndexes.map((pageIndex) =>\n this.getNewPageGeometryAndCache(documentId, pageIndex),\n );\n Task.all(tasks).wait(() => {\n // Notify affected pages about geometry updates\n pageIndexes.forEach((pageIndex) => {\n this.notifyPage(documentId, pageIndex);\n });\n }, ignore);\n });\n\n this.viewportCapability?.onViewportChange(\n (event) => {\n this.recalculateMenuPlacement(event.documentId);\n },\n { mode: 'throttle', wait: 100 },\n );\n }\n\n /* ── life-cycle ────────────────────────────────────────── */\n protected override onDocumentLoadingStarted(documentId: string): void {\n this.dispatch(initSelectionState(documentId, initialSelectionDocumentState));\n this.enabledModesPerDoc.set(documentId, new Set<string>(['pointerMode']));\n this.pageCallbacks.set(documentId, new Map());\n this.selecting.set(documentId, false);\n this.anchor.set(documentId, undefined);\n }\n\n protected override onDocumentClosed(documentId: string): void {\n this.dispatch(cleanupSelectionState(documentId));\n this.enabledModesPerDoc.delete(documentId);\n this.pageCallbacks.delete(documentId);\n this.selecting.delete(documentId);\n this.anchor.delete(documentId);\n this.selChange$.clearScope(documentId);\n this.textRetrieved$.clearScope(documentId);\n this.copyToClipboard$.clearScope(documentId);\n this.beginSelection$.clearScope(documentId);\n this.endSelection$.clearScope(documentId);\n this.menuPlacement$.clearScope(documentId);\n }\n\n async initialize() {}\n async destroy() {\n this.selChange$.clear();\n this.textRetrieved$.clear();\n this.copyToClipboard$.clear();\n this.beginSelection$.clear();\n this.endSelection$.clear();\n this.menuPlacement$.clear();\n super.destroy();\n }\n\n /* ── capability exposed to UI / other plugins ─────────── */\n buildCapability(): SelectionCapability {\n const getDocId = (documentId?: string) => documentId ?? this.getActiveDocumentId();\n\n return {\n // Active document operations\n getFormattedSelection: (docId) =>\n selector.getFormattedSelection(this.getDocumentState(getDocId(docId))),\n getFormattedSelectionForPage: (p, docId) =>\n selector.getFormattedSelectionForPage(this.getDocumentState(getDocId(docId)), p),\n getHighlightRectsForPage: (p, docId) =>\n selector.selectRectsForPage(this.getDocumentState(getDocId(docId)), p),\n getHighlightRects: (docId) => this.getDocumentState(getDocId(docId)).rects,\n getBoundingRectForPage: (p, docId) =>\n selector.selectBoundingRectForPage(this.getDocumentState(getDocId(docId)), p),\n getBoundingRects: (docId) =>\n selector.selectBoundingRectsForAllPages(this.getDocumentState(getDocId(docId))),\n getSelectedText: (docId) => this.getSelectedText(getDocId(docId)),\n clear: (docId) => this.clearSelection(getDocId(docId)),\n copyToClipboard: (docId) => this.copyToClipboard(getDocId(docId)),\n getState: (docId) => this.getDocumentState(getDocId(docId)),\n enableForMode: (modeId, docId) => this.enabledModesPerDoc.get(getDocId(docId))?.add(modeId),\n isEnabledForMode: (modeId, docId) =>\n this.enabledModesPerDoc.get(getDocId(docId))?.has(modeId) ?? false,\n\n // Document-scoped operations\n forDocument: this.createSelectionScope.bind(this),\n\n // Global events\n onCopyToClipboard: this.copyToClipboard$.onGlobal,\n onSelectionChange: this.selChange$.onGlobal,\n onTextRetrieved: this.textRetrieved$.onGlobal,\n onBeginSelection: this.beginSelection$.onGlobal,\n onEndSelection: this.endSelection$.onGlobal,\n };\n }\n\n private createSelectionScope(documentId: string): SelectionScope {\n return {\n getFormattedSelection: () =>\n selector.getFormattedSelection(this.getDocumentState(documentId)),\n getFormattedSelectionForPage: (p) =>\n selector.getFormattedSelectionForPage(this.getDocumentState(documentId), p),\n getHighlightRectsForPage: (p) =>\n selector.selectRectsForPage(this.getDocumentState(documentId), p),\n getHighlightRects: () => this.getDocumentState(documentId).rects,\n getBoundingRectForPage: (p) =>\n selector.selectBoundingRectForPage(this.getDocumentState(documentId), p),\n getBoundingRects: () =>\n selector.selectBoundingRectsForAllPages(this.getDocumentState(documentId)),\n getSelectedText: () => this.getSelectedText(documentId),\n clear: () => this.clearSelection(documentId),\n copyToClipboard: () => this.copyToClipboard(documentId),\n getState: () => this.getDocumentState(documentId),\n onSelectionChange: this.selChange$.forScope(documentId),\n onTextRetrieved: this.textRetrieved$.forScope(documentId),\n onCopyToClipboard: this.copyToClipboard$.forScope(documentId),\n onBeginSelection: this.beginSelection$.forScope(documentId),\n onEndSelection: this.endSelection$.forScope(documentId),\n };\n }\n\n private getDocumentState(documentId: string): SelectionDocumentState {\n const state = this.state.documents[documentId];\n if (!state) {\n throw new Error(`Selection state not found for document: ${documentId}`);\n }\n return state;\n }\n\n /**\n * Subscribe to menu placement changes for a specific document\n * @param documentId - The document ID to subscribe to\n * @param listener - Callback function that receives placement updates\n * @returns Unsubscribe function\n */\n public onMenuPlacement(\n documentId: string,\n listener: (placement: SelectionMenuPlacement | null) => void,\n ) {\n return this.menuPlacement$.forScope(documentId)(listener);\n }\n\n public registerSelectionOnPage(opts: RegisterSelectionOnPageOptions) {\n const { documentId, pageIndex, onRectsChange } = opts;\n const docState = this.state.documents[documentId];\n\n if (!docState) {\n this.logger.warn(\n 'SelectionPlugin',\n 'RegisterFailed',\n `Cannot register selection on page ${pageIndex} for document ${documentId}: document state not initialized.`,\n );\n return () => {};\n }\n\n // Track this callback for the page\n this.pageCallbacks.get(documentId)?.set(pageIndex, onRectsChange);\n\n const geoTask = this.getOrLoadGeometry(documentId, pageIndex);\n const interactionScope = this.interactionManagerCapability.forDocument(documentId);\n const enabledModes = this.enabledModesPerDoc.get(documentId);\n\n // Send initial state\n onRectsChange({\n rects: selector.selectRectsForPage(docState, pageIndex),\n boundingRect: selector.selectBoundingRectForPage(docState, pageIndex),\n });\n\n const handlers: PointerEventHandlersWithLifecycle<PointerEvent> = {\n onPointerDown: (point: Position, _evt, modeId) => {\n if (!enabledModes?.has(modeId)) return;\n\n // Clear the selection\n this.clearSelection(documentId);\n\n // Get geometry from cache (or load if needed)\n const cached = this.getDocumentState(documentId).geometry[pageIndex];\n if (cached) {\n const g = glyphAt(cached, point);\n if (g !== -1) {\n this.beginSelection(documentId, pageIndex, g);\n }\n }\n },\n onPointerMove: (point: Position, _evt, modeId) => {\n if (!enabledModes?.has(modeId)) return;\n\n // Get cached geometry (should be instant if already loaded)\n const cached = this.getDocumentState(documentId).geometry[pageIndex];\n if (cached) {\n const g = glyphAt(cached, point);\n\n // Update cursor\n if (g !== -1) {\n interactionScope.setCursor('selection-text', 'text', 10);\n } else {\n interactionScope.removeCursor('selection-text');\n }\n\n // Update selection if we're selecting\n if (this.selecting.get(documentId) && g !== -1) {\n this.updateSelection(documentId, pageIndex, g);\n }\n }\n },\n onPointerUp: (_point: Position, _evt, modeId) => {\n if (!enabledModes?.has(modeId)) return;\n this.endSelection(documentId);\n },\n onHandlerActiveEnd: (modeId) => {\n if (!enabledModes?.has(modeId)) return;\n this.clearSelection(documentId);\n },\n };\n\n // Register the handlers with interaction manager for all enabled modes\n // This assumes `registerAlways` is a method that runs handlers for *any* mode,\n // and the handler itself checks the `modeId`.\n // If `registerAlways` is not available, this would need to register\n // for each mode in `enabledModes` separately.\n const unregisterHandlers = this.interactionManagerCapability.registerAlways({\n scope: { type: 'page', documentId, pageIndex },\n handlers,\n });\n\n // Return cleanup function\n return () => {\n unregisterHandlers();\n this.pageCallbacks.get(documentId)?.delete(pageIndex);\n geoTask.abort({ code: PdfErrorCode.Cancelled, message: 'Cleanup' });\n };\n }\n\n /**\n * Helper to calculate viewport relative metrics for a page rect.\n * Returns null if the rect cannot be converted to viewport space.\n */\n private getPlacementMetrics(\n documentId: string,\n pageIndex: number,\n rect: Rect,\n vpMetrics: ViewportMetrics,\n ) {\n // 1. Convert Page Coordinate -> Viewport Coordinate\n // We use the scroll capability to handle rotation, scale, and scroll offset automatically\n const scrollScope = this.scrollCapability?.forDocument(documentId);\n const viewportRect = scrollScope?.getRectPositionForPage(pageIndex, rect);\n\n if (!viewportRect) return null;\n\n // 2. Calculate relative positions\n const rectTopInView = viewportRect.origin.y - vpMetrics.scrollTop;\n const rectBottomInView = viewportRect.origin.y + viewportRect.size.height - vpMetrics.scrollTop;\n\n return {\n pageIndex,\n rect, // Original Page Rect\n spaceAbove: rectTopInView,\n spaceBelow: vpMetrics.clientHeight - rectBottomInView,\n isBottomVisible: rectBottomInView > 0 && rectBottomInView <= vpMetrics.clientHeight,\n isTopVisible: rectTopInView >= 0 && rectTopInView < vpMetrics.clientHeight,\n };\n }\n\n private recalculateMenuPlacement(documentId: string) {\n const docState = this.state.documents[documentId];\n if (!docState) return;\n\n // Only show menu when not actively selecting\n if (docState.selecting || docState.selection === null) {\n this.menuPlacement$.emit(documentId, null);\n return;\n }\n\n // 1. Get Bounding Rects for all pages involved in selection.\n // These are implicitly sorted by pageIndex because updateRectsAndSlices\n // populates the map in ascending page order.\n const bounds = selector.selectBoundingRectsForAllPages(docState);\n\n if (bounds.length === 0) {\n this.menuPlacement$.emit(documentId, null);\n return;\n }\n\n const tail = bounds[bounds.length - 1];\n\n // Fallback: If viewport/scroll plugins are missing, always default to bottom of the last rect\n if (!this.viewportCapability || !this.scrollCapability) {\n this.menuPlacement$.emit(documentId, {\n pageIndex: tail.page,\n rect: tail.rect,\n spaceAbove: 0,\n spaceBelow: Infinity, // Pretend we have infinite space below to prevent auto-flipping\n suggestTop: false,\n isVisible: true, // Assume visible\n });\n return;\n }\n\n // Use document-scoped viewport to get metrics for this specific document\n const viewportScope = this.viewportCapability.forDocument(documentId);\n const vpMetrics = viewportScope.getMetrics();\n\n // 2. Calculate metrics for Head (Start) and Tail (End)\n const head = bounds[0];\n\n const tailMetrics = this.getPlacementMetrics(documentId, tail.page, tail.rect, vpMetrics);\n const headMetrics = this.getPlacementMetrics(documentId, head.page, head.rect, vpMetrics);\n\n // 3. Apply Heuristic Logic\n\n // Priority A: Bottom of Tail (Standard selection end)\n // If the bottom of the selection is visible and we have space below.\n if (tailMetrics) {\n if (tailMetrics.isBottomVisible && tailMetrics.spaceBelow > this.menuHeight) {\n this.menuPlacement$.emit(documentId, {\n ...tailMetrics,\n suggestTop: false,\n isVisible: true,\n });\n return;\n }\n }\n\n // Priority B: Top of Head (Selection start, if user scrolled up)\n // If the top of the start selection is visible, put the menu there.\n if (headMetrics) {\n if (headMetrics.isTopVisible) {\n this.menuPlacement$.emit(documentId, {\n ...headMetrics,\n suggestTop: true,\n isVisible: true,\n });\n return;\n }\n }\n\n // Priority C: Fallback to Tail Bottom if visible (even if tight space)\n // It's better to stick to the cursor end than jump to the top if space is tight.\n if (tailMetrics && tailMetrics.isBottomVisible) {\n this.menuPlacement$.emit(documentId, {\n ...tailMetrics,\n suggestTop: false,\n isVisible: true,\n });\n return;\n }\n\n // If completely off screen\n this.menuPlacement$.emit(documentId, null);\n }\n\n private notifyPage(documentId: string, pageIndex: number) {\n const callback = this.pageCallbacks.get(documentId)?.get(pageIndex);\n if (callback) {\n const docState = this.getDocumentState(documentId);\n const mode = this.interactionManagerCapability.forDocument(documentId).getActiveMode();\n if (mode === 'pointerMode') {\n callback({\n rects: selector.selectRectsForPage(docState, pageIndex),\n boundingRect: selector.selectBoundingRectForPage(docState, pageIndex),\n });\n } else {\n callback({ rects: [], boundingRect: null });\n }\n }\n }\n\n private notifyAllPages(documentId: string) {\n this.pageCallbacks.get(documentId)?.forEach((_, pageIndex) => {\n this.notifyPage(documentId, pageIndex);\n });\n }\n\n private getNewPageGeometryAndCache(\n documentId: string,\n pageIdx: number,\n ): PdfTask<PdfPageGeometry> {\n const coreDoc = this.getCoreDocument(documentId);\n if (!coreDoc || !coreDoc.document)\n return PdfTaskHelper.reject({ code: PdfErrorCode.NotFound, message: 'Doc Not Found' });\n\n const page = coreDoc.document.pages.find((p) => p.index === pageIdx)!;\n const task = this.engine.getPageGeometry(coreDoc.document, page);\n task.wait((geo) => {\n this.dispatch(cachePageGeometry(documentId, pageIdx, geo));\n }, ignore);\n return task;\n }\n\n /* ── geometry cache ───────────────────────────────────── */\n private getOrLoadGeometry(documentId: string, pageIdx: number): PdfTask<PdfPageGeometry> {\n const cached = this.getDocumentState(documentId).geometry[pageIdx];\n if (cached) return PdfTaskHelper.resolve(cached);\n\n return this.getNewPageGeometryAndCache(documentId, pageIdx);\n }\n\n /* ── selection state updates ───────────────────────────── */\n private beginSelection(documentId: string, page: number, index: number) {\n this.selecting.set(documentId, true);\n this.anchor.set(documentId, { page, index });\n this.dispatch(startSelection(documentId));\n this.beginSelection$.emit(documentId, { page, index });\n this.recalculateMenuPlacement(documentId);\n }\n\n private endSelection(documentId: string) {\n this.selecting.set(documentId, false);\n this.anchor.set(documentId, undefined);\n this.dispatch(endSelection(documentId));\n this.endSelection$.emit(documentId);\n this.recalculateMenuPlacement(documentId);\n }\n\n private clearSelection(documentId: string) {\n this.selecting.set(documentId, false);\n this.anchor.set(documentId, undefined);\n this.dispatch(clearSelection(documentId));\n this.selChange$.emit(documentId, null);\n this.menuPlacement$.emit(documentId, null);\n this.notifyAllPages(documentId);\n }\n\n private updateSelection(documentId: string, page: number, index: number) {\n if (!this.selecting.get(documentId) || !this.anchor.get(documentId)) return;\n\n const a = this.anchor.get(documentId)!;\n const forward = page > a.page || (page === a.page && index >= a.index);\n\n const start = forward ? a : { page, index };\n const end = forward ? { page, index } : a;\n\n const range = { start, end };\n this.dispatch(setSelection(documentId, range));\n this.updateRectsAndSlices(documentId, range);\n this.selChange$.emit(documentId, range);\n\n // Notify affected pages\n for (let p = range.start.page; p <= range.end.page; p++) {\n this.notifyPage(documentId, p);\n }\n }\n\n private updateRectsAndSlices(documentId: string, range: SelectionRangeX) {\n const docState = this.getDocumentState(documentId);\n const allRects: Record<number, Rect[]> = {};\n const allSlices: Record<number, { start: number; count: number }> = {};\n\n for (let p = range.start.page; p <= range.end.page; p++) {\n const geo = docState.geometry[p];\n const sb = sliceBounds(range, geo, p);\n if (!sb) continue;\n\n allRects[p] = rectsWithinSlice(geo!, sb.from, sb.to);\n allSlices[p] = { start: sb.from, count: sb.to - sb.from + 1 };\n }\n\n this.dispatch(setRects(documentId, allRects));\n this.dispatch(setSlices(documentId, allSlices));\n }\n\n private getSelectedText(documentId: string): PdfTask<string[]> {\n // Prevent extracting text without permission\n if (!this.checkPermission(documentId, PdfPermissionFlag.CopyContents)) {\n this.logger.debug(\n 'SelectionPlugin',\n 'GetSelectedText',\n `Cannot get selected text: document ${documentId} lacks CopyContents permission`,\n );\n return PdfTaskHelper.reject({\n code: PdfErrorCode.Security,\n message: 'Document lacks CopyContents permission',\n });\n }\n\n const coreDoc = this.getCoreDocument(documentId);\n const docState = this.getDocumentState(documentId);\n\n if (!coreDoc?.document || !docState.selection) {\n return PdfTaskHelper.reject({\n code: PdfErrorCode.NotFound,\n message: 'Doc Not Found or No Selection',\n });\n }\n\n const sel = docState.selection;\n const req: PageTextSlice[] = [];\n\n for (let p = sel.start.page; p <= sel.end.page; p++) {\n const s = docState.slices[p];\n if (s) req.push({ pageIndex: p, charIndex: s.start, charCount: s.count });\n }\n\n if (req.length === 0) return PdfTaskHelper.resolve([] as string[]);\n\n const task = this.engine.getTextSlices(coreDoc.document, req);\n\n // Emit the text when it's retrieved\n task.wait((text) => {\n this.textRetrieved$.emit(documentId, text);\n }, ignore);\n\n return task;\n }\n\n private copyToClipboard(documentId: string) {\n // Prevent copying text without permission\n if (!this.checkPermission(documentId, PdfPermissionFlag.CopyContents)) {\n this.logger.debug(\n 'SelectionPlugin',\n 'CopyToClipboard',\n `Cannot copy to clipboard: document ${documentId} lacks CopyContents permission`,\n );\n return;\n }\n\n const text = this.getSelectedText(documentId);\n text.wait((text) => {\n this.copyToClipboard$.emit(documentId, text.join('\\n'));\n }, ignore);\n }\n}\n","import { PluginPackage } from '@embedpdf/core';\nimport { manifest, SELECTION_PLUGIN_ID } from './manifest';\nimport { SelectionPluginConfig, SelectionState } from './types';\n\nimport { SelectionPlugin } from './selection-plugin';\nimport { SelectionAction } from './actions';\nimport { selectionReducer, initialState } from './reducer';\n\nexport const SelectionPluginPackage: PluginPackage<\n SelectionPlugin,\n SelectionPluginConfig,\n SelectionState,\n SelectionAction\n> = {\n manifest,\n create: (registry, config) => new SelectionPlugin(SELECTION_PLUGIN_ID, registry, config),\n reducer: selectionReducer,\n initialState,\n};\n\nexport * from './selection-plugin';\nexport * from './types';\nexport * from './manifest';\nexport * from './utils';\n"],"names":["SELECTION_PLUGIN_ID","manifest","id","name","version","provides","requires","optional","defaultConfig","menuHeight","INIT_SELECTION_STATE","CLEANUP_SELECTION_STATE","CACHE_PAGE_GEOMETRY","SET_SELECTION","START_SELECTION","END_SELECTION","CLEAR_SELECTION","SET_RECTS","SET_SLICES","initialSelectionDocumentState","geometry","rects","slices","selection","active","selecting","initialState","documents","updateDocState","state","documentId","newDocState","selectRectsForPage","page","selectBoundingRectForPage","boundingRect","selectBoundingRectsForAllPages","out","rectMap","key","Number","bRect","push","rect","getFormattedSelectionForPage","segmentRects","length","pageIndex","getFormattedSelection","result","pages","Object","keys","map","glyphAt","geo","pt","run","runs","y","height","x","width","rel","glyphs","findIndex","g","charStart","sliceBounds","sel","start","end","from","index","lastRun","lastCharOnPage","to","rectsWithinSlice","merge","textRuns","runStart","runEnd","sIdx","Math","max","eIdx","min","minX","Infinity","maxX","minY","maxY","charCount","i","flags","origin","size","mergeAdjacentRects","rectUnion","rect1","rect2","left","top","rectIntersect","right","bottom","rectIsEmpty","getVerticalOverlap","unionRect","shouldMergeHorizontalRects","textRun1","textRun2","averageWidth1","averageWidth2","rect1Left","rect1Right","rect2Left","results","previousTextRun","currentRect","textRun","_SelectionPlugin","BasePlugin","constructor","registry","config","super","this","enabledModesPerDoc","Map","anchor","pageCallbacks","menuPlacement$","createScopedEmitter","placement","selChange$","textRetrieved$","text","copyToClipboard$","cache","beginSelection$","data","endSelection$","viewportCapability","scrollCapability","imPlugin","getPlugin","Error","interactionManagerCapability","_a","_b","coreStore","onAction","REFRESH_PAGES","action","pageIndexes","payload","tasks","getNewPageGeometryAndCache","Task","all","wait","forEach","notifyPage","ignore","_c","onViewportChange","event","recalculateMenuPlacement","mode","onDocumentLoadingStarted","dispatch","type","initSelectionState","set","Set","onDocumentClosed","cleanupSelectionState","delete","clearScope","initialize","destroy","clear","buildCapability","getDocId","getActiveDocumentId","docId","selector.getFormattedSelection","getDocumentState","p","selector.getFormattedSelectionForPage","getHighlightRectsForPage","selector.selectRectsForPage","getHighlightRects","getBoundingRectForPage","selector.selectBoundingRectForPage","getBoundingRects","selector.selectBoundingRectsForAllPages","getSelectedText","clearSelection","copyToClipboard","getState","enableForMode","modeId","get","add","isEnabledForMode","has","forDocument","createSelectionScope","bind","onCopyToClipboard","onGlobal","onSelectionChange","onTextRetrieved","onBeginSelection","onEndSelection","forScope","onMenuPlacement","listener","registerSelectionOnPage","opts","onRectsChange","docState","logger","warn","geoTask","getOrLoadGeometry","interactionScope","enabledModes","handlers","onPointerDown","point","_evt","cached","beginSelection","onPointerMove","setCursor","removeCursor","updateSelection","onPointerUp","_point","endSelection","onHandlerActiveEnd","unregisterHandlers","registerAlways","scope","abort","code","PdfErrorCode","Cancelled","message","getPlacementMetrics","vpMetrics","scrollScope","viewportRect","getRectPositionForPage","rectTopInView","scrollTop","rectBottomInView","spaceAbove","spaceBelow","clientHeight","isBottomVisible","isTopVisible","emit","bounds","tail","suggestTop","isVisible","getMetrics","head","tailMetrics","headMetrics","callback","getActiveMode","notifyAllPages","_","pageIdx","coreDoc","getCoreDocument","document","PdfTaskHelper","reject","NotFound","find","task","engine","getPageGeometry","cachePageGeometry","resolve","startSelection","a","forward","range","setSelection","updateRectsAndSlices","allRects","allSlices","sb","count","setRects","setSlices","checkPermission","PdfPermissionFlag","CopyContents","debug","Security","req","s","charIndex","getTextSlices","join","SelectionPlugin","SelectionPluginPackage","create","reducer","removed","remaining"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","sources":["../src/lib/manifest.ts","../src/lib/actions.ts","../src/lib/reducer.ts","../src/lib/selectors.ts","../src/lib/utils.ts","../src/lib/selection-plugin.ts","../src/lib/handlers/text-selection.handler.ts","../src/lib/handlers/marquee-selection.handler.ts","../src/lib/index.ts"],"sourcesContent":["import { PluginManifest } from '@embedpdf/core';\nimport { SelectionPluginConfig } from './types';\n\nexport const SELECTION_PLUGIN_ID = 'selection';\n\nexport const manifest: PluginManifest<SelectionPluginConfig> = {\n id: SELECTION_PLUGIN_ID,\n name: 'Selection Plugin',\n version: '1.0.0',\n provides: ['selection'],\n requires: ['interaction-manager'],\n optional: ['viewport', 'scroll'],\n defaultConfig: {\n menuHeight: 40,\n },\n};\n","import { Action } from '@embedpdf/core';\nimport { PdfPageGeometry, Rect } from '@embedpdf/models';\nimport { SelectionDocumentState, SelectionRangeX } from './types';\n\nexport const INIT_SELECTION_STATE = 'SELECTION/INIT_STATE';\nexport const CLEANUP_SELECTION_STATE = 'SELECTION/CLEANUP_STATE';\nexport const CACHE_PAGE_GEOMETRY = 'SELECTION/CACHE_PAGE_GEOMETRY';\nexport const SET_SELECTION = 'SELECTION/SET_SELECTION';\nexport const START_SELECTION = 'SELECTION/START_SELECTION';\nexport const END_SELECTION = 'SELECTION/END_SELECTION';\nexport const CLEAR_SELECTION = 'SELECTION/CLEAR_SELECTION';\nexport const SET_RECTS = 'SELECTION/SET_RECTS';\nexport const SET_SLICES = 'SELECTION/SET_SLICES';\nexport const RESET = 'SELECTION/RESET'; // This might be obsolete, but we'll keep it for now\n\nexport interface InitSelectionStateAction extends Action {\n type: typeof INIT_SELECTION_STATE;\n payload: {\n documentId: string;\n state: SelectionDocumentState;\n };\n}\n\nexport interface CleanupSelectionStateAction extends Action {\n type: typeof CLEANUP_SELECTION_STATE;\n payload: string; // documentId\n}\n\nexport interface CachePageGeometryAction extends Action {\n type: typeof CACHE_PAGE_GEOMETRY;\n payload: { documentId: string; page: number; geo: PdfPageGeometry };\n}\nexport interface SetSelectionAction extends Action {\n type: typeof SET_SELECTION;\n payload: { documentId: string; selection: SelectionRangeX | null };\n}\n\nexport interface StartSelectionAction extends Action {\n type: typeof START_SELECTION;\n payload: { documentId: string };\n}\n\nexport interface EndSelectionAction extends Action {\n type: typeof END_SELECTION;\n payload: { documentId: string };\n}\n\nexport interface ClearSelectionAction extends Action {\n type: typeof CLEAR_SELECTION;\n payload: { documentId: string };\n}\n\nexport interface SetRectsAction extends Action {\n type: typeof SET_RECTS;\n payload: { documentId: string; rects: Record<number, Rect[]> };\n}\n\nexport interface SetSlicesAction extends Action {\n type: typeof SET_SLICES;\n payload: { documentId: string; slices: Record<number, { start: number; count: number }> };\n}\n\nexport interface ResetAction extends Action {\n type: typeof RESET;\n payload: { documentId: string };\n}\n\nexport type SelectionAction =\n | InitSelectionStateAction\n | CleanupSelectionStateAction\n | CachePageGeometryAction\n | SetSelectionAction\n | StartSelectionAction\n | EndSelectionAction\n | ClearSelectionAction\n | SetRectsAction\n | SetSlicesAction\n | ResetAction;\n\nexport const initSelectionState = (\n documentId: string,\n state: SelectionDocumentState,\n): InitSelectionStateAction => ({\n type: INIT_SELECTION_STATE,\n payload: { documentId, state },\n});\n\nexport const cleanupSelectionState = (documentId: string): CleanupSelectionStateAction => ({\n type: CLEANUP_SELECTION_STATE,\n payload: documentId,\n});\n\nexport const cachePageGeometry = (\n documentId: string,\n page: number,\n geo: PdfPageGeometry,\n): CachePageGeometryAction => ({\n type: CACHE_PAGE_GEOMETRY,\n payload: { documentId, page, geo },\n});\n\nexport const setSelection = (\n documentId: string,\n sel: SelectionRangeX | null,\n): SetSelectionAction => ({\n type: SET_SELECTION,\n payload: { documentId, selection: sel },\n});\n\nexport const startSelection = (documentId: string): StartSelectionAction => ({\n type: START_SELECTION,\n payload: { documentId },\n});\n\nexport const endSelection = (documentId: string): EndSelectionAction => ({\n type: END_SELECTION,\n payload: { documentId },\n});\n\nexport const clearSelection = (documentId: string): ClearSelectionAction => ({\n type: CLEAR_SELECTION,\n payload: { documentId },\n});\n\nexport const setRects = (documentId: string, allRects: Record<number, Rect[]>): SetRectsAction => ({\n type: SET_RECTS,\n payload: { documentId, rects: allRects },\n});\n\nexport const setSlices = (\n documentId: string,\n slices: Record<number, { start: number; count: number }>,\n): SetSlicesAction => ({ type: SET_SLICES, payload: { documentId, slices } });\n\nexport const reset = (documentId: string): ResetAction => ({\n type: RESET,\n payload: { documentId },\n});\n","import { SelectionDocumentState, SelectionState } from './types';\nimport {\n SelectionAction,\n CACHE_PAGE_GEOMETRY,\n SET_SELECTION,\n START_SELECTION,\n END_SELECTION,\n CLEAR_SELECTION,\n RESET,\n SET_SLICES,\n SET_RECTS,\n INIT_SELECTION_STATE,\n CLEANUP_SELECTION_STATE,\n} from './actions';\n\nexport const initialSelectionDocumentState: SelectionDocumentState = {\n geometry: {},\n rects: {},\n slices: {},\n selection: null,\n active: false,\n selecting: false,\n};\n\nexport const initialState: SelectionState = {\n documents: {},\n};\n\nconst updateDocState = (\n state: SelectionState,\n documentId: string,\n newDocState: SelectionDocumentState,\n): SelectionState => ({\n ...state,\n documents: {\n ...state.documents,\n [documentId]: newDocState,\n },\n});\n\nexport const selectionReducer = (state = initialState, action: SelectionAction): SelectionState => {\n switch (action.type) {\n case INIT_SELECTION_STATE: {\n const { documentId, state: docState } = action.payload;\n return updateDocState(state, documentId, docState);\n }\n\n case CLEANUP_SELECTION_STATE: {\n const documentId = action.payload;\n const { [documentId]: removed, ...remaining } = state.documents;\n return {\n ...state,\n documents: remaining,\n };\n }\n\n case CACHE_PAGE_GEOMETRY: {\n const { documentId, page, geo } = action.payload;\n const docState = state.documents[documentId];\n if (!docState) return state;\n return updateDocState(state, documentId, {\n ...docState,\n geometry: { ...docState.geometry, [page]: geo },\n });\n }\n\n case SET_SELECTION: {\n const { documentId, selection } = action.payload;\n const docState = state.documents[documentId];\n if (!docState) return state;\n return updateDocState(state, documentId, {\n ...docState,\n selection,\n active: true,\n });\n }\n\n case START_SELECTION: {\n const { documentId } = action.payload;\n const docState = state.documents[documentId];\n if (!docState) return state;\n return updateDocState(state, documentId, {\n ...docState,\n selecting: true,\n selection: null,\n rects: {},\n });\n }\n\n case END_SELECTION: {\n const { documentId } = action.payload;\n const docState = state.documents[documentId];\n if (!docState) return state;\n return updateDocState(state, documentId, { ...docState, selecting: false });\n }\n\n case CLEAR_SELECTION: {\n const { documentId } = action.payload;\n const docState = state.documents[documentId];\n if (!docState) return state;\n return updateDocState(state, documentId, {\n ...docState,\n selecting: false,\n selection: null,\n rects: {},\n active: false,\n });\n }\n\n case SET_RECTS: {\n const { documentId, rects } = action.payload;\n const docState = state.documents[documentId];\n if (!docState) return state;\n return updateDocState(state, documentId, { ...docState, rects });\n }\n\n case SET_SLICES: {\n const { documentId, slices } = action.payload;\n const docState = state.documents[documentId];\n if (!docState) return state;\n return updateDocState(state, documentId, { ...docState, slices });\n }\n\n case RESET: {\n const { documentId } = action.payload;\n const docState = state.documents[documentId];\n if (!docState) return state;\n return updateDocState(state, documentId, initialSelectionDocumentState);\n }\n\n default:\n return state;\n }\n};\n","import { Rect, boundingRect } from '@embedpdf/models';\nimport { FormattedSelection, SelectionDocumentState } from './types';\n\nexport function selectRectsForPage(state: SelectionDocumentState, page: number) {\n return state.rects[page] ?? [];\n}\n\nexport function selectBoundingRectForPage(state: SelectionDocumentState, page: number) {\n return boundingRect(selectRectsForPage(state, page));\n}\n\nexport function selectRectsAndBoundingRectForPage(state: SelectionDocumentState, page: number) {\n return {\n rects: selectRectsForPage(state, page),\n boundingRect: selectBoundingRectForPage(state, page),\n };\n}\n\nexport function selectBoundingRectsForAllPages(state: SelectionDocumentState) {\n const out: { page: number; rect: Rect }[] = [];\n const rectMap = state.rects;\n\n for (const key in rectMap) {\n const page = Number(key);\n const bRect = boundingRect(rectMap[page]);\n if (bRect) out.push({ page, rect: bRect });\n }\n return out;\n}\n\nexport function getFormattedSelectionForPage(\n state: SelectionDocumentState,\n page: number,\n): FormattedSelection | null {\n const segmentRects = state.rects[page] || [];\n if (segmentRects.length === 0) return null;\n const boundingRect = selectBoundingRectForPage(state, page);\n if (!boundingRect) return null;\n return { pageIndex: page, rect: boundingRect, segmentRects };\n}\n\nexport function getFormattedSelection(state: SelectionDocumentState) {\n const result: FormattedSelection[] = [];\n\n // Get all pages that have rects\n const pages = Object.keys(state.rects).map(Number);\n\n for (const pageIndex of pages) {\n const segmentRects = state.rects[pageIndex] || [];\n\n if (segmentRects.length === 0) continue;\n\n // Calculate bounding rect for this page\n const boundingRect = selectBoundingRectForPage(state, pageIndex);\n\n if (boundingRect) {\n result.push({\n pageIndex,\n rect: boundingRect,\n segmentRects,\n });\n }\n }\n\n return result;\n}\n","import { PdfPageGeometry, Position, Rect } from '@embedpdf/models';\nimport { SelectionRangeX } from './types';\n\n/**\n * Hit-test helper using runs\n * @param geo - page geometry\n * @param pt - point\n * @returns glyph index\n */\nexport function glyphAt(geo: PdfPageGeometry, pt: Position) {\n for (const run of geo.runs) {\n const inRun =\n pt.y >= run.rect.y &&\n pt.y <= run.rect.y + run.rect.height &&\n pt.x >= run.rect.x &&\n pt.x <= run.rect.x + run.rect.width;\n\n if (!inRun) continue;\n\n // Simply check if the point is within any glyph's bounding box\n const rel = run.glyphs.findIndex(\n (g) => pt.x >= g.x && pt.x <= g.x + g.width && pt.y >= g.y && pt.y <= g.y + g.height,\n );\n\n if (rel !== -1) {\n return run.charStart + rel;\n }\n }\n return -1;\n}\n\n/**\n * Helper: min/max glyph indices on `page` for current sel\n * @param sel - selection range\n * @param geo - page geometry\n * @param page - page index\n * @returns { from: number; to: number } | null\n */\nexport function sliceBounds(\n sel: SelectionRangeX | null,\n geo: PdfPageGeometry | undefined,\n page: number,\n): { from: number; to: number } | null {\n if (!sel || !geo) return null;\n if (page < sel.start.page || page > sel.end.page) return null;\n\n const from = page === sel.start.page ? sel.start.index : 0;\n\n const lastRun = geo.runs[geo.runs.length - 1];\n const lastCharOnPage = lastRun.charStart + lastRun.glyphs.length - 1;\n\n const to = page === sel.end.page ? sel.end.index : lastCharOnPage;\n\n return { from, to };\n}\n\n/**\n * Helper: build rects for a slice of the page\n * @param geo - page geometry\n * @param from - from index\n * @param to - to index\n * @param merge - whether to merge adjacent rects (default: true)\n * @returns rects\n */\nexport function rectsWithinSlice(\n geo: PdfPageGeometry,\n from: number,\n to: number,\n merge: boolean = true,\n): Rect[] {\n const textRuns: TextRunInfo[] = [];\n\n for (const run of geo.runs) {\n const runStart = run.charStart;\n const runEnd = runStart + run.glyphs.length - 1;\n if (runEnd < from || runStart > to) continue;\n\n const sIdx = Math.max(from, runStart) - runStart;\n const eIdx = Math.min(to, runEnd) - runStart;\n\n let minX = Infinity,\n maxX = -Infinity;\n let minY = Infinity,\n maxY = -Infinity;\n let charCount = 0;\n\n for (let i = sIdx; i <= eIdx; i++) {\n const g = run.glyphs[i];\n if (g.flags === 2) continue; // empty glyph\n\n minX = Math.min(minX, g.x);\n maxX = Math.max(maxX, g.x + g.width);\n minY = Math.min(minY, g.y);\n maxY = Math.max(maxY, g.y + g.height);\n charCount++;\n }\n\n if (minX !== Infinity && charCount > 0) {\n textRuns.push({\n rect: {\n origin: { x: minX, y: minY },\n size: { width: maxX - minX, height: maxY - minY },\n },\n charCount,\n });\n }\n }\n\n // If merge is false, just return the individual rects\n if (!merge) {\n return textRuns.map((run) => run.rect);\n }\n\n // Otherwise merge adjacent rects\n return mergeAdjacentRects(textRuns);\n}\n\n/**\n * ============================================================================\n * Rectangle Merging Algorithm\n * ============================================================================\n *\n * The following code is adapted from Chromium's PDF text selection implementation.\n *\n * Copyright 2010 The Chromium Authors\n * Use of this source code is governed by a BSD-style license that can be\n * found in the LICENSE file: https://source.chromium.org/chromium/chromium/src/+/main:LICENSE\n *\n * Original source:\n * https://source.chromium.org/chromium/chromium/src/+/main:pdf/pdfium/pdfium_range.cc\n *\n * Adapted for TypeScript and this project's Rect/geometry types.\n */\n\n/**\n * Text run info for rect merging (similar to Chromium's ScreenRectTextRunInfo)\n */\nexport interface TextRunInfo {\n rect: Rect;\n charCount: number;\n}\n\n/**\n * Helper functions for Rect operations\n */\nexport function rectUnion(rect1: Rect, rect2: Rect): Rect {\n const left = Math.min(rect1.origin.x, rect2.origin.x);\n const top = Math.min(rect1.origin.y, rect2.origin.y);\n const right = Math.max(rect1.origin.x + rect1.size.width, rect2.origin.x + rect2.size.width);\n const bottom = Math.max(rect1.origin.y + rect1.size.height, rect2.origin.y + rect2.size.height);\n\n return {\n origin: { x: left, y: top },\n size: { width: right - left, height: bottom - top },\n };\n}\n\nexport function rectIntersect(rect1: Rect, rect2: Rect): Rect {\n const left = Math.max(rect1.origin.x, rect2.origin.x);\n const top = Math.max(rect1.origin.y, rect2.origin.y);\n const right = Math.min(rect1.origin.x + rect1.size.width, rect2.origin.x + rect2.size.width);\n const bottom = Math.min(rect1.origin.y + rect1.size.height, rect2.origin.y + rect2.size.height);\n\n const width = Math.max(0, right - left);\n const height = Math.max(0, bottom - top);\n\n return {\n origin: { x: left, y: top },\n size: { width, height },\n };\n}\n\nexport function rectIsEmpty(rect: Rect): boolean {\n return rect.size.width <= 0 || rect.size.height <= 0;\n}\n\n/**\n * Returns a ratio between [0, 1] representing vertical overlap\n */\nexport function getVerticalOverlap(rect1: Rect, rect2: Rect): number {\n if (rectIsEmpty(rect1) || rectIsEmpty(rect2)) return 0;\n\n const unionRect = rectUnion(rect1, rect2);\n\n if (unionRect.size.height === rect1.size.height || unionRect.size.height === rect2.size.height) {\n return 1.0;\n }\n\n const intersectRect = rectIntersect(rect1, rect2);\n return intersectRect.size.height / unionRect.size.height;\n}\n\n/**\n * Returns true if there is sufficient horizontal and vertical overlap\n */\nexport function shouldMergeHorizontalRects(textRun1: TextRunInfo, textRun2: TextRunInfo): boolean {\n const VERTICAL_OVERLAP_THRESHOLD = 0.8;\n const rect1 = textRun1.rect;\n const rect2 = textRun2.rect;\n\n if (getVerticalOverlap(rect1, rect2) < VERTICAL_OVERLAP_THRESHOLD) {\n return false;\n }\n\n const HORIZONTAL_WIDTH_FACTOR = 1.0;\n const averageWidth1 = (HORIZONTAL_WIDTH_FACTOR * rect1.size.width) / textRun1.charCount;\n const averageWidth2 = (HORIZONTAL_WIDTH_FACTOR * rect2.size.width) / textRun2.charCount;\n\n const rect1Left = rect1.origin.x - averageWidth1;\n const rect1Right = rect1.origin.x + rect1.size.width + averageWidth1;\n const rect2Left = rect2.origin.x - averageWidth2;\n const rect2Right = rect2.origin.x + rect2.size.width + averageWidth2;\n\n return rect1Left < rect2Right && rect1Right > rect2Left;\n}\n\n/**\n * Merge adjacent rectangles based on proximity and overlap (similar to Chromium's algorithm)\n */\nexport function mergeAdjacentRects(textRuns: TextRunInfo[]): Rect[] {\n const results: Rect[] = [];\n let previousTextRun: TextRunInfo | null = null;\n let currentRect: Rect | null = null;\n\n for (const textRun of textRuns) {\n if (previousTextRun && currentRect) {\n if (shouldMergeHorizontalRects(previousTextRun, textRun)) {\n currentRect = rectUnion(currentRect, textRun.rect);\n } else {\n results.push(currentRect);\n currentRect = textRun.rect;\n }\n } else {\n currentRect = textRun.rect;\n }\n previousTextRun = textRun;\n }\n\n if (currentRect && !rectIsEmpty(currentRect)) {\n results.push(currentRect);\n }\n\n return results;\n}\n","import {\n BasePlugin,\n Listener,\n PluginRegistry,\n REFRESH_PAGES,\n createScopedEmitter,\n} from '@embedpdf/core';\nimport {\n PdfPageGeometry,\n Rect,\n PdfTask,\n PdfTaskHelper,\n PdfErrorCode,\n PdfPermissionFlag,\n ignore,\n PageTextSlice,\n Task,\n Position,\n} from '@embedpdf/models';\nimport {\n InteractionManagerCapability,\n InteractionManagerPlugin,\n PointerEventHandlersWithLifecycle,\n EmbedPdfPointerEvent,\n} from '@embedpdf/plugin-interaction-manager';\nimport { ViewportCapability, ViewportMetrics, ViewportPlugin } from '@embedpdf/plugin-viewport';\nimport { ScrollCapability, ScrollPlugin } from '@embedpdf/plugin-scroll';\n\nimport {\n cachePageGeometry,\n setSelection,\n SelectionAction,\n endSelection,\n startSelection,\n clearSelection,\n setRects,\n setSlices,\n initSelectionState,\n cleanupSelectionState,\n} from './actions';\nimport { initialSelectionDocumentState } from './reducer';\nimport * as selector from './selectors';\nimport {\n SelectionCapability,\n SelectionPluginConfig,\n SelectionRangeX,\n SelectionState,\n RegisterSelectionOnPageOptions,\n RegisterMarqueeOnPageOptions,\n SelectionRectsCallback,\n SelectionScope,\n SelectionChangeEvent,\n TextRetrievedEvent,\n CopyToClipboardEvent,\n BeginSelectionEvent,\n EndSelectionEvent,\n SelectionDocumentState,\n SelectionMenuPlacement,\n SelectionMenuPlacementEvent,\n EnableForModeOptions,\n MarqueeChangeEvent,\n MarqueeEndEvent,\n MarqueeScopeEvent,\n MarqueeEndScopeEvent,\n} from './types';\nimport { sliceBounds, rectsWithinSlice } from './utils';\nimport { createTextSelectionHandler } from './handlers/text-selection.handler';\nimport { createMarqueeSelectionHandler } from './handlers/marquee-selection.handler';\n\nexport class SelectionPlugin extends BasePlugin<\n SelectionPluginConfig,\n SelectionCapability,\n SelectionState,\n SelectionAction\n> {\n static readonly id = 'selection' as const;\n\n /** Modes that should trigger text-selection logic, per document (mode -> config) */\n private enabledModesPerDoc = new Map<string, Map<string, EnableForModeOptions>>();\n\n /* interactive state, per document */\n private selecting = new Map<string, boolean>();\n private anchor = new Map<string, { page: number; index: number } | undefined>();\n\n /** Marquee state tracking, per document */\n private marqueeEnabled = new Map<string, boolean>();\n private marqueePage = new Map<string, number>();\n\n /** Page callbacks for rect updates, per document */\n private pageCallbacks = new Map<string, Map<number, (data: SelectionRectsCallback) => void>>();\n\n private readonly menuPlacement$ = createScopedEmitter<\n SelectionMenuPlacement | null,\n SelectionMenuPlacementEvent,\n string\n >((documentId, placement) => ({ documentId, placement }));\n private readonly selChange$ = createScopedEmitter<\n SelectionRangeX | null,\n SelectionChangeEvent,\n string\n >((documentId, selection) => ({ documentId, selection }));\n private readonly textRetrieved$ = createScopedEmitter<string[], TextRetrievedEvent, string>(\n (documentId, text) => ({ documentId, text }),\n );\n private readonly copyToClipboard$ = createScopedEmitter<string, CopyToClipboardEvent, string>(\n (documentId, text) => ({ documentId, text }),\n { cache: false },\n );\n private readonly beginSelection$ = createScopedEmitter<\n { page: number; index: number },\n BeginSelectionEvent,\n string\n >((documentId, data) => ({ documentId, page: data.page, index: data.index }), { cache: false });\n private readonly endSelection$ = createScopedEmitter<void, EndSelectionEvent, string>(\n (documentId) => ({ documentId }),\n { cache: false },\n );\n\n // Marquee selection emitters\n private readonly marqueeChange$ = createScopedEmitter<\n MarqueeScopeEvent,\n MarqueeChangeEvent,\n string\n >((documentId, data) => ({ documentId, pageIndex: data.pageIndex, rect: data.rect }), {\n cache: false,\n });\n private readonly marqueeEnd$ = createScopedEmitter<MarqueeEndScopeEvent, MarqueeEndEvent, string>(\n (documentId, data) => ({ documentId, pageIndex: data.pageIndex, rect: data.rect }),\n {\n cache: false,\n },\n );\n\n private interactionManagerCapability: InteractionManagerCapability;\n private viewportCapability: ViewportCapability | null = null;\n private scrollCapability: ScrollCapability | null = null;\n\n private readonly menuHeight: number;\n private readonly config: SelectionPluginConfig;\n\n constructor(id: string, registry: PluginRegistry, config: SelectionPluginConfig) {\n super(id, registry);\n this.config = config;\n this.menuHeight = config.menuHeight ?? 40;\n\n const imPlugin = registry.getPlugin<InteractionManagerPlugin>('interaction-manager');\n if (!imPlugin) {\n throw new Error('SelectionPlugin: InteractionManagerPlugin is required.');\n }\n this.interactionManagerCapability = imPlugin.provides();\n this.viewportCapability = registry.getPlugin<ViewportPlugin>('viewport')?.provides() ?? null;\n this.scrollCapability = registry.getPlugin<ScrollPlugin>('scroll')?.provides() ?? null;\n\n this.coreStore.onAction(REFRESH_PAGES, (action) => {\n const { documentId, pageIndexes } = action.payload;\n const tasks = pageIndexes.map((pageIndex) =>\n this.getNewPageGeometryAndCache(documentId, pageIndex),\n );\n Task.all(tasks).wait(() => {\n // Notify affected pages about geometry updates\n pageIndexes.forEach((pageIndex) => {\n this.notifyPage(documentId, pageIndex);\n });\n }, ignore);\n });\n\n this.viewportCapability?.onViewportChange(\n (event) => {\n this.recalculateMenuPlacement(event.documentId);\n },\n { mode: 'throttle', wait: 100 },\n );\n }\n\n /* ── life-cycle ────────────────────────────────────────── */\n protected override onDocumentLoadingStarted(documentId: string): void {\n this.dispatch(initSelectionState(documentId, initialSelectionDocumentState));\n this.enabledModesPerDoc.set(\n documentId,\n new Map<string, EnableForModeOptions>([['pointerMode', { showRects: true }]]),\n );\n this.pageCallbacks.set(documentId, new Map());\n this.selecting.set(documentId, false);\n this.anchor.set(documentId, undefined);\n\n // Initialize marquee state based on config\n const marqueeConfig = this.config.marquee;\n this.marqueeEnabled.set(documentId, marqueeConfig?.enabled !== false);\n }\n\n protected override onDocumentClosed(documentId: string): void {\n this.dispatch(cleanupSelectionState(documentId));\n this.enabledModesPerDoc.delete(documentId);\n this.pageCallbacks.delete(documentId);\n this.selecting.delete(documentId);\n this.anchor.delete(documentId);\n this.marqueeEnabled.delete(documentId);\n this.marqueePage.delete(documentId);\n this.selChange$.clearScope(documentId);\n this.textRetrieved$.clearScope(documentId);\n this.copyToClipboard$.clearScope(documentId);\n this.beginSelection$.clearScope(documentId);\n this.endSelection$.clearScope(documentId);\n this.menuPlacement$.clearScope(documentId);\n this.marqueeChange$.clearScope(documentId);\n this.marqueeEnd$.clearScope(documentId);\n }\n\n async initialize() {}\n async destroy() {\n this.selChange$.clear();\n this.textRetrieved$.clear();\n this.copyToClipboard$.clear();\n this.beginSelection$.clear();\n this.endSelection$.clear();\n this.menuPlacement$.clear();\n this.marqueeChange$.clear();\n this.marqueeEnd$.clear();\n super.destroy();\n }\n\n /* ── capability exposed to UI / other plugins ─────────── */\n buildCapability(): SelectionCapability {\n const getDocId = (documentId?: string) => documentId ?? this.getActiveDocumentId();\n\n return {\n // Active document operations\n getFormattedSelection: (docId) =>\n selector.getFormattedSelection(this.getDocumentState(getDocId(docId))),\n getFormattedSelectionForPage: (p, docId) =>\n selector.getFormattedSelectionForPage(this.getDocumentState(getDocId(docId)), p),\n getHighlightRectsForPage: (p, docId) =>\n selector.selectRectsForPage(this.getDocumentState(getDocId(docId)), p),\n getHighlightRects: (docId) => this.getDocumentState(getDocId(docId)).rects,\n getBoundingRectForPage: (p, docId) =>\n selector.selectBoundingRectForPage(this.getDocumentState(getDocId(docId)), p),\n getBoundingRects: (docId) =>\n selector.selectBoundingRectsForAllPages(this.getDocumentState(getDocId(docId))),\n getSelectedText: (docId) => this.getSelectedText(getDocId(docId)),\n clear: (docId) => this.clearSelection(getDocId(docId)),\n copyToClipboard: (docId) => this.copyToClipboard(getDocId(docId)),\n getState: (docId) => this.getDocumentState(getDocId(docId)),\n enableForMode: (modeId, options, docId) =>\n this.enabledModesPerDoc.get(getDocId(docId))?.set(modeId, { showRects: true, ...options }),\n isEnabledForMode: (modeId, docId) =>\n this.enabledModesPerDoc.get(getDocId(docId))?.has(modeId) ?? false,\n\n // Marquee selection\n setMarqueeEnabled: (enabled, docId) => this.setMarqueeEnabled(getDocId(docId), enabled),\n isMarqueeEnabled: (docId) => this.isMarqueeEnabled(getDocId(docId)),\n\n // Document-scoped operations\n forDocument: this.createSelectionScope.bind(this),\n\n // Global events\n onCopyToClipboard: this.copyToClipboard$.onGlobal,\n onSelectionChange: this.selChange$.onGlobal,\n onTextRetrieved: this.textRetrieved$.onGlobal,\n onBeginSelection: this.beginSelection$.onGlobal,\n onEndSelection: this.endSelection$.onGlobal,\n\n // Marquee selection events\n onMarqueeChange: this.marqueeChange$.onGlobal,\n onMarqueeEnd: this.marqueeEnd$.onGlobal,\n };\n }\n\n private createSelectionScope(documentId: string): SelectionScope {\n return {\n getFormattedSelection: () =>\n selector.getFormattedSelection(this.getDocumentState(documentId)),\n getFormattedSelectionForPage: (p) =>\n selector.getFormattedSelectionForPage(this.getDocumentState(documentId), p),\n getHighlightRectsForPage: (p) =>\n selector.selectRectsForPage(this.getDocumentState(documentId), p),\n getHighlightRects: () => this.getDocumentState(documentId).rects,\n getBoundingRectForPage: (p) =>\n selector.selectBoundingRectForPage(this.getDocumentState(documentId), p),\n getBoundingRects: () =>\n selector.selectBoundingRectsForAllPages(this.getDocumentState(documentId)),\n getSelectedText: () => this.getSelectedText(documentId),\n clear: () => this.clearSelection(documentId),\n copyToClipboard: () => this.copyToClipboard(documentId),\n getState: () => this.getDocumentState(documentId),\n setMarqueeEnabled: (enabled) => this.setMarqueeEnabled(documentId, enabled),\n isMarqueeEnabled: () => this.isMarqueeEnabled(documentId),\n onSelectionChange: this.selChange$.forScope(documentId),\n onTextRetrieved: this.textRetrieved$.forScope(documentId),\n onCopyToClipboard: this.copyToClipboard$.forScope(documentId),\n onBeginSelection: this.beginSelection$.forScope(documentId),\n onEndSelection: this.endSelection$.forScope(documentId),\n onMarqueeChange: this.marqueeChange$.forScope(documentId),\n onMarqueeEnd: this.marqueeEnd$.forScope(documentId),\n };\n }\n\n private getDocumentState(documentId: string): SelectionDocumentState {\n const state = this.state.documents[documentId];\n if (!state) {\n throw new Error(`Selection state not found for document: ${documentId}`);\n }\n return state;\n }\n\n /**\n * Subscribe to menu placement changes for a specific document\n * @param documentId - The document ID to subscribe to\n * @param listener - Callback function that receives placement updates\n * @returns Unsubscribe function\n */\n public onMenuPlacement(\n documentId: string,\n listener: (placement: SelectionMenuPlacement | null) => void,\n ) {\n return this.menuPlacement$.forScope(documentId)(listener);\n }\n\n /**\n * Register text selection on a page. Uses `registerAlways` so any plugin\n * can enable text selection for their mode via `enableForMode()`.\n */\n public registerSelectionOnPage(opts: RegisterSelectionOnPageOptions) {\n const { documentId, pageIndex, onRectsChange } = opts;\n const docState = this.state.documents[documentId];\n\n if (!docState) {\n this.logger.warn(\n 'SelectionPlugin',\n 'RegisterFailed',\n `Cannot register selection on page ${pageIndex} for document ${documentId}: document state not initialized.`,\n );\n return () => {};\n }\n\n // Track this callback for the page\n this.pageCallbacks.get(documentId)?.set(pageIndex, onRectsChange);\n\n const geoTask = this.getOrLoadGeometry(documentId, pageIndex);\n const interactionScope = this.interactionManagerCapability.forDocument(documentId);\n const enabledModes = this.enabledModesPerDoc.get(documentId);\n\n // Send initial state\n onRectsChange({\n rects: selector.selectRectsForPage(docState, pageIndex),\n boundingRect: selector.selectBoundingRectForPage(docState, pageIndex),\n });\n\n // Create text selection handler\n const textHandler = createTextSelectionHandler({\n getGeometry: () => this.getDocumentState(documentId).geometry[pageIndex],\n isEnabled: (modeId) => enabledModes?.has(modeId) ?? false,\n onBegin: (g) => this.beginSelection(documentId, pageIndex, g),\n onUpdate: (g) => this.updateSelection(documentId, pageIndex, g),\n onEnd: () => this.endSelection(documentId),\n onClear: () => this.clearSelection(documentId),\n isSelecting: () => this.selecting.get(documentId) ?? false,\n setCursor: (cursor) =>\n cursor\n ? interactionScope.setCursor('selection-text', cursor, 10)\n : interactionScope.removeCursor('selection-text'),\n });\n\n // Register text selection with registerAlways - any plugin can enable it for their mode\n const unregisterHandlers = this.interactionManagerCapability.registerAlways({\n scope: { type: 'page', documentId, pageIndex },\n handlers: textHandler,\n });\n\n // Return cleanup function\n return () => {\n unregisterHandlers();\n this.pageCallbacks.get(documentId)?.delete(pageIndex);\n geoTask.abort({ code: PdfErrorCode.Cancelled, message: 'Cleanup' });\n };\n }\n\n /**\n * Register marquee selection on a page. Uses `registerHandlers` with `pointerMode`\n * only - marquee selection is only active in the default pointer mode.\n */\n public registerMarqueeOnPage(opts: RegisterMarqueeOnPageOptions) {\n const { documentId, pageIndex, scale, onRectChange } = opts;\n const docState = this.state.documents[documentId];\n\n if (!docState) {\n this.logger.warn(\n 'SelectionPlugin',\n 'RegisterMarqueeFailed',\n `Cannot register marquee on page ${pageIndex} for document ${documentId}: document state not initialized.`,\n );\n return () => {};\n }\n\n // Get page size from core state (same pattern as ZoomPlugin)\n const coreDoc = this.coreState.core.documents[documentId];\n if (!coreDoc || !coreDoc.document) {\n this.logger.warn(\n 'SelectionPlugin',\n 'DocumentNotFound',\n `Cannot register marquee on page ${pageIndex}: document not found`,\n );\n return () => {};\n }\n\n const page = coreDoc.document.pages[pageIndex];\n if (!page) {\n this.logger.warn(\n 'SelectionPlugin',\n 'PageNotFound',\n `Cannot register marquee on page ${pageIndex}: page not found`,\n );\n return () => {};\n }\n\n const pageSize = page.size;\n const minDragPx = this.config.marquee?.minDragPx ?? 5;\n\n // Create marquee selection handler\n const marqueeHandler = createMarqueeSelectionHandler({\n pageSize,\n scale,\n minDragPx,\n isEnabled: () => this.marqueeEnabled.get(documentId) !== false,\n onBegin: (pos) => this.beginMarquee(documentId, pageIndex, pos),\n onChange: (rect) => {\n this.updateMarquee(documentId, pageIndex, rect);\n onRectChange(rect);\n },\n onEnd: (rect) => {\n this.endMarquee(documentId, pageIndex, rect);\n onRectChange(null);\n },\n onCancel: () => {\n this.cancelMarquee(documentId);\n onRectChange(null);\n },\n });\n\n // Register marquee ONLY for pointerMode\n const unregisterHandlers = this.interactionManagerCapability.registerHandlers({\n documentId,\n pageIndex,\n modeId: 'pointerMode',\n handlers: marqueeHandler,\n });\n\n return unregisterHandlers;\n }\n\n /**\n * Helper to calculate viewport relative metrics for a page rect.\n * Returns null if the rect cannot be converted to viewport space.\n */\n private getPlacementMetrics(\n documentId: string,\n pageIndex: number,\n rect: Rect,\n vpMetrics: ViewportMetrics,\n ) {\n // 1. Convert Page Coordinate -> Viewport Coordinate\n // We use the scroll capability to handle rotation, scale, and scroll offset automatically\n const scrollScope = this.scrollCapability?.forDocument(documentId);\n const viewportRect = scrollScope?.getRectPositionForPage(pageIndex, rect);\n\n if (!viewportRect) return null;\n\n // 2. Calculate relative positions\n const rectTopInView = viewportRect.origin.y - vpMetrics.scrollTop;\n const rectBottomInView = viewportRect.origin.y + viewportRect.size.height - vpMetrics.scrollTop;\n\n return {\n pageIndex,\n rect, // Original Page Rect\n spaceAbove: rectTopInView,\n spaceBelow: vpMetrics.clientHeight - rectBottomInView,\n isBottomVisible: rectBottomInView > 0 && rectBottomInView <= vpMetrics.clientHeight,\n isTopVisible: rectTopInView >= 0 && rectTopInView < vpMetrics.clientHeight,\n };\n }\n\n private recalculateMenuPlacement(documentId: string) {\n const docState = this.state.documents[documentId];\n if (!docState) return;\n\n // Only show menu when not actively selecting\n if (docState.selecting || docState.selection === null) {\n this.menuPlacement$.emit(documentId, null);\n return;\n }\n\n // 1. Get Bounding Rects for all pages involved in selection.\n // These are implicitly sorted by pageIndex because updateRectsAndSlices\n // populates the map in ascending page order.\n const bounds = selector.selectBoundingRectsForAllPages(docState);\n\n if (bounds.length === 0) {\n this.menuPlacement$.emit(documentId, null);\n return;\n }\n\n const tail = bounds[bounds.length - 1];\n\n // Fallback: If viewport/scroll plugins are missing, always default to bottom of the last rect\n if (!this.viewportCapability || !this.scrollCapability) {\n this.menuPlacement$.emit(documentId, {\n pageIndex: tail.page,\n rect: tail.rect,\n spaceAbove: 0,\n spaceBelow: Infinity, // Pretend we have infinite space below to prevent auto-flipping\n suggestTop: false,\n isVisible: true, // Assume visible\n });\n return;\n }\n\n // Use document-scoped viewport to get metrics for this specific document\n const viewportScope = this.viewportCapability.forDocument(documentId);\n const vpMetrics = viewportScope.getMetrics();\n\n // 2. Calculate metrics for Head (Start) and Tail (End)\n const head = bounds[0];\n\n const tailMetrics = this.getPlacementMetrics(documentId, tail.page, tail.rect, vpMetrics);\n const headMetrics = this.getPlacementMetrics(documentId, head.page, head.rect, vpMetrics);\n\n // 3. Apply Heuristic Logic\n\n // Priority A: Bottom of Tail (Standard selection end)\n // If the bottom of the selection is visible and we have space below.\n if (tailMetrics) {\n if (tailMetrics.isBottomVisible && tailMetrics.spaceBelow > this.menuHeight) {\n this.menuPlacement$.emit(documentId, {\n ...tailMetrics,\n suggestTop: false,\n isVisible: true,\n });\n return;\n }\n }\n\n // Priority B: Top of Head (Selection start, if user scrolled up)\n // If the top of the start selection is visible, put the menu there.\n if (headMetrics) {\n if (headMetrics.isTopVisible) {\n this.menuPlacement$.emit(documentId, {\n ...headMetrics,\n suggestTop: true,\n isVisible: true,\n });\n return;\n }\n }\n\n // Priority C: Fallback to Tail Bottom if visible (even if tight space)\n // It's better to stick to the cursor end than jump to the top if space is tight.\n if (tailMetrics && tailMetrics.isBottomVisible) {\n this.menuPlacement$.emit(documentId, {\n ...tailMetrics,\n suggestTop: false,\n isVisible: true,\n });\n return;\n }\n\n // If completely off screen\n this.menuPlacement$.emit(documentId, null);\n }\n\n private notifyPage(documentId: string, pageIndex: number) {\n const callback = this.pageCallbacks.get(documentId)?.get(pageIndex);\n if (callback) {\n const docState = this.getDocumentState(documentId);\n const mode = this.interactionManagerCapability.forDocument(documentId).getActiveMode();\n const modeConfig = this.enabledModesPerDoc.get(documentId)?.get(mode);\n\n // Show rects if mode is enabled and showRects is not explicitly false\n const shouldShowRects = modeConfig && modeConfig.showRects !== false;\n\n if (shouldShowRects) {\n callback({\n rects: selector.selectRectsForPage(docState, pageIndex),\n boundingRect: selector.selectBoundingRectForPage(docState, pageIndex),\n });\n } else {\n callback({ rects: [], boundingRect: null });\n }\n }\n }\n\n private notifyAllPages(documentId: string) {\n this.pageCallbacks.get(documentId)?.forEach((_, pageIndex) => {\n this.notifyPage(documentId, pageIndex);\n });\n }\n\n private getNewPageGeometryAndCache(\n documentId: string,\n pageIdx: number,\n ): PdfTask<PdfPageGeometry> {\n const coreDoc = this.getCoreDocument(documentId);\n if (!coreDoc || !coreDoc.document)\n return PdfTaskHelper.reject({ code: PdfErrorCode.NotFound, message: 'Doc Not Found' });\n\n const page = coreDoc.document.pages.find((p) => p.index === pageIdx)!;\n const task = this.engine.getPageGeometry(coreDoc.document, page);\n task.wait((geo) => {\n this.dispatch(cachePageGeometry(documentId, pageIdx, geo));\n }, ignore);\n return task;\n }\n\n /* ── geometry cache ───────────────────────────────────── */\n private getOrLoadGeometry(documentId: string, pageIdx: number): PdfTask<PdfPageGeometry> {\n const cached = this.getDocumentState(documentId).geometry[pageIdx];\n if (cached) return PdfTaskHelper.resolve(cached);\n\n return this.getNewPageGeometryAndCache(documentId, pageIdx);\n }\n\n /* ── selection state updates ───────────────────────────── */\n private beginSelection(documentId: string, page: number, index: number) {\n this.selecting.set(documentId, true);\n this.anchor.set(documentId, { page, index });\n this.dispatch(startSelection(documentId));\n this.beginSelection$.emit(documentId, { page, index });\n this.recalculateMenuPlacement(documentId);\n }\n\n private endSelection(documentId: string) {\n this.selecting.set(documentId, false);\n this.anchor.set(documentId, undefined);\n this.dispatch(endSelection(documentId));\n this.endSelection$.emit(documentId);\n this.recalculateMenuPlacement(documentId);\n }\n\n private clearSelection(documentId: string) {\n this.selecting.set(documentId, false);\n this.anchor.set(documentId, undefined);\n this.dispatch(clearSelection(documentId));\n this.selChange$.emit(documentId, null);\n this.menuPlacement$.emit(documentId, null);\n this.notifyAllPages(documentId);\n }\n\n private updateSelection(documentId: string, page: number, index: number) {\n if (!this.selecting.get(documentId) || !this.anchor.get(documentId)) return;\n\n const a = this.anchor.get(documentId)!;\n const forward = page > a.page || (page === a.page && index >= a.index);\n\n const start = forward ? a : { page, index };\n const end = forward ? { page, index } : a;\n\n const range = { start, end };\n this.dispatch(setSelection(documentId, range));\n this.updateRectsAndSlices(documentId, range);\n this.selChange$.emit(documentId, range);\n\n // Notify affected pages\n for (let p = range.start.page; p <= range.end.page; p++) {\n this.notifyPage(documentId, p);\n }\n }\n\n private updateRectsAndSlices(documentId: string, range: SelectionRangeX) {\n const docState = this.getDocumentState(documentId);\n const allRects: Record<number, Rect[]> = {};\n const allSlices: Record<number, { start: number; count: number }> = {};\n\n for (let p = range.start.page; p <= range.end.page; p++) {\n const geo = docState.geometry[p];\n const sb = sliceBounds(range, geo, p);\n if (!sb) continue;\n\n allRects[p] = rectsWithinSlice(geo!, sb.from, sb.to);\n allSlices[p] = { start: sb.from, count: sb.to - sb.from + 1 };\n }\n\n this.dispatch(setRects(documentId, allRects));\n this.dispatch(setSlices(documentId, allSlices));\n }\n\n private getSelectedText(documentId: string): PdfTask<string[]> {\n // Prevent extracting text without permission\n if (!this.checkPermission(documentId, PdfPermissionFlag.CopyContents)) {\n this.logger.debug(\n 'SelectionPlugin',\n 'GetSelectedText',\n `Cannot get selected text: document ${documentId} lacks CopyContents permission`,\n );\n return PdfTaskHelper.reject({\n code: PdfErrorCode.Security,\n message: 'Document lacks CopyContents permission',\n });\n }\n\n const coreDoc = this.getCoreDocument(documentId);\n const docState = this.getDocumentState(documentId);\n\n if (!coreDoc?.document || !docState.selection) {\n return PdfTaskHelper.reject({\n code: PdfErrorCode.NotFound,\n message: 'Doc Not Found or No Selection',\n });\n }\n\n const sel = docState.selection;\n const req: PageTextSlice[] = [];\n\n for (let p = sel.start.page; p <= sel.end.page; p++) {\n const s = docState.slices[p];\n if (s) req.push({ pageIndex: p, charIndex: s.start, charCount: s.count });\n }\n\n if (req.length === 0) return PdfTaskHelper.resolve([] as string[]);\n\n const task = this.engine.getTextSlices(coreDoc.document, req);\n\n // Emit the text when it's retrieved\n task.wait((text) => {\n this.textRetrieved$.emit(documentId, text);\n }, ignore);\n\n return task;\n }\n\n private copyToClipboard(documentId: string) {\n // Prevent copying text without permission\n if (!this.checkPermission(documentId, PdfPermissionFlag.CopyContents)) {\n this.logger.debug(\n 'SelectionPlugin',\n 'CopyToClipboard',\n `Cannot copy to clipboard: document ${documentId} lacks CopyContents permission`,\n );\n return;\n }\n\n const text = this.getSelectedText(documentId);\n text.wait((text) => {\n this.copyToClipboard$.emit(documentId, text.join('\\n'));\n }, ignore);\n }\n\n /* ── marquee selection state updates ─────────────────────── */\n private beginMarquee(documentId: string, pageIndex: number, _startPos: Position) {\n this.marqueePage.set(documentId, pageIndex);\n }\n\n private updateMarquee(documentId: string, pageIndex: number, rect: Rect) {\n this.marqueeChange$.emit(documentId, { pageIndex, rect });\n }\n\n private endMarquee(documentId: string, pageIndex: number, rect: Rect) {\n this.marqueeEnd$.emit(documentId, { pageIndex, rect });\n this.marqueeChange$.emit(documentId, { pageIndex, rect: null });\n this.marqueePage.delete(documentId);\n }\n\n private cancelMarquee(documentId: string) {\n const pageIndex = this.marqueePage.get(documentId);\n if (pageIndex !== undefined) {\n this.marqueeChange$.emit(documentId, { pageIndex, rect: null });\n this.marqueePage.delete(documentId);\n }\n }\n\n private setMarqueeEnabled(documentId: string, enabled: boolean) {\n this.marqueeEnabled.set(documentId, enabled);\n }\n\n private isMarqueeEnabled(documentId: string): boolean {\n return this.marqueeEnabled.get(documentId) !== false;\n }\n}\n","import { PdfPageGeometry, Position } from '@embedpdf/models';\nimport {\n EmbedPdfPointerEvent,\n PointerEventHandlersWithLifecycle,\n} from '@embedpdf/plugin-interaction-manager';\nimport { glyphAt } from '../utils';\n\nexport interface TextSelectionHandlerOptions {\n /** Returns the page geometry, or undefined if not loaded yet */\n getGeometry: () => PdfPageGeometry | undefined;\n /** Check if selection is enabled for this mode */\n isEnabled: (modeId: string) => boolean;\n /** Called when selection begins on a glyph */\n onBegin: (glyphIndex: number) => void;\n /** Called when selection updates to a new glyph */\n onUpdate: (glyphIndex: number) => void;\n /** Called when selection ends (pointer up) */\n onEnd: () => void;\n /** Called to clear the current selection */\n onClear: () => void;\n /** Returns whether text selection is currently in progress */\n isSelecting: () => boolean;\n /** Set or remove the text cursor */\n setCursor: (cursor: string | null) => void;\n}\n\n/**\n * Creates a text selection handler that manages pointer-based text selection.\n *\n * When text is hit on pointerdown, the handler calls `stopImmediatePropagation()`\n * to prevent other handlers (e.g., marquee) from processing the event.\n */\nexport function createTextSelectionHandler(\n opts: TextSelectionHandlerOptions,\n): PointerEventHandlersWithLifecycle<EmbedPdfPointerEvent> {\n return {\n onPointerDown: (point: Position, evt, modeId) => {\n if (!opts.isEnabled(modeId)) return;\n\n // Clear the current selection\n opts.onClear();\n\n // Get geometry from cache\n const geo = opts.getGeometry();\n if (geo) {\n const g = glyphAt(geo, point);\n if (g !== -1) {\n opts.onBegin(g);\n // Prevent other handlers (e.g., marquee) from processing this event\n evt.stopImmediatePropagation();\n }\n }\n },\n\n onPointerMove: (point: Position, evt, modeId) => {\n if (!opts.isEnabled(modeId)) return;\n\n // Get cached geometry\n const geo = opts.getGeometry();\n if (geo) {\n const g = glyphAt(geo, point);\n\n // Update cursor based on whether we're over text\n opts.setCursor(g !== -1 ? 'text' : null);\n\n // Update selection if we're actively selecting\n if (opts.isSelecting() && g !== -1) {\n opts.onUpdate(g);\n // Prevent other handlers from processing this event\n evt.stopImmediatePropagation();\n }\n }\n },\n\n onPointerUp: (_point: Position, _evt, modeId) => {\n if (!opts.isEnabled(modeId)) return;\n opts.onEnd();\n },\n\n onHandlerActiveEnd: (modeId) => {\n if (!opts.isEnabled(modeId)) return;\n opts.onClear();\n },\n };\n}\n","import { Position, Rect, Size } from '@embedpdf/models';\nimport { clamp } from '@embedpdf/core';\nimport {\n EmbedPdfPointerEvent,\n PointerEventHandlersWithLifecycle,\n} from '@embedpdf/plugin-interaction-manager';\n\nexport interface MarqueeSelectionHandlerOptions {\n /** The page size for clamping */\n pageSize: Size;\n /** Current scale factor for min drag threshold calculation */\n scale: number;\n /** Minimum drag distance in pixels before considering it a marquee (default: 5) */\n minDragPx?: number;\n /** Check if marquee selection is enabled for this mode */\n isEnabled: (modeId: string) => boolean;\n /** Called when marquee selection begins */\n onBegin: (startPos: Position) => void;\n /** Called during drag with the current marquee rect */\n onChange: (rect: Rect) => void;\n /** Called when marquee selection completes (drag was large enough) */\n onEnd: (rect: Rect) => void;\n /** Called when marquee selection is cancelled (drag too small or cancelled) */\n onCancel: () => void;\n}\n\n/**\n * Creates a marquee selection handler that allows users to drag a selection rectangle.\n *\n * This handler is meant to be combined with the text selection handler using `mergeHandlers`.\n * The text selection handler should have higher priority and call `stopImmediatePropagation()`\n * when text is hit, preventing this marquee handler from activating.\n */\nexport function createMarqueeSelectionHandler(\n opts: MarqueeSelectionHandlerOptions,\n): PointerEventHandlersWithLifecycle<EmbedPdfPointerEvent> {\n const { pageSize, scale, minDragPx = 5 } = opts;\n\n let start: Position | null = null;\n let last: Rect | null = null;\n\n return {\n onPointerDown: (pos, evt, modeId) => {\n if (!opts.isEnabled(modeId)) return;\n\n start = pos;\n last = { origin: { x: pos.x, y: pos.y }, size: { width: 0, height: 0 } };\n opts.onBegin(pos);\n evt.setPointerCapture?.();\n },\n\n onPointerMove: (pos, _evt, modeId) => {\n if (!start || !opts.isEnabled(modeId)) return;\n\n // Clamp position to page bounds\n const x = clamp(pos.x, 0, pageSize.width);\n const y = clamp(pos.y, 0, pageSize.height);\n\n // Build the marquee rect (handle negative drag directions)\n last = {\n origin: { x: Math.min(start.x, x), y: Math.min(start.y, y) },\n size: { width: Math.abs(x - start.x), height: Math.abs(y - start.y) },\n };\n\n opts.onChange(last);\n },\n\n onPointerUp: (_pos, evt, modeId) => {\n if (!opts.isEnabled(modeId)) return;\n\n if (last && start) {\n // Only commit if the drag was large enough\n const dragPx = Math.max(last.size.width, last.size.height) * scale;\n if (dragPx > minDragPx) {\n opts.onEnd(last);\n } else {\n opts.onCancel();\n }\n }\n\n start = null;\n last = null;\n evt.releasePointerCapture?.();\n },\n\n onPointerCancel: (_pos, evt, modeId) => {\n if (!opts.isEnabled(modeId)) return;\n\n start = null;\n last = null;\n opts.onCancel();\n evt.releasePointerCapture?.();\n },\n };\n}\n","import { PluginPackage } from '@embedpdf/core';\nimport { manifest, SELECTION_PLUGIN_ID } from './manifest';\nimport { SelectionPluginConfig, SelectionState } from './types';\n\nimport { SelectionPlugin } from './selection-plugin';\nimport { SelectionAction } from './actions';\nimport { selectionReducer, initialState } from './reducer';\n\nexport const SelectionPluginPackage: PluginPackage<\n SelectionPlugin,\n SelectionPluginConfig,\n SelectionState,\n SelectionAction\n> = {\n manifest,\n create: (registry, config) => new SelectionPlugin(SELECTION_PLUGIN_ID, registry, config),\n reducer: selectionReducer,\n initialState,\n};\n\nexport * from './selection-plugin';\nexport * from './types';\nexport * from './manifest';\nexport * from './utils';\n"],"names":["SELECTION_PLUGIN_ID","manifest","id","name","version","provides","requires","optional","defaultConfig","menuHeight","INIT_SELECTION_STATE","CLEANUP_SELECTION_STATE","CACHE_PAGE_GEOMETRY","SET_SELECTION","START_SELECTION","END_SELECTION","CLEAR_SELECTION","SET_RECTS","SET_SLICES","initialSelectionDocumentState","geometry","rects","slices","selection","active","selecting","initialState","documents","updateDocState","state","documentId","newDocState","selectRectsForPage","page","selectBoundingRectForPage","boundingRect","selectBoundingRectsForAllPages","out","rectMap","key","Number","bRect","push","rect","getFormattedSelectionForPage","segmentRects","length","pageIndex","getFormattedSelection","result","pages","Object","keys","map","glyphAt","geo","pt","run","runs","y","height","x","width","rel","glyphs","findIndex","g","charStart","sliceBounds","sel","start","end","from","index","lastRun","lastCharOnPage","to","rectsWithinSlice","merge","textRuns","runStart","runEnd","sIdx","Math","max","eIdx","min","minX","Infinity","maxX","minY","maxY","charCount","i","flags","origin","size","mergeAdjacentRects","rectUnion","rect1","rect2","left","top","rectIntersect","right","bottom","rectIsEmpty","getVerticalOverlap","unionRect","shouldMergeHorizontalRects","textRun1","textRun2","averageWidth1","averageWidth2","rect1Left","rect1Right","rect2Left","results","previousTextRun","currentRect","textRun","_SelectionPlugin","BasePlugin","constructor","registry","config","super","this","enabledModesPerDoc","Map","anchor","marqueeEnabled","marqueePage","pageCallbacks","menuPlacement$","createScopedEmitter","placement","selChange$","textRetrieved$","text","copyToClipboard$","cache","beginSelection$","data","endSelection$","marqueeChange$","marqueeEnd$","viewportCapability","scrollCapability","imPlugin","getPlugin","Error","interactionManagerCapability","_a","_b","coreStore","onAction","REFRESH_PAGES","action","pageIndexes","payload","tasks","getNewPageGeometryAndCache","Task","all","wait","forEach","notifyPage","ignore","_c","onViewportChange","event","recalculateMenuPlacement","mode","onDocumentLoadingStarted","dispatch","type","initSelectionState","set","showRects","marqueeConfig","marquee","enabled","onDocumentClosed","cleanupSelectionState","delete","clearScope","initialize","destroy","clear","buildCapability","getDocId","getActiveDocumentId","docId","selector.getFormattedSelection","getDocumentState","p","selector.getFormattedSelectionForPage","getHighlightRectsForPage","selector.selectRectsForPage","getHighlightRects","getBoundingRectForPage","selector.selectBoundingRectForPage","getBoundingRects","selector.selectBoundingRectsForAllPages","getSelectedText","clearSelection","copyToClipboard","getState","enableForMode","modeId","options","get","isEnabledForMode","has","setMarqueeEnabled","isMarqueeEnabled","forDocument","createSelectionScope","bind","onCopyToClipboard","onGlobal","onSelectionChange","onTextRetrieved","onBeginSelection","onEndSelection","onMarqueeChange","onMarqueeEnd","forScope","onMenuPlacement","listener","registerSelectionOnPage","opts","onRectsChange","docState","logger","warn","geoTask","getOrLoadGeometry","interactionScope","enabledModes","textHandler","onPointerDown","point","evt","isEnabled","onClear","getGeometry","onBegin","stopImmediatePropagation","onPointerMove","setCursor","isSelecting","onUpdate","onPointerUp","_point","_evt","onEnd","onHandlerActiveEnd","createTextSelectionHandler","beginSelection","updateSelection","endSelection","cursor","removeCursor","unregisterHandlers","registerAlways","scope","handlers","abort","code","PdfErrorCode","Cancelled","message","registerMarqueeOnPage","scale","onRectChange","coreDoc","coreState","core","document","marqueeHandler","pageSize","minDragPx","last","pos","setPointerCapture","call","clamp","abs","onChange","_pos","onCancel","releasePointerCapture","onPointerCancel","createMarqueeSelectionHandler","beginMarquee","updateMarquee","endMarquee","cancelMarquee","registerHandlers","getPlacementMetrics","vpMetrics","scrollScope","viewportRect","getRectPositionForPage","rectTopInView","scrollTop","rectBottomInView","spaceAbove","spaceBelow","clientHeight","isBottomVisible","isTopVisible","emit","bounds","tail","suggestTop","isVisible","getMetrics","head","tailMetrics","headMetrics","callback","getActiveMode","modeConfig","notifyAllPages","_","pageIdx","getCoreDocument","PdfTaskHelper","reject","NotFound","find","task","engine","getPageGeometry","cachePageGeometry","cached","resolve","startSelection","a","forward","range","setSelection","updateRectsAndSlices","allRects","allSlices","sb","count","setRects","setSlices","checkPermission","PdfPermissionFlag","CopyContents","debug","Security","req","s","charIndex","getTextSlices","join","_startPos","SelectionPlugin","SelectionPluginPackage","create","reducer","removed","remaining"],"mappings":"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"}