22// boundary in the empty space between lines (as in PDF.js's TextLayerBuilder).
33const layers = new Map ( ) ;
44const textRuns = new Map ( ) ;
5+ const selectionOverlays = new Map ( ) ;
56let listeners ;
67
8+ function drawSelection ( document ) {
9+ const selection = document . getSelection ( ) ;
10+ for ( const [ host , overlay ] of selectionOverlays ) {
11+ const boxes = [ ] ;
12+ if ( selection ?. rangeCount && ! selection . isCollapsed ) {
13+ const selected = selection . getRangeAt ( 0 ) ;
14+ const bounds = host . getBoundingClientRect ( ) ;
15+ for ( const span of textRuns . get ( host ) ( ) ) {
16+ if ( ! host . contains ( span ) || ! selected . intersectsNode ( span ) ) continue ;
17+ const range = document . createRange ( ) ;
18+ range . selectNodeContents ( span ) ;
19+ if ( span . contains ( selected . startContainer ) )
20+ range . setStart ( selected . startContainer , selected . startOffset ) ;
21+ if ( span . contains ( selected . endContainer ) )
22+ range . setEnd ( selected . endContainer , selected . endOffset ) ;
23+ for ( const rect of range . getClientRects ( ) ) {
24+ if ( ! rect . width || ! rect . height ) continue ;
25+ const box = document . createElement ( 'div' ) ;
26+ Object . assign ( box . style , {
27+ left : `${ rect . left - bounds . left } px` ,
28+ top : `${ rect . top - bounds . top } px` ,
29+ width : `${ rect . width } px` ,
30+ height : `${ rect . height } px` ,
31+ } ) ;
32+ boxes . push ( box ) ;
33+ }
34+ }
35+ }
36+ overlay . replaceChildren ( ...boxes ) ;
37+ }
38+ }
39+
740export function preserveTextLayerSelection ( spans ) {
841 const document = spans [ 0 ] ?. ownerDocument ;
942 const selection = document ?. getSelection ( ) ;
@@ -61,6 +94,7 @@ function installSharedListeners(document) {
6194 document . addEventListener (
6295 'selectionchange' ,
6396 ( ) => {
97+ drawSelection ( document ) ;
6498 const selection = document . getSelection ( ) ;
6599 if ( ! selection ?. rangeCount || selection . isCollapsed ) {
66100 resetAll ( ) ;
@@ -180,6 +214,14 @@ export function bindTextLayerSelection(host, getSpans) {
180214 end . className = 'endOfContent' ;
181215 end . setAttribute ( 'aria-hidden' , 'true' ) ;
182216 host . append ( end ) ;
217+ const overlay = document . createElement ( 'div' ) ;
218+ overlay . className = 'text-selection-overlay' ;
219+ overlay . setAttribute ( 'aria-hidden' , 'true' ) ;
220+ host . append ( overlay ) ;
221+ selectionOverlays . set ( host , overlay ) ;
222+ const redraw = ( ) => drawSelection ( document ) ;
223+ const resizeObserver = new ResizeObserver ( redraw ) ;
224+ resizeObserver . observe ( host ) ;
183225 const start = ( ) => host . classList . add ( 'selecting' ) ;
184226 host . addEventListener ( 'mousedown' , start ) ;
185227 layers . set ( host , end ) ;
@@ -192,6 +234,9 @@ export function bindTextLayerSelection(host, getSpans) {
192234 host . removeEventListener ( 'mousedown' , start ) ;
193235 layers . delete ( host ) ;
194236 textRuns . delete ( host ) ;
237+ selectionOverlays . delete ( host ) ;
238+ resizeObserver . disconnect ( ) ;
239+ overlay . remove ( ) ;
195240 end . remove ( ) ;
196241 host . classList . remove ( 'selecting' ) ;
197242 if ( ! layers . size ) {
0 commit comments