@embedpdf/plugin-selection 2.15.0 → 3.0.0-next.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.
- package/LICENSE +192 -21
- package/dist/index.cjs +575 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +167 -0
- package/dist/index.d.ts +167 -1
- package/dist/index.js +549 -1508
- package/dist/index.js.map +1 -1
- package/package.json +21 -62
- package/dist/lib/actions.d.ts +0 -103
- package/dist/lib/handlers/index.d.ts +0 -2
- package/dist/lib/handlers/marquee-selection.handler.d.ts +0 -30
- package/dist/lib/handlers/text-selection.handler.d.ts +0 -52
- package/dist/lib/index.d.ts +0 -9
- package/dist/lib/manifest.d.ts +0 -4
- package/dist/lib/reducer.d.ts +0 -5
- package/dist/lib/selection-plugin.d.ts +0 -121
- package/dist/lib/selectors.d.ts +0 -14
- package/dist/lib/types.d.ts +0 -268
- package/dist/lib/utils.d.ts +0 -108
- package/dist/preact/adapter.d.ts +0 -4
- package/dist/preact/core.d.ts +0 -1
- package/dist/preact/index.cjs +0 -2
- package/dist/preact/index.cjs.map +0 -1
- package/dist/preact/index.d.ts +0 -1
- package/dist/preact/index.js +0 -218
- package/dist/preact/index.js.map +0 -1
- package/dist/preact/interaction-manager.d.ts +0 -1
- package/dist/preact/utils.d.ts +0 -1
- package/dist/react/adapter.d.ts +0 -2
- package/dist/react/core.d.ts +0 -1
- package/dist/react/index.cjs +0 -2
- package/dist/react/index.cjs.map +0 -1
- package/dist/react/index.d.ts +0 -1
- package/dist/react/index.js +0 -217
- package/dist/react/index.js.map +0 -1
- package/dist/react/interaction-manager.d.ts +0 -1
- package/dist/react/utils.d.ts +0 -1
- package/dist/shared/components/copy-to-clipboard.d.ts +0 -1
- package/dist/shared/components/index.d.ts +0 -4
- package/dist/shared/components/marquee-selection.d.ts +0 -36
- package/dist/shared/components/selection-layer.d.ts +0 -29
- package/dist/shared/components/text-selection.d.ts +0 -21
- package/dist/shared/hooks/index.d.ts +0 -1
- package/dist/shared/hooks/use-selection.d.ts +0 -11
- package/dist/shared/index.d.ts +0 -5
- package/dist/shared/types.d.ts +0 -7
- package/dist/shared-preact/components/copy-to-clipboard.d.ts +0 -1
- package/dist/shared-preact/components/index.d.ts +0 -4
- package/dist/shared-preact/components/marquee-selection.d.ts +0 -36
- package/dist/shared-preact/components/selection-layer.d.ts +0 -29
- package/dist/shared-preact/components/text-selection.d.ts +0 -21
- package/dist/shared-preact/hooks/index.d.ts +0 -1
- package/dist/shared-preact/hooks/use-selection.d.ts +0 -11
- package/dist/shared-preact/index.d.ts +0 -5
- package/dist/shared-preact/types.d.ts +0 -7
- package/dist/shared-react/components/copy-to-clipboard.d.ts +0 -1
- package/dist/shared-react/components/index.d.ts +0 -4
- package/dist/shared-react/components/marquee-selection.d.ts +0 -36
- package/dist/shared-react/components/selection-layer.d.ts +0 -29
- package/dist/shared-react/components/text-selection.d.ts +0 -21
- package/dist/shared-react/hooks/index.d.ts +0 -1
- package/dist/shared-react/hooks/use-selection.d.ts +0 -11
- package/dist/shared-react/index.d.ts +0 -5
- package/dist/shared-react/types.d.ts +0 -7
- package/dist/svelte/components/CopyToClipboard.svelte.d.ts +0 -3
- package/dist/svelte/components/MarqueeSelection.svelte.d.ts +0 -27
- package/dist/svelte/components/SelectionLayer.svelte.d.ts +0 -32
- package/dist/svelte/components/TextSelection.svelte.d.ts +0 -22
- package/dist/svelte/components/index.d.ts +0 -4
- package/dist/svelte/hooks/index.d.ts +0 -1
- package/dist/svelte/hooks/use-selection.svelte.d.ts +0 -19
- package/dist/svelte/index.cjs +0 -2
- package/dist/svelte/index.cjs.map +0 -1
- package/dist/svelte/index.d.ts +0 -5
- package/dist/svelte/index.js +0 -359
- package/dist/svelte/index.js.map +0 -1
- package/dist/svelte/types.d.ts +0 -7
- package/dist/vue/components/copy-to-clipboard.vue.d.ts +0 -3
- package/dist/vue/components/index.d.ts +0 -4
- package/dist/vue/components/marquee-selection.vue.d.ts +0 -29
- package/dist/vue/components/selection-layer.vue.d.ts +0 -60
- package/dist/vue/components/text-selection.vue.d.ts +0 -51
- package/dist/vue/hooks/index.d.ts +0 -1
- package/dist/vue/hooks/use-selection.d.ts +0 -11
- package/dist/vue/index.cjs +0 -2
- package/dist/vue/index.cjs.map +0 -1
- package/dist/vue/index.d.ts +0 -5
- package/dist/vue/index.js +0 -311
- package/dist/vue/index.js.map +0 -1
- package/dist/vue/types.d.ts +0 -7
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,575 @@
|
|
|
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",r="SELECTION/CACHE_PAGE_GEOMETRY",a="SELECTION/SET_SELECTION",c="SELECTION/START_SELECTION",l="SELECTION/END_SELECTION",d="SELECTION/CLEAR_SELECTION",h="SELECTION/SET_RECTS",g="SELECTION/SET_SLICES",u="SELECTION/EVICT_PAGE_GEOMETRY",p=(e,t)=>({type:a,payload:{documentId:e,selection:t}}),m=e=>({type:c,payload:{documentId:e}}),f=e=>({type:l,payload:{documentId:e}}),S=(e,t)=>({type:h,payload:{documentId:e,rects:t}}),y=(e,t)=>({type:g,payload:{documentId:e,slices:t}}),x={geometry:{},rects:{},slices:{},selection:null,active:!1,selecting:!1},C={documents:{}},b=(e,t,n)=>({...e,documents:{...e.documents,[t]:n}});function M(e,t){return e.rects[t]??[]}function P(e,n){return t.boundingRect(M(e,n))}function I(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 E(e,t){const n=e.rects[t]||[];if(0===n.length)return null;const i=P(e,t);return i?{pageIndex:t,rect:i,segmentRects:n}:null}function v(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=P(e,i);o&&t.push({pageIndex:i,rect:o,segmentRects:n})}return t}function T(e,t,n=1.5){for(const a of e.runs){if(!(t.y>=a.rect.y&&t.y<=a.rect.y+a.rect.height&&t.x>=a.rect.x&&t.x<=a.rect.x+a.rect.width))continue;const e=a.glyphs.findIndex(e=>{const n=e.tightX??e.x,i=e.tightY??e.y,o=e.tightWidth??e.width,s=e.tightHeight??e.height;return t.x>=n&&t.x<=n+o&&t.y>=i&&t.y<=i+s});if(-1!==e)return a.charStart+e}if(n<=0)return-1;const i=function(e,t){let n=0,i=0;for(const o of e.runs)for(const e of o.glyphs)2!==e.flags&&(n+=e.height,i++);return 0===i?0:n/i*t}(e,n),o=i/2;let s=-1,r=1/0;for(const a of e.runs)if(!(t.y<a.rect.y-o||t.y>a.rect.y+a.rect.height+o||t.x<a.rect.x-o||t.x>a.rect.x+a.rect.width+o))for(let e=0;e<a.glyphs.length;e++){const n=a.glyphs[e];if(2===n.flags)continue;const i=n.tightX??n.x,c=n.tightY??n.y,l=n.tightWidth??n.width,d=n.tightHeight??n.height,h=i-o,g=i+l+o,u=c-o,p=c+d+o;if(t.x<h||t.x>g||t.y<u||t.y>p)continue;const m=Math.min(Math.abs(t.x-i),Math.abs(t.x-(i+l)))+Math.min(Math.abs(t.y-c),Math.abs(t.y-(c+d)));m<r&&(r=m,s=a.charStart+e)}return s}function w(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 $(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 r=Math.max(t,e)-e,a=Math.min(n,i)-e;let c=1/0,l=-1/0,d=1/0,h=-1/0,g=0,u=0,p=-1/0;const m=()=>{c!==1/0&&g>0&&o.push({rect:{origin:{x:c,y:d},size:{width:l-c,height:h-d}},charCount:g,fontSize:s.fontSize}),c=1/0,l=-1/0,d=1/0,h=-1/0,g=0,u=0,p=-1/0};for(let t=r;t<=a;t++){const e=s.glyphs[t];if(2!==e.flags){if(g>0&&p>-1/0){const t=Math.abs(e.x-p),n=u/g;n>0&&t>2.5*n&&m()}c=Math.min(c,e.x),l=Math.max(l,e.x+e.width),d=Math.min(d,e.y),h=Math.max(h,e.y+e.height),g++,u+=e.width,p=e.x+e.width}}m()}return i?A(o):o.map(e=>e.rect)}function D(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 q(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 R(e){return e.size.width<=0||e.size.height<=0}function k(e,t){if(R(e)||R(t))return 0;const n=D(e,t);if(n.size.height===e.size.height||n.size.height===t.size.height)return 1;return q(e,t).size.height/n.size.height}function z(e,t){if(null!=e.fontSize&&null!=t.fontSize&&e.fontSize>0&&t.fontSize>0){if(Math.max(e.fontSize,t.fontSize)/Math.min(e.fontSize,t.fontSize)>1.5)return!1}const n=e.rect,i=t.rect;if(k(n,i)<.8)return!1;const o=1*n.size.width/e.charCount,s=1*i.size.width/t.charCount,r=n.origin.x-o,a=n.origin.x+n.size.width+o,c=i.origin.x-s;return r<i.origin.x+i.size.width+s&&a>c}function A(e){const t=[];let n=null,i=null;for(const o of e)n&&i?z(n,o)?i=D(i,o.rect):(t.push(i),i=o.rect):i=o.rect,n=o;return i&&!R(i)&&t.push(i),t}function N(e,t){for(let n=0;n<e.runs.length;n++){const i=e.runs[n],o=t-i.charStart;if(o>=0&&o<i.glyphs.length)return{runIdx:n,localIdx:o}}return null}function G(e){return 1===e||2===e}function O(e,t){if(!N(e,t))return null;const n=function(e){if(0===e.runs.length)return 0;const t=e.runs[e.runs.length-1];return t.charStart+t.glyphs.length}(e);if(0===n)return null;let i=t;for(;i>0;){const t=N(e,i-1);if(!t)break;if(G(e.runs[t.runIdx].glyphs[t.localIdx].flags))break;i--}let o=t;for(;o<n-1;){const t=N(e,o+1);if(!t)break;if(G(e.runs[t.runIdx].glyphs[t.localIdx].flags))break;o++}return{from:i,to:o}}function F(e,t){const n=N(e,t);if(!n)return null;const i=e.runs[n.runIdx],o=i.rect.y,s=i.rect.y+i.rect.height;let r=i.charStart,a=i.charStart+i.glyphs.length-1;for(let c=n.runIdx-1;c>=0;c--){const t=e.runs[c];if(!H(t)){if(!L(t.rect.y,t.rect.y+t.rect.height,o,s))break;r=t.charStart}}for(let c=n.runIdx+1;c<e.runs.length;c++){const t=e.runs[c];if(!H(t)){if(!L(t.rect.y,t.rect.y+t.rect.height,o,s))break;a=t.charStart+t.glyphs.length-1}}return{from:r,to:a}}function H(e){return 0===e.rect.width&&0===e.rect.height}function L(e,t,n,i){const o=Math.max(t,i)-Math.min(e,n),s=Math.max(0,Math.min(t,i)-Math.max(e,n));return 0!==o&&s/o>=.5}const B=class extends e.BasePlugin{constructor(n,i,o){var s,r,a;super(n,i),this.enabledModesPerDoc=new Map,this.selecting=new Map,this.anchor=new Map,this.hasTextAnchor=new Map,this.marqueePage=new Map,this.pageCallbacks=new Map,this.geoAccessOrder=new Map,this.menuPlacement$=e.createScopedEmitter((e,t)=>({documentId:e,placement:t})),this.selChange$=e.createScopedEmitter((e,t)=>({documentId:e,selection:t,modeId:this.interactionManagerCapability.forDocument(e).getActiveMode()})),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,modeId:t.modeId}),{cache:!1}),this.endSelection$=e.createScopedEmitter((e,t)=>({documentId:e,modeId:t.modeId}),{cache:!1}),this.marqueeChange$=e.createScopedEmitter((e,t)=>({documentId:e,pageIndex:t.pageIndex,rect:t.rect,modeId:t.modeId}),{cache:!1}),this.marqueeEnd$=e.createScopedEmitter((e,t)=>({documentId:e,pageIndex:t.pageIndex,rect:t.rect,modeId:t.modeId}),{cache:!1}),this.emptySpaceClick$=e.createScopedEmitter((e,t)=>({documentId:e,pageIndex:t.pageIndex,modeId:t.modeId}),{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==(r=i.getPlugin("scroll"))?void 0:r.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==(a=this.viewportCapability)||a.onViewportChange(e=>{this.recalculateMenuPlacement(e.documentId)},{mode:"throttle",wait:100})}onDocumentLoadingStarted(e){var t;this.dispatch(((e,t)=>({type:o,payload:{documentId:e,state:t}}))(e,x));const n=!1!==(null==(t=this.config.marquee)?void 0:t.enabled);this.enabledModesPerDoc.set(e,new Map([["pointerMode",{enableSelection:!0,showSelectionRects:!0,enableMarquee:n,showMarqueeRects:!0}]])),this.pageCallbacks.set(e,new Map),this.geoAccessOrder.set(e,[]),this.selecting.set(e,!1),this.anchor.set(e,void 0),this.hasTextAnchor.set(e,!1)}onDocumentClosed(e){this.dispatch((e=>({type:s,payload:e}))(e)),this.enabledModesPerDoc.delete(e),this.pageCallbacks.delete(e),this.geoAccessOrder.delete(e),this.selecting.delete(e),this.hasTextAnchor.delete(e),this.anchor.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),this.emptySpaceClick$.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(),this.emptySpaceClick$.clear(),super.destroy()}buildCapability(){const e=e=>e??this.getActiveDocumentId();return{getFormattedSelection:t=>v(this.getDocumentState(e(t))),getFormattedSelectionForPage:(t,n)=>E(this.getDocumentState(e(n)),t),getHighlightRectsForPage:(t,n)=>M(this.getDocumentState(e(n)),t),getHighlightRects:t=>this.getDocumentState(e(t)).rects,getBoundingRectForPage:(t,n)=>P(this.getDocumentState(e(n)),t),getBoundingRects:t=>I(this.getDocumentState(e(t))),getSelectedText:t=>this.getSelectedText(e(t)),setSelection:(t,n)=>this.applySelection(e(n),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,{...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,onEmptySpaceClick:this.emptySpaceClick$.onGlobal}}createSelectionScope(e){return{getFormattedSelection:()=>v(this.getDocumentState(e)),getFormattedSelectionForPage:t=>E(this.getDocumentState(e),t),getHighlightRectsForPage:t=>M(this.getDocumentState(e),t),getHighlightRects:()=>this.getDocumentState(e).rects,getBoundingRectForPage:t=>P(this.getDocumentState(e),t),getBoundingRects:()=>I(this.getDocumentState(e)),getSelectedText:()=>this.getSelectedText(e),setSelection:t=>this.applySelection(e,t),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),onEmptySpaceClick:this.emptySpaceClick$.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,r=this.state.documents[i];if(!r)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 a=this.getOrLoadGeometry(i,o),c=this.interactionManagerCapability.forDocument(i),l=this.enabledModesPerDoc.get(i);s({rects:M(r,o),boundingRect:P(r,o)}),a.wait(e=>{const t=this.getDocumentState(i),n=t.selection;if(!n||o<n.start.page||o>n.end.page)return;const s=w(n,e,o);if(!s)return;const r=$(e,s.from,s.to);this.dispatch(S(i,{...t.rects,[o]:r})),this.dispatch(y(i,{...t.slices,[o]:{start:s.from,count:s.to-s.from+1}})),this.notifyPage(i,o)},t.ignore);const d=function(e){const t=e.minDragDistance??3,n=e.toleranceFactor??1.5;let i=null,o=null,s=!1,r=0;function a(){var t;i=null,o=null,s=!1,null==(t=e.setHasTextAnchor)||t.call(e,!1)}return{onPointerDown:(t,a,c)=>{var l,d;if(a.target===a.currentTarget&&(null==(l=e.onEmptySpaceClick)||l.call(e,c)),!e.isEnabled(c))return;const h=Date.now();(0===r||h-r>=500)&&e.onClear(c);const g=e.getGeometry();if(!g)return;const u=T(g,t,n);-1!==u&&(i=u,o=t,s=!1,null==(d=e.setHasTextAnchor)||d.call(e,!0))},onPointerMove:(r,a,c)=>{if(!e.isEnabled(c))return;const l=e.getGeometry();if(!l)return;const d=T(l,r,n);if(e.setCursor(-1!==d?"text":null),null!==i&&o&&!s){const n=r.x-o.x,a=r.y-o.y;return void(Math.sqrt(n*n+a*a)>=t&&(s=!0,e.onBegin(i,c),-1!==d&&e.onUpdate(d,c)))}e.isSelecting()&&-1!==d&&e.onUpdate(d,c)},onPointerUp:(t,n,i)=>{e.isEnabled(i)?(s&&e.onEnd(i),a()):a()},onDoubleClick:(t,i,o)=>{var s;if(!e.isEnabled(o))return;const a=e.getGeometry();if(!a)return;const c=T(a,t,n);-1!==c&&(r=Date.now(),null==(s=e.onWordSelect)||s.call(e,c,o))},onClick:(t,i,o)=>{var s;if(!e.isEnabled(o))return;if(0===r)return;if(Date.now()-r>500)return void(r=0);r=0;const a=e.getGeometry();if(!a)return;const c=T(a,t,n);-1!==c&&(null==(s=e.onLineSelect)||s.call(e,c,o))},onHandlerActiveEnd:t=>{a(),e.isEnabled(t)&&e.onClear(t)}}}({getGeometry:()=>this.getDocumentState(i).geometry[o],isEnabled:e=>{const t=null==l?void 0:l.get(e);return!!t&&!1!==t.enableSelection},onBegin:(e,t)=>this.beginSelection(i,o,e,t),onUpdate:(e,t)=>this.updateSelection(i,o,e,t),onEnd:e=>this.endSelection(i,e),onClear:e=>this.clearSelection(i,e),isSelecting:()=>this.selecting.get(i)??!1,setCursor:e=>e?c.setCursor("selection-text",e,10):c.removeCursor("selection-text"),onEmptySpaceClick:e=>this.emptySpaceClick$.emit(i,{pageIndex:o,modeId:e}),onWordSelect:(e,t)=>this.selectWord(i,o,e,t),onLineSelect:(e,t)=>this.selectLine(i,o,e,t),setHasTextAnchor:e=>this.hasTextAnchor.set(i,e),minDragDistance:this.config.minSelectionDragDistance,toleranceFactor:this.config.toleranceFactor}),h=this.interactionManagerCapability.registerAlways({scope:{type:"page",documentId:i,pageIndex:o},handlers:d});return()=>{var e;h(),null==(e=this.pageCallbacks.get(i))||e.delete(o),a.abort({code:t.PdfErrorCode.Cancelled,message:"Cleanup"})}}registerMarqueeOnPage(t){var n;const{documentId:i,pageIndex:o,scale:s,onRectChange:r}=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 a=this.coreState.core.documents[i];if(!a||!a.document)return this.logger.warn("SelectionPlugin","DocumentNotFound",`Cannot register marquee on page ${o}: document not found`),()=>{};const c=a.document.pages[o];if(!c)return this.logger.warn("SelectionPlugin","PageNotFound",`Cannot register marquee on page ${o}: page not found`),()=>{};const l=c.size,d=(null==(n=this.config.marquee)?void 0:n.minDragPx)??5,h=()=>{var e;const t=this.interactionManagerCapability.forDocument(i).getActiveMode(),n=null==(e=this.enabledModesPerDoc.get(i))?void 0:e.get(t);return!1!==(null==n?void 0:n.showMarqueeRects)},g=function(t){const{pageSize:n,scale:i,minDragPx:o=5}=t;let s=null,r=null;return{onPointerDown:(e,n,i)=>{var o,a;t.isEnabled(i)&&((null==(o=t.isTextSelecting)?void 0:o.call(t))||(s=e,r={origin:{x:e.x,y:e.y},size:{width:0,height:0}},t.onBegin(e,i),null==(a=n.setPointerCapture)||a.call(n)))},onPointerMove:(i,o,a)=>{if(!s||!t.isEnabled(a))return;const c=e.clamp(i.x,0,n.width),l=e.clamp(i.y,0,n.height);r={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(r,a)},onPointerUp:(e,n,a)=>{var c;t.isEnabled(a)&&(r&&s&&(Math.max(r.size.width,r.size.height)*i>o?t.onEnd(r,a):t.onCancel(a)),s=null,r=null,null==(c=n.releasePointerCapture)||c.call(n))},onPointerCancel:(e,n,i)=>{var o;t.isEnabled(i)&&(s=null,r=null,t.onCancel(i),null==(o=n.releasePointerCapture)||o.call(n))}}}({pageSize:l,scale:s,minDragPx:d,isEnabled:e=>{var t;const n=null==(t=this.enabledModesPerDoc.get(i))?void 0:t.get(e);return!0===(null==n?void 0:n.enableMarquee)},isTextSelecting:()=>(this.selecting.get(i)??!1)||(this.hasTextAnchor.get(i)??!1),onBegin:(e,t)=>this.beginMarquee(i,o,e,t),onChange:(e,t)=>{this.updateMarquee(i,o,e,t),r(h()?e:null)},onEnd:(e,t)=>{this.endMarquee(i,o,e,t),r(null)},onCancel:e=>{this.cancelMarquee(i,e),r(null)}});return this.interactionManagerCapability.registerAlways({scope:{type:"page",documentId:i,pageIndex:o},handlers:g})}getPlacementMetrics(e,t,n,i){var o;const s=null==(o=this.scrollCapability)?void 0:o.forDocument(e),r=null==s?void 0:s.getRectPositionForPage(t,n);if(!r)return null;const a=r.origin.y-i.scrollTop,c=r.origin.y+r.size.height-i.scrollTop;return{pageIndex:t,rect:n,spaceAbove:a,spaceBelow:i.clientHeight-c,isBottomVisible:c>0&&c<=i.clientHeight,isTopVisible:a>=0&&a<i.clientHeight}}emitMenuPlacement(e,t){this.menuPlacement$.emit(e,t),t?this.interactionManagerCapability.claimPageActivity(e,"selection-menu",t.pageIndex):this.interactionManagerCapability.releasePageActivity(e,"selection-menu")}recalculateMenuPlacement(e){const t=this.state.documents[e];if(!t)return;if(t.selecting||null===t.selection)return void this.emitMenuPlacement(e,null);const n=I(t);if(0===n.length)return void this.emitMenuPlacement(e,null);const i=n[n.length-1];if(!this.viewportCapability||!this.scrollCapability)return void this.emitMenuPlacement(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],r=this.getPlacementMetrics(e,i.page,i.rect,o),a=this.getPlacementMetrics(e,s.page,s.rect,o);r&&r.isBottomVisible&&r.spaceBelow>this.menuHeight?this.emitMenuPlacement(e,{...r,suggestTop:!1,isVisible:!0}):a&&a.isTopVisible?this.emitMenuPlacement(e,{...a,suggestTop:!0,isVisible:!0}):r&&r.isBottomVisible?this.emitMenuPlacement(e,{...r,suggestTop:!1,isVisible:!0}):this.emitMenuPlacement(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(),r=null==(i=this.enabledModesPerDoc.get(e))?void 0:i.get(s);o(r&&!1!==(r.showSelectionRects??r.showRects)?{rects:M(n,t),boundingRect:P(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:r,payload:{documentId:e,page:t,geo:n}}))(e,n,t)),this.touchGeometry(e,n)},t.ignore),s}getOrLoadGeometry(e,n){const i=this.getDocumentState(e).geometry[n];return i?(this.touchGeometry(e,n),t.PdfTaskHelper.resolve(i)):this.getNewPageGeometryAndCache(e,n)}touchGeometry(e,t){const n=this.geoAccessOrder.get(e);if(!n)return;const i=n.indexOf(t);i>-1&&n.splice(i,1),n.push(t),this.evictGeometryIfNeeded(e)}evictGeometryIfNeeded(e){const t=this.config.maxCachedGeometries??50,n=this.geoAccessOrder.get(e);if(!n||n.length<=t)return;const i=this.pageCallbacks.get(e),o=[];for(;n.length-o.length>t;){const e=n.find(e=>!o.includes(e)&&!(null==i?void 0:i.has(e)));if(void 0===e)break;o.push(e)}if(0!==o.length){for(const e of o){const t=n.indexOf(e);t>-1&&n.splice(t,1)}this.dispatch(((e,t)=>({type:u,payload:{documentId:e,pages:t}}))(e,o))}}beginSelection(e,t,n,i){this.selecting.set(e,!0),this.anchor.set(e,{page:t,index:n}),this.dispatch(m(e)),this.beginSelection$.emit(e,{page:t,index:n,modeId:i}),this.recalculateMenuPlacement(e)}endSelection(e,t){this.selecting.set(e,!1),this.anchor.set(e,void 0),this.dispatch(f(e)),this.endSelection$.emit(e,{modeId:t}),this.recalculateMenuPlacement(e)}clearSelection(e,t){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.emitMenuPlacement(e,null),this.notifyAllPages(e)}applySelection(e,n){if(!this.state.documents[e])return t.PdfTaskHelper.reject({code:t.PdfErrorCode.NotFound,message:`Selection state not found for document: ${e}`});if(null===n)return this.clearSelection(e),t.PdfTaskHelper.resolve(void 0);const i=this.validateRange(e,n);if(i)return t.PdfTaskHelper.reject(i);const o=this.normalizeRange(n),s=[];for(let t=o.start.page;t<=o.end.page;t++)s.push(t);const r=s.map(t=>this.getOrLoadGeometry(e,t)),a=t.PdfTaskHelper.create();return t.Task.all(r).wait(()=>{if(!this.state.documents[e])return void a.reject({code:t.PdfErrorCode.NotFound,message:`Selection state not found for document: ${e}`});const n=this.clampRangeToGeometry(e,o);if(!n)return void a.reject({code:t.PdfErrorCode.NotFound,message:`Cannot apply selection: page(s) ${o.start.page}-${o.end.page} have no text geometry`});this.selecting.set(e,!1),this.anchor.set(e,void 0);const i=Object.keys(this.getDocumentState(e).rects).map(Number);this.dispatch(m(e)),this.dispatch(p(e,n)),this.updateRectsAndSlices(e,n),this.dispatch(f(e)),this.selChange$.emit(e,n);const s=new Set(i);for(let e=n.start.page;e<=n.end.page;e++)s.add(e);s.forEach(t=>this.notifyPage(e,t)),this.recalculateMenuPlacement(e),a.resolve(void 0)},e=>a.reject(e.reason)),a}validateRange(e,n){var i,o;const s=e=>!!e&&"object"==typeof e&&Number.isInteger(e.page)&&Number.isInteger(e.index);if(!n||"object"!=typeof n||!n.start||!n.end)return{code:t.PdfErrorCode.Unknown,message:"Invalid selection range: expected { start, end }"};if(!s(n.start)||!s(n.end))return{code:t.PdfErrorCode.Unknown,message:"Invalid selection range: start/end must have integer page and index"};if(n.start.index<0||n.end.index<0)return{code:t.PdfErrorCode.Unknown,message:"Invalid selection range: glyph index cannot be negative"};const r=(null==(o=null==(i=this.getCoreDocument(e))?void 0:i.document)?void 0:o.pageCount)??0,a=Math.min(n.start.page,n.end.page),c=Math.max(n.start.page,n.end.page);return a<0||c>=r?{code:t.PdfErrorCode.NotFound,message:`Invalid selection range: page ${a<0?a:c} out of bounds [0, ${r})`}:null}clampRangeToGeometry(e,t){const n=this.getDocumentState(e),i=e=>{const t=n.geometry[e];if(!t||0===t.runs.length)return-1;const i=t.runs[t.runs.length-1];return i.charStart+i.glyphs.length-1},o=i(t.start.page),s=i(t.end.page);if(o<0&&s<0)return null;const r=(e,t)=>t<0?0:Math.min(Math.max(e,0),t);return{start:{page:t.start.page,index:r(t.start.index,o)},end:{page:t.end.page,index:r(t.end.index,s)}}}normalizeRange(e){const{start:t,end:n}=e;return n.page>t.page||n.page===t.page&&n.index>=t.index?{start:t,end:n}:{start:n,end:t}}selectWord(e,t,n,i){const o=this.getDocumentState(e).geometry[t];if(!o)return;const s=O(o,n);s&&this.applyInstantSelection(e,t,s.from,s.to,i)}selectLine(e,t,n,i){const o=this.getDocumentState(e).geometry[t];if(!o)return;const s=F(o,n);s&&this.applyInstantSelection(e,t,s.from,s.to,i)}applyInstantSelection(e,t,n,i,o){const s={start:{page:t,index:n},end:{page:t,index:i}};this.selecting.set(e,!1),this.anchor.set(e,void 0),this.dispatch(m(e)),this.dispatch(p(e,s)),this.updateRectsAndSlices(e,s),this.dispatch(f(e)),this.selChange$.emit(e,s),this.beginSelection$.emit(e,{page:t,index:n,modeId:o}),this.endSelection$.emit(e,{modeId:o});for(let r=s.start.page;r<=s.end.page;r++)this.notifyPage(e,r);this.recalculateMenuPlacement(e)}updateSelection(e,t,n,i){if(!this.selecting.get(e)||!this.anchor.get(e))return;const o=this.anchor.get(e),s=t>o.page||t===o.page&&n>=o.index,r={start:s?o:{page:t,index:n},end:s?{page:t,index:n}:o};this.dispatch(p(e,r)),this.updateRectsAndSlices(e,r),this.selChange$.emit(e,r);for(let a=r.start.page;a<=r.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],r=w(t,e,s);r&&(i[s]=$(e,r.from,r.to),o[s]={start:r.from,count:r.to-r.from+1})}this.dispatch(S(e,i)),this.dispatch(y(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 r=this.engine.getTextSlices(n.document,s);return r.wait(t=>{this.textRetrieved$.emit(e,t)},t.ignore),r}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,i){this.marqueePage.set(e,t)}updateMarquee(e,t,n,i){this.marqueeChange$.emit(e,{pageIndex:t,rect:n,modeId:i})}endMarquee(e,t,n,i){this.marqueeEnd$.emit(e,{pageIndex:t,rect:n,modeId:i}),this.marqueeChange$.emit(e,{pageIndex:t,rect:null,modeId:i}),this.marqueePage.delete(e)}cancelMarquee(e,t){const n=this.marqueePage.get(e);void 0!==n&&(this.marqueeChange$.emit(e,{pageIndex:n,rect:null,modeId:t}),this.marqueePage.delete(e))}setMarqueeEnabled(e,t){const n=this.enabledModesPerDoc.get(e);if(!n)return;const i=n.get("pointerMode");i?i.enableMarquee=t:t&&n.set("pointerMode",{enableMarquee:!0})}isMarqueeEnabled(e){var t;const n=null==(t=this.enabledModesPerDoc.get(e))?void 0:t.get("pointerMode");return!1!==(null==n?void 0:n.enableMarquee)}};B.id="selection";let j=B;const _={manifest:i,create:(e,t)=>new j(n,e,t),reducer:(e=C,t)=>{switch(t.type){case o:{const{documentId:n,state:i}=t.payload;return b(e,n,i)}case s:{const n=t.payload,{[n]:i,...o}=e.documents;return{...e,documents:o}}case r:{const{documentId:n,page:i,geo:o}=t.payload,s=e.documents[n];return s?b(e,n,{...s,geometry:{...s.geometry,[i]:o}}):e}case a:{const{documentId:n,selection:i}=t.payload,o=e.documents[n];return o?b(e,n,{...o,selection:i,active:!0}):e}case c:{const{documentId:n}=t.payload,i=e.documents[n];return i?b(e,n,{...i,selecting:!0,selection:null,rects:{}}):e}case l:{const{documentId:n}=t.payload,i=e.documents[n];return i?b(e,n,{...i,selecting:!1}):e}case d:{const{documentId:n}=t.payload,i=e.documents[n];return i?b(e,n,{...i,selecting:!1,selection:null,rects:{},active:!1}):e}case h:{const{documentId:n,rects:i}=t.payload,o=e.documents[n];return o?b(e,n,{...o,rects:i}):e}case g:{const{documentId:n,slices:i}=t.payload,o=e.documents[n];return o?b(e,n,{...o,slices:i}):e}case u:{const{documentId:n,pages:i}=t.payload,o=e.documents[n];if(!o)return e;const s={...o.geometry},r={...o.rects},a={...o.slices};for(const e of i)delete s[e],delete r[e],delete a[e];return b(e,n,{...o,geometry:s,rects:r,slices:a})}case"SELECTION/RESET":{const{documentId:n}=t.payload;return e.documents[n]?b(e,n,x):e}default:return e}},initialState:C};exports.SELECTION_PLUGIN_ID=n,exports.SelectionPlugin=j,exports.SelectionPluginPackage=_,exports.expandToLineBoundary=F,exports.expandToWordBoundary=O,exports.getVerticalOverlap=k,exports.glyphAt=T,exports.manifest=i,exports.mergeAdjacentRects=A,exports.rectIntersect=q,exports.rectIsEmpty=R,exports.rectUnion=D,exports.rectsWithinSlice=$,exports.shouldMergeHorizontalRects=z,exports.sliceBounds=w;
|
|
2
|
-
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let _embedpdf_core = require("@embedpdf/core");
|
|
3
|
+
let _embedpdf_plugin_interaction = require("@embedpdf/plugin-interaction");
|
|
4
|
+
let _embedpdf_core_geometry = require("@embedpdf/core-geometry");
|
|
5
|
+
//#region src/geometry.ts
|
|
6
|
+
/**
|
|
7
|
+
* The pure selection geometry: engine text geometry (PDF user space, y-up) →
|
|
8
|
+
* viewer content space (y-down), glyph hit-testing, word/line expansion, and the
|
|
9
|
+
* line-merge. No DOM, no engine, no React.
|
|
10
|
+
*
|
|
11
|
+
* The non-trivial bits are ports of battle-tested algorithms, adapted to v3's
|
|
12
|
+
* content-space `Rect {x,y,width,height}`:
|
|
13
|
+
* - `glyphAt` — PDFium `GetIndexAtPos` (exact tight box, then a tolerance pass).
|
|
14
|
+
* - `rectsForRange` — Chromium `pdfium_range.cc` `MergeAdjacentRects` (one rect per line).
|
|
15
|
+
* - `expandToWord/Line` — Chromium `OnMultipleClick` (double = word, triple = line).
|
|
16
|
+
* The PDF↔content y-flip is the geometry package's `pageGeometry` (the one place
|
|
17
|
+
* that math lives), never hand-rolled here.
|
|
18
|
+
*/
|
|
19
|
+
const FLAG_EMPTY = 2;
|
|
20
|
+
const isBoundary = (flags) => (flags & 3) !== 0;
|
|
21
|
+
const isEmpty = (flags) => (flags & FLAG_EMPTY) !== 0;
|
|
22
|
+
const toContent = (m, b) => (0, _embedpdf_core_geometry.applyRect)(m, {
|
|
23
|
+
x: b.left,
|
|
24
|
+
y: b.bottom,
|
|
25
|
+
width: b.right - b.left,
|
|
26
|
+
height: b.top - b.bottom
|
|
27
|
+
});
|
|
28
|
+
/**
|
|
29
|
+
* Flatten a page's text geometry into content-space glyphs + runs. Rotation does
|
|
30
|
+
* NOT enter here — the overlay rides the page's CSS rotation, so glyphs stay in
|
|
31
|
+
* un-rotated content space (the crop-relative y-flip is the only conversion).
|
|
32
|
+
*/
|
|
33
|
+
function buildPageText(snapshot, crop, rotation, userUnit) {
|
|
34
|
+
const { pdfToContent } = (0, _embedpdf_core_geometry.pageGeometry)({
|
|
35
|
+
crop,
|
|
36
|
+
rotation,
|
|
37
|
+
userUnit
|
|
38
|
+
}, 1);
|
|
39
|
+
const glyphs = [];
|
|
40
|
+
const runs = [];
|
|
41
|
+
for (const run of snapshot.runs) {
|
|
42
|
+
const start = glyphs.length;
|
|
43
|
+
for (const g of run.glyphs) glyphs.push({
|
|
44
|
+
loose: toContent(pdfToContent, g.looseBox),
|
|
45
|
+
tight: g.tightBox ? toContent(pdfToContent, g.tightBox) : void 0,
|
|
46
|
+
flags: g.flags
|
|
47
|
+
});
|
|
48
|
+
runs.push({
|
|
49
|
+
start,
|
|
50
|
+
count: run.glyphs.length,
|
|
51
|
+
rect: toContent(pdfToContent, run.rect),
|
|
52
|
+
fontSize: run.fontSize
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
glyphs,
|
|
57
|
+
runs
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function avgGlyphHeight(text) {
|
|
61
|
+
let total = 0;
|
|
62
|
+
let count = 0;
|
|
63
|
+
for (const g of text.glyphs) {
|
|
64
|
+
if (isEmpty(g.flags)) continue;
|
|
65
|
+
total += g.loose.height;
|
|
66
|
+
count++;
|
|
67
|
+
}
|
|
68
|
+
return count === 0 ? 0 : total / count;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The glyph index at a content-space point, or null when nothing is near (so the
|
|
72
|
+
* caller can show the pointer cursor off-text). PDFium `GetIndexAtPos`: exact
|
|
73
|
+
* tight-box containment first, then a tolerance pass (closest by Manhattan
|
|
74
|
+
* distance within `toleranceFactor × average glyph height`).
|
|
75
|
+
*/
|
|
76
|
+
function glyphAt(text, p, toleranceFactor = 1.5) {
|
|
77
|
+
for (const run of text.runs) {
|
|
78
|
+
const r = run.rect;
|
|
79
|
+
if (p.x < r.x || p.x > r.x + r.width || p.y < r.y || p.y > r.y + r.height) continue;
|
|
80
|
+
for (let i = 0; i < run.count; i++) {
|
|
81
|
+
const b = text.glyphs[run.start + i].tight ?? text.glyphs[run.start + i].loose;
|
|
82
|
+
if (p.x >= b.x && p.x <= b.x + b.width && p.y >= b.y && p.y <= b.y + b.height) return run.start + i;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (toleranceFactor <= 0) return null;
|
|
86
|
+
const half = avgGlyphHeight(text) * toleranceFactor / 2;
|
|
87
|
+
let best = -1;
|
|
88
|
+
let bestDist = Infinity;
|
|
89
|
+
for (const run of text.runs) {
|
|
90
|
+
const r = run.rect;
|
|
91
|
+
if (p.y < r.y - half || p.y > r.y + r.height + half || p.x < r.x - half || p.x > r.x + r.width + half) continue;
|
|
92
|
+
for (let i = 0; i < run.count; i++) {
|
|
93
|
+
const g = text.glyphs[run.start + i];
|
|
94
|
+
if (isEmpty(g.flags)) continue;
|
|
95
|
+
const b = g.tight ?? g.loose;
|
|
96
|
+
if (p.x < b.x - half || p.x > b.x + b.width + half || p.y < b.y - half || p.y > b.y + b.height + half) continue;
|
|
97
|
+
const dx = Math.min(Math.abs(p.x - b.x), Math.abs(p.x - (b.x + b.width)));
|
|
98
|
+
const dy = Math.min(Math.abs(p.y - b.y), Math.abs(p.y - (b.y + b.height)));
|
|
99
|
+
if (dx + dy < bestDist) {
|
|
100
|
+
bestDist = dx + dy;
|
|
101
|
+
best = run.start + i;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return best >= 0 ? best : null;
|
|
106
|
+
}
|
|
107
|
+
/** Double-click: the word around `i` (walk to space/empty glyphs both ways). */
|
|
108
|
+
function expandToWord(text, i) {
|
|
109
|
+
const n = text.glyphs.length;
|
|
110
|
+
if (i < 0 || i >= n) return [i, i];
|
|
111
|
+
let from = i;
|
|
112
|
+
while (from > 0 && !isBoundary(text.glyphs[from - 1].flags)) from--;
|
|
113
|
+
let to = i;
|
|
114
|
+
while (to < n - 1 && !isBoundary(text.glyphs[to + 1].flags)) to++;
|
|
115
|
+
return [from, to];
|
|
116
|
+
}
|
|
117
|
+
/** Triple-click: the full visual line — runs whose vertical extent overlaps the anchor run's. */
|
|
118
|
+
function expandToLine(text, i) {
|
|
119
|
+
const ri = text.runs.findIndex((r) => i >= r.start && i < r.start + r.count);
|
|
120
|
+
if (ri < 0) return [i, i];
|
|
121
|
+
const anchor = text.runs[ri];
|
|
122
|
+
const top = anchor.rect.y;
|
|
123
|
+
const bottom = anchor.rect.y + anchor.rect.height;
|
|
124
|
+
let from = anchor.start;
|
|
125
|
+
let to = anchor.start + anchor.count - 1;
|
|
126
|
+
for (let r = ri - 1; r >= 0; r--) {
|
|
127
|
+
const run = text.runs[r];
|
|
128
|
+
if (isZero(run.rect)) continue;
|
|
129
|
+
if (!overlapV(run.rect.y, run.rect.y + run.rect.height, top, bottom)) break;
|
|
130
|
+
from = run.start;
|
|
131
|
+
}
|
|
132
|
+
for (let r = ri + 1; r < text.runs.length; r++) {
|
|
133
|
+
const run = text.runs[r];
|
|
134
|
+
if (isZero(run.rect)) continue;
|
|
135
|
+
if (!overlapV(run.rect.y, run.rect.y + run.rect.height, top, bottom)) break;
|
|
136
|
+
to = run.start + run.count - 1;
|
|
137
|
+
}
|
|
138
|
+
return [from, to];
|
|
139
|
+
}
|
|
140
|
+
const CHAR_DISTANCE_FACTOR = 2.5;
|
|
141
|
+
const FONT_SIZE_RATIO_THRESHOLD = 1.5;
|
|
142
|
+
const VERTICAL_OVERLAP_THRESHOLD = .8;
|
|
143
|
+
const LINE_OVERLAP_THRESHOLD = .5;
|
|
144
|
+
function rectsForRange(text, from, to) {
|
|
145
|
+
const lo = Math.max(0, Math.min(from, to));
|
|
146
|
+
const hi = Math.min(text.glyphs.length - 1, Math.max(from, to));
|
|
147
|
+
const subRuns = [];
|
|
148
|
+
for (const run of text.runs) {
|
|
149
|
+
const runEnd = run.start + run.count - 1;
|
|
150
|
+
if (runEnd < lo || run.start > hi) continue;
|
|
151
|
+
const s = Math.max(lo, run.start);
|
|
152
|
+
const e = Math.min(hi, runEnd);
|
|
153
|
+
let minX = Infinity;
|
|
154
|
+
let maxX = -Infinity;
|
|
155
|
+
let minY = Infinity;
|
|
156
|
+
let maxY = -Infinity;
|
|
157
|
+
let charCount = 0;
|
|
158
|
+
let widthSum = 0;
|
|
159
|
+
let prevRight = -Infinity;
|
|
160
|
+
const flush = () => {
|
|
161
|
+
if (minX !== Infinity && charCount > 0) subRuns.push({
|
|
162
|
+
rect: {
|
|
163
|
+
x: minX,
|
|
164
|
+
y: minY,
|
|
165
|
+
width: maxX - minX,
|
|
166
|
+
height: maxY - minY
|
|
167
|
+
},
|
|
168
|
+
charCount,
|
|
169
|
+
fontSize: run.fontSize
|
|
170
|
+
});
|
|
171
|
+
minX = Infinity;
|
|
172
|
+
maxX = -Infinity;
|
|
173
|
+
minY = Infinity;
|
|
174
|
+
maxY = -Infinity;
|
|
175
|
+
charCount = 0;
|
|
176
|
+
widthSum = 0;
|
|
177
|
+
prevRight = -Infinity;
|
|
178
|
+
};
|
|
179
|
+
for (let gi = s; gi <= e; gi++) {
|
|
180
|
+
const g = text.glyphs[gi];
|
|
181
|
+
if (isEmpty(g.flags)) continue;
|
|
182
|
+
const b = g.loose;
|
|
183
|
+
if (charCount > 0 && prevRight > -Infinity) {
|
|
184
|
+
const avg = widthSum / charCount;
|
|
185
|
+
if (avg > 0 && Math.abs(b.x - prevRight) > CHAR_DISTANCE_FACTOR * avg) flush();
|
|
186
|
+
}
|
|
187
|
+
minX = Math.min(minX, b.x);
|
|
188
|
+
maxX = Math.max(maxX, b.x + b.width);
|
|
189
|
+
minY = Math.min(minY, b.y);
|
|
190
|
+
maxY = Math.max(maxY, b.y + b.height);
|
|
191
|
+
charCount++;
|
|
192
|
+
widthSum += b.width;
|
|
193
|
+
prevRight = b.x + b.width;
|
|
194
|
+
}
|
|
195
|
+
flush();
|
|
196
|
+
}
|
|
197
|
+
return mergeAdjacentRects(subRuns);
|
|
198
|
+
}
|
|
199
|
+
function mergeAdjacentRects(runs) {
|
|
200
|
+
const out = [];
|
|
201
|
+
let prev = null;
|
|
202
|
+
let cur = null;
|
|
203
|
+
for (const run of runs) {
|
|
204
|
+
if (prev && cur && shouldMerge(prev, run)) cur = (0, _embedpdf_core_geometry.boundsOfRects)([cur, run.rect]);
|
|
205
|
+
else {
|
|
206
|
+
if (cur) out.push(cur);
|
|
207
|
+
cur = run.rect;
|
|
208
|
+
}
|
|
209
|
+
prev = run;
|
|
210
|
+
}
|
|
211
|
+
if (cur && cur.width > 0 && cur.height > 0) out.push(cur);
|
|
212
|
+
return out;
|
|
213
|
+
}
|
|
214
|
+
function shouldMerge(a, b) {
|
|
215
|
+
if (a.fontSize != null && b.fontSize != null && a.fontSize > 0 && b.fontSize > 0) {
|
|
216
|
+
if (Math.max(a.fontSize, b.fontSize) / Math.min(a.fontSize, b.fontSize) > FONT_SIZE_RATIO_THRESHOLD) return false;
|
|
217
|
+
}
|
|
218
|
+
if (verticalOverlap(a.rect, b.rect) < VERTICAL_OVERLAP_THRESHOLD) return false;
|
|
219
|
+
const aw = a.rect.width / a.charCount;
|
|
220
|
+
const bw = b.rect.width / b.charCount;
|
|
221
|
+
const aL = a.rect.x - aw;
|
|
222
|
+
const aR = a.rect.x + a.rect.width + aw;
|
|
223
|
+
const bL = b.rect.x - bw;
|
|
224
|
+
return aL < b.rect.x + b.rect.width + bw && aR > bL;
|
|
225
|
+
}
|
|
226
|
+
function verticalOverlap(a, b) {
|
|
227
|
+
if (a.height <= 0 || b.height <= 0) return 0;
|
|
228
|
+
const u = Math.max(a.y + a.height, b.y + b.height) - Math.min(a.y, b.y);
|
|
229
|
+
if (u === a.height || u === b.height) return 1;
|
|
230
|
+
return Math.max(0, Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y)) / u;
|
|
231
|
+
}
|
|
232
|
+
const isZero = (r) => r.width === 0 && r.height === 0;
|
|
233
|
+
function overlapV(top1, bottom1, top2, bottom2) {
|
|
234
|
+
const u = Math.max(bottom1, bottom2) - Math.min(top1, top2);
|
|
235
|
+
if (u === 0) return false;
|
|
236
|
+
return Math.max(0, Math.min(bottom1, bottom2) - Math.max(top1, top2)) / u >= LINE_OVERLAP_THRESHOLD;
|
|
237
|
+
}
|
|
238
|
+
//#endregion
|
|
239
|
+
//#region src/capability.ts
|
|
240
|
+
const EMPTY = [];
|
|
241
|
+
/**
|
|
242
|
+
* The selection capability. The reducer state holds the selection range, the
|
|
243
|
+
* derived content-space rects (per page), and per-page loaded flags; the (large,
|
|
244
|
+
* non-serializable) per-page text geometry is cached HERE in the closure.
|
|
245
|
+
*
|
|
246
|
+
* Selection is cross-page: glyphs are ordered globally by (pageIndex, glyph), so
|
|
247
|
+
* a drag from page 2 into page 4 selects the tail of 2, all of 3, and the head of
|
|
248
|
+
* 4. `recompute` rebuilds the merged line rects for every loaded page in the span
|
|
249
|
+
* and re-runs whenever a mid-span page finishes loading.
|
|
250
|
+
*/
|
|
251
|
+
function createSelectionCapability(ctx) {
|
|
252
|
+
const cache = /* @__PURE__ */ new Map();
|
|
253
|
+
const pending = /* @__PURE__ */ new Set();
|
|
254
|
+
const changeCbs = /* @__PURE__ */ new Set();
|
|
255
|
+
const commitCbs = /* @__PURE__ */ new Set();
|
|
256
|
+
const fireChange = () => changeCbs.forEach((cb) => cb());
|
|
257
|
+
const pageIndexOf = (pon) => ctx.document()?.pages.findIndex((p) => p.pageObjectNumber === pon) ?? -1;
|
|
258
|
+
const ponAtIndex = (i) => ctx.document()?.pages[i]?.pageObjectNumber;
|
|
259
|
+
function orderedEnds(sel) {
|
|
260
|
+
const ai = pageIndexOf(sel.anchor.pon);
|
|
261
|
+
const fi = pageIndexOf(sel.focus.pon);
|
|
262
|
+
return ai < fi || ai === fi && sel.anchor.glyph <= sel.focus.glyph ? {
|
|
263
|
+
start: sel.anchor,
|
|
264
|
+
end: sel.focus,
|
|
265
|
+
direction: "forward"
|
|
266
|
+
} : {
|
|
267
|
+
start: sel.focus,
|
|
268
|
+
end: sel.anchor,
|
|
269
|
+
direction: "backward"
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
function endpointFor(ptr, which) {
|
|
273
|
+
const rects = ctx.getState().rects[ptr.pon] ?? EMPTY;
|
|
274
|
+
if (!rects.length) return null;
|
|
275
|
+
return {
|
|
276
|
+
pon: ptr.pon,
|
|
277
|
+
rect: which === "start" ? rects[0] : rects[rects.length - 1]
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function snapshot() {
|
|
281
|
+
const { selection, rects } = ctx.getState();
|
|
282
|
+
const pages = Object.keys(rects).map(Number).filter((pon) => (rects[pon]?.length ?? 0) > 0).map((pon) => ({
|
|
283
|
+
pon,
|
|
284
|
+
rects: rects[pon]
|
|
285
|
+
}));
|
|
286
|
+
if (!selection) return {
|
|
287
|
+
pages,
|
|
288
|
+
start: null,
|
|
289
|
+
end: null,
|
|
290
|
+
direction: "forward"
|
|
291
|
+
};
|
|
292
|
+
const { start, end, direction } = orderedEnds(selection);
|
|
293
|
+
return {
|
|
294
|
+
pages,
|
|
295
|
+
start: endpointFor(start, "start"),
|
|
296
|
+
end: endpointFor(end, "end"),
|
|
297
|
+
direction
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
function ensurePage(pon) {
|
|
301
|
+
if (cache.has(pon) || pending.has(pon)) return;
|
|
302
|
+
const doc = ctx.doc;
|
|
303
|
+
const layout = ctx.document()?.pages.find((p) => p.pageObjectNumber === pon);
|
|
304
|
+
if (!doc || !layout) return;
|
|
305
|
+
pending.add(pon);
|
|
306
|
+
doc.page(pon).geometry.read().then((snapshot) => {
|
|
307
|
+
pending.delete(pon);
|
|
308
|
+
cache.set(pon, buildPageText(snapshot, layout.boxes.crop, layout.rotation, layout.userUnit));
|
|
309
|
+
ctx.dispatch({
|
|
310
|
+
type: "PAGE_LOADED",
|
|
311
|
+
pon
|
|
312
|
+
});
|
|
313
|
+
if (ctx.getState().selection) recompute();
|
|
314
|
+
}, () => {
|
|
315
|
+
pending.delete(pon);
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
function recompute(sel = ctx.getState().selection) {
|
|
319
|
+
if (!sel) return;
|
|
320
|
+
const { start, end } = orderedEnds(sel);
|
|
321
|
+
const si = pageIndexOf(start.pon);
|
|
322
|
+
const ei = pageIndexOf(end.pon);
|
|
323
|
+
if (si < 0 || ei < 0) return;
|
|
324
|
+
const rects = {};
|
|
325
|
+
for (let i = si; i <= ei; i++) {
|
|
326
|
+
const pon = ponAtIndex(i);
|
|
327
|
+
if (pon == null) continue;
|
|
328
|
+
const text = cache.get(pon);
|
|
329
|
+
if (!text) {
|
|
330
|
+
ensurePage(pon);
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
rects[pon] = rectsForRange(text, i === si ? start.glyph : 0, i === ei ? end.glyph : text.glyphs.length - 1);
|
|
334
|
+
}
|
|
335
|
+
ctx.dispatch({
|
|
336
|
+
type: "SET",
|
|
337
|
+
selection: sel,
|
|
338
|
+
rects
|
|
339
|
+
});
|
|
340
|
+
fireChange();
|
|
341
|
+
}
|
|
342
|
+
function selectSpan(pon, point, expand) {
|
|
343
|
+
const text = cache.get(pon);
|
|
344
|
+
if (!text) return;
|
|
345
|
+
const i = glyphAt(text, point);
|
|
346
|
+
if (i == null) return;
|
|
347
|
+
const [from, to] = expand === "word" ? expandToWord(text, i) : expandToLine(text, i);
|
|
348
|
+
recompute({
|
|
349
|
+
anchor: {
|
|
350
|
+
pon,
|
|
351
|
+
glyph: from
|
|
352
|
+
},
|
|
353
|
+
focus: {
|
|
354
|
+
pon,
|
|
355
|
+
glyph: to
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
return {
|
|
360
|
+
ensurePage,
|
|
361
|
+
isLoaded: (pon) => !!ctx.getState().loaded[pon],
|
|
362
|
+
isOverText: (pon, point) => {
|
|
363
|
+
const text = cache.get(pon);
|
|
364
|
+
return text ? glyphAt(text, point) != null : false;
|
|
365
|
+
},
|
|
366
|
+
beginAt: (pon, point) => {
|
|
367
|
+
const text = cache.get(pon);
|
|
368
|
+
if (!text) return false;
|
|
369
|
+
const i = glyphAt(text, point);
|
|
370
|
+
if (i == null) return false;
|
|
371
|
+
recompute({
|
|
372
|
+
anchor: {
|
|
373
|
+
pon,
|
|
374
|
+
glyph: i
|
|
375
|
+
},
|
|
376
|
+
focus: {
|
|
377
|
+
pon,
|
|
378
|
+
glyph: i
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
return true;
|
|
382
|
+
},
|
|
383
|
+
selectWord: (pon, point) => selectSpan(pon, point, "word"),
|
|
384
|
+
selectLine: (pon, point) => selectSpan(pon, point, "line"),
|
|
385
|
+
extendTo: (pon, point) => {
|
|
386
|
+
const cur = ctx.getState().selection;
|
|
387
|
+
if (!cur) return;
|
|
388
|
+
const text = cache.get(pon);
|
|
389
|
+
if (!text) {
|
|
390
|
+
ensurePage(pon);
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
const i = glyphAt(text, point);
|
|
394
|
+
if (i == null) return;
|
|
395
|
+
recompute({
|
|
396
|
+
anchor: cur.anchor,
|
|
397
|
+
focus: {
|
|
398
|
+
pon,
|
|
399
|
+
glyph: i
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
},
|
|
403
|
+
end: () => commitCbs.forEach((cb) => cb()),
|
|
404
|
+
clear: () => {
|
|
405
|
+
ctx.dispatch({ type: "CLEAR" });
|
|
406
|
+
fireChange();
|
|
407
|
+
},
|
|
408
|
+
snapshot,
|
|
409
|
+
rectsForPage: (pon) => ctx.getState().rects[pon] ?? EMPTY,
|
|
410
|
+
hasSelection: () => ctx.getState().selection != null,
|
|
411
|
+
selectedPages: () => {
|
|
412
|
+
const { rects } = ctx.getState();
|
|
413
|
+
return Object.keys(rects).map(Number).filter((pon) => (rects[pon]?.length ?? 0) > 0);
|
|
414
|
+
},
|
|
415
|
+
onChange: (cb) => {
|
|
416
|
+
changeCbs.add(cb);
|
|
417
|
+
return () => changeCbs.delete(cb);
|
|
418
|
+
},
|
|
419
|
+
onCommit: (cb) => {
|
|
420
|
+
commitCbs.add(cb);
|
|
421
|
+
return () => commitCbs.delete(cb);
|
|
422
|
+
},
|
|
423
|
+
setHighlightVisible: (visible) => ctx.dispatch({
|
|
424
|
+
type: "SET_HIGHLIGHT_HIDDEN",
|
|
425
|
+
hidden: !visible
|
|
426
|
+
}),
|
|
427
|
+
highlightVisible: () => !ctx.getState().highlightHidden
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
//#endregion
|
|
431
|
+
//#region src/handler.ts
|
|
432
|
+
const CURSOR_TOKEN = "selection-text";
|
|
433
|
+
const DRAG_THRESHOLD_PX = 4;
|
|
434
|
+
/**
|
|
435
|
+
* The text-selection pointer handler. It is live only under tools that enable the
|
|
436
|
+
* `'text-select'` tag (the built-in `pointer` tool does; `pan` does not — so
|
|
437
|
+
* switching to pan disables text selection with zero special-casing here).
|
|
438
|
+
*
|
|
439
|
+
* - single click → deselect; records an anchor but selects NOTHING until
|
|
440
|
+
* the pointer moves past the drag threshold (Chrome/Acrobat feel)
|
|
441
|
+
* - single-click + drag → caret selection from the anchor (extends across pages)
|
|
442
|
+
* - double-click → select the word
|
|
443
|
+
* - triple-click → select the visual line
|
|
444
|
+
* - down off-text → deselect, and DON'T capture (so it can't block)
|
|
445
|
+
* - hover → I-beam only when over text, else the pointer cursor
|
|
446
|
+
*/
|
|
447
|
+
function createTextSelectHandler(selection, interaction) {
|
|
448
|
+
let anchor = null;
|
|
449
|
+
let dragging = false;
|
|
450
|
+
return {
|
|
451
|
+
id: "text-select",
|
|
452
|
+
priority: 60,
|
|
453
|
+
enabledFor: (tool) => tool.enables.has("text-select"),
|
|
454
|
+
onDown: (s) => {
|
|
455
|
+
anchor = null;
|
|
456
|
+
dragging = false;
|
|
457
|
+
if (!s.page) return false;
|
|
458
|
+
const { pon, point } = s.page;
|
|
459
|
+
const clicks = s.clickCount ?? 1;
|
|
460
|
+
if (clicks >= 3) {
|
|
461
|
+
selection.selectLine(pon, point);
|
|
462
|
+
return true;
|
|
463
|
+
}
|
|
464
|
+
if (clicks === 2) {
|
|
465
|
+
selection.selectWord(pon, point);
|
|
466
|
+
return true;
|
|
467
|
+
}
|
|
468
|
+
selection.clear();
|
|
469
|
+
if (!selection.isOverText(pon, point)) return false;
|
|
470
|
+
anchor = {
|
|
471
|
+
pon,
|
|
472
|
+
point,
|
|
473
|
+
vx: s.viewport.x,
|
|
474
|
+
vy: s.viewport.y
|
|
475
|
+
};
|
|
476
|
+
return true;
|
|
477
|
+
},
|
|
478
|
+
onMove: (s) => {
|
|
479
|
+
if (!s.page || !anchor) return;
|
|
480
|
+
if (!dragging) {
|
|
481
|
+
if (Math.hypot(s.viewport.x - anchor.vx, s.viewport.y - anchor.vy) < DRAG_THRESHOLD_PX) return;
|
|
482
|
+
dragging = true;
|
|
483
|
+
selection.beginAt(anchor.pon, anchor.point);
|
|
484
|
+
}
|
|
485
|
+
selection.extendTo(s.page.pon, s.page.point);
|
|
486
|
+
},
|
|
487
|
+
onUp: () => {
|
|
488
|
+
anchor = null;
|
|
489
|
+
dragging = false;
|
|
490
|
+
selection.end();
|
|
491
|
+
},
|
|
492
|
+
onHover: (s) => {
|
|
493
|
+
if (!s.page) {
|
|
494
|
+
interaction.setCursor(CURSOR_TOKEN, null);
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
selection.ensurePage(s.page.pon);
|
|
498
|
+
const overText = selection.isOverText(s.page.pon, s.page.point);
|
|
499
|
+
interaction.setCursor(CURSOR_TOKEN, overText ? "text" : null, 10);
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
//#endregion
|
|
504
|
+
//#region src/reducer.ts
|
|
505
|
+
const initialSelectionState = {
|
|
506
|
+
selection: null,
|
|
507
|
+
rects: {},
|
|
508
|
+
loaded: {},
|
|
509
|
+
highlightHidden: false
|
|
510
|
+
};
|
|
511
|
+
const selectionReducer = (state, action) => {
|
|
512
|
+
switch (action.type) {
|
|
513
|
+
case "PAGE_LOADED": return state.loaded[action.pon] ? state : {
|
|
514
|
+
...state,
|
|
515
|
+
loaded: {
|
|
516
|
+
...state.loaded,
|
|
517
|
+
[action.pon]: true
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
case "SET": return {
|
|
521
|
+
...state,
|
|
522
|
+
selection: action.selection,
|
|
523
|
+
rects: action.rects
|
|
524
|
+
};
|
|
525
|
+
case "CLEAR": return state.selection === null && Object.keys(state.rects).length === 0 ? state : {
|
|
526
|
+
...state,
|
|
527
|
+
selection: null,
|
|
528
|
+
rects: {}
|
|
529
|
+
};
|
|
530
|
+
case "SET_HIGHLIGHT_HIDDEN": return state.highlightHidden === action.hidden ? state : {
|
|
531
|
+
...state,
|
|
532
|
+
highlightHidden: action.hidden
|
|
533
|
+
};
|
|
534
|
+
default: return state;
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
//#endregion
|
|
538
|
+
//#region src/types.ts
|
|
539
|
+
const SelectionToken = (0, _embedpdf_core.createCapabilityToken)("selection");
|
|
540
|
+
//#endregion
|
|
541
|
+
//#region src/selection.plugin.ts
|
|
542
|
+
/**
|
|
543
|
+
* Text selection — document-scoped, requires the interaction hub. In `init` it
|
|
544
|
+
* registers ITS pointer handler with the hub; the hub owns the pointer stream
|
|
545
|
+
* and arbitration. Works with `<Stage>` or a standalone `<PageView>` — selection
|
|
546
|
+
* only needs the page coordinate context + the engine's text geometry.
|
|
547
|
+
*/
|
|
548
|
+
const selectionPlugin = () => (0, _embedpdf_core.definePlugin)({
|
|
549
|
+
id: "selection",
|
|
550
|
+
token: SelectionToken,
|
|
551
|
+
scope: "document",
|
|
552
|
+
requires: [_embedpdf_plugin_interaction.InteractionToken],
|
|
553
|
+
initialState: initialSelectionState,
|
|
554
|
+
reduce: selectionReducer,
|
|
555
|
+
capability: createSelectionCapability,
|
|
556
|
+
init: (ctx) => {
|
|
557
|
+
const interaction = ctx.get(_embedpdf_plugin_interaction.InteractionToken);
|
|
558
|
+
const selection = ctx.get(SelectionToken);
|
|
559
|
+
interaction.registerHandler(createTextSelectHandler(selection, interaction));
|
|
560
|
+
}
|
|
561
|
+
});
|
|
562
|
+
//#endregion
|
|
563
|
+
exports.SelectionToken = SelectionToken;
|
|
564
|
+
exports.buildPageText = buildPageText;
|
|
565
|
+
exports.createSelectionCapability = createSelectionCapability;
|
|
566
|
+
exports.createTextSelectHandler = createTextSelectHandler;
|
|
567
|
+
exports.expandToLine = expandToLine;
|
|
568
|
+
exports.expandToWord = expandToWord;
|
|
569
|
+
exports.glyphAt = glyphAt;
|
|
570
|
+
exports.initialSelectionState = initialSelectionState;
|
|
571
|
+
exports.rectsForRange = rectsForRange;
|
|
572
|
+
exports.selectionPlugin = selectionPlugin;
|
|
573
|
+
exports.selectionReducer = selectionReducer;
|
|
574
|
+
|
|
575
|
+
//# sourceMappingURL=index.cjs.map
|