@anaralabs/lector 3.14.12 → 3.15.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/README.md +15 -15
- package/dist/_tsup-dts-rollup.d.ts +260 -20
- package/dist/index.d.ts +6 -0
- package/dist/index.js +1879 -223
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import React, { createContext, memo, forwardRef, useMemo, cloneElement, useEffect, useRef,
|
|
3
|
+
import React, { createContext, memo, forwardRef, useMemo, cloneElement, useEffect, useRef, useLayoutEffect, useContext, useState, useCallback, useSyncExternalStore, createRef, useImperativeHandle } from 'react';
|
|
4
4
|
import { createPortal } from 'react-dom';
|
|
5
5
|
import { useFloating, autoUpdate, offset, flip, shift, useDismiss, useInteractions, inline } from '@floating-ui/react';
|
|
6
6
|
import { useStore, createStore } from 'zustand';
|
|
@@ -9,7 +9,7 @@ import { useShallow } from 'zustand/react/shallow';
|
|
|
9
9
|
import { useDebounce } from 'use-debounce';
|
|
10
10
|
import { v4 } from 'uuid';
|
|
11
11
|
import { Slot } from '@radix-ui/react-slot';
|
|
12
|
-
import { useVirtualizer, elementScroll,
|
|
12
|
+
import { useVirtualizer, elementScroll, observeElementRect } from '@tanstack/react-virtual';
|
|
13
13
|
|
|
14
14
|
// src/lib/clamp.ts
|
|
15
15
|
var clamp = (value, minimum, maximum) => {
|
|
@@ -219,6 +219,828 @@ function createDarkModeColorMap(colors) {
|
|
|
219
219
|
return map;
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
+
// src/lib/selection-page.ts
|
|
223
|
+
var SelectionPage = class {
|
|
224
|
+
constructor(element, pageNumber, width, height) {
|
|
225
|
+
this.element = element;
|
|
226
|
+
this.pageNumber = pageNumber;
|
|
227
|
+
this.width = width;
|
|
228
|
+
this.height = height;
|
|
229
|
+
const walker = element.ownerDocument.createTreeWalker(
|
|
230
|
+
element,
|
|
231
|
+
NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT
|
|
232
|
+
);
|
|
233
|
+
const chunks = [];
|
|
234
|
+
const lineBreaks = [];
|
|
235
|
+
let offset3 = 0;
|
|
236
|
+
for (let node = walker.nextNode(); node; node = walker.nextNode()) {
|
|
237
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
238
|
+
const text = node.textContent ?? "";
|
|
239
|
+
if (!text) continue;
|
|
240
|
+
this.nodes.push({
|
|
241
|
+
node,
|
|
242
|
+
start: offset3,
|
|
243
|
+
end: offset3 + text.length
|
|
244
|
+
});
|
|
245
|
+
chunks.push(text);
|
|
246
|
+
offset3 += text.length;
|
|
247
|
+
} else if (node.tagName === "BR") lineBreaks.push(offset3);
|
|
248
|
+
}
|
|
249
|
+
this.content = { text: chunks.join(""), lineBreaks };
|
|
250
|
+
}
|
|
251
|
+
nodes = [];
|
|
252
|
+
content;
|
|
253
|
+
offset(node, offset3) {
|
|
254
|
+
if (!this.element.contains(node)) return null;
|
|
255
|
+
const item = this.nodes.find((item2) => item2.node === node);
|
|
256
|
+
if (item)
|
|
257
|
+
return item.start + Math.max(0, Math.min(offset3, item.end - item.start));
|
|
258
|
+
const range = this.element.ownerDocument.createRange();
|
|
259
|
+
range.selectNodeContents(this.element);
|
|
260
|
+
try {
|
|
261
|
+
range.setEnd(node, offset3);
|
|
262
|
+
} catch {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
return Math.min(range.toString().length, this.content.text.length);
|
|
266
|
+
}
|
|
267
|
+
point(offset3, end = false) {
|
|
268
|
+
const index = Math.max(0, Math.min(offset3, this.content.text.length));
|
|
269
|
+
const item = this.nodes.find((item2) => end ? item2.end >= index : item2.end > index) ?? this.nodes.at(-1);
|
|
270
|
+
return item ? { node: item.node, offset: Math.max(0, index - item.start) } : null;
|
|
271
|
+
}
|
|
272
|
+
rects(from, to) {
|
|
273
|
+
const bounds = this.element.getBoundingClientRect();
|
|
274
|
+
const scaleX = bounds.width / this.width || 1;
|
|
275
|
+
const scaleY = bounds.height / this.height || 1;
|
|
276
|
+
const rects = [];
|
|
277
|
+
for (const item of this.nodes) {
|
|
278
|
+
const start = Math.max(from, item.start);
|
|
279
|
+
const end = Math.min(to, item.end);
|
|
280
|
+
if (end <= start || !item.node.textContent?.slice(start - item.start, end - item.start).trim())
|
|
281
|
+
continue;
|
|
282
|
+
const range = this.element.ownerDocument.createRange();
|
|
283
|
+
range.setStart(item.node, start - item.start);
|
|
284
|
+
range.setEnd(item.node, end - item.start);
|
|
285
|
+
for (const rect of range.getClientRects()) {
|
|
286
|
+
if (rect.width <= 0 || rect.height <= 0) continue;
|
|
287
|
+
rects.push({
|
|
288
|
+
pageNumber: this.pageNumber,
|
|
289
|
+
left: (rect.left - bounds.left) / scaleX,
|
|
290
|
+
top: (rect.top - bounds.top) / scaleY,
|
|
291
|
+
width: rect.width / scaleX,
|
|
292
|
+
height: rect.height / scaleY
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return rects;
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
async function withSelectionPage(proxy, owner, signal, read) {
|
|
300
|
+
signal.throwIfAborted();
|
|
301
|
+
const { TextLayer: TextLayer2 } = await import('pdfjs-dist/legacy/build/pdf.mjs');
|
|
302
|
+
signal.throwIfAborted();
|
|
303
|
+
const viewport = proxy.getViewport({ scale: 1 });
|
|
304
|
+
const container = owner.createElement("div");
|
|
305
|
+
container.className = "textLayer";
|
|
306
|
+
container.setAttribute("aria-hidden", "true");
|
|
307
|
+
container.style.cssText = `position:fixed;left:-100000px;top:0;visibility:hidden;pointer-events:none;width:${viewport.width}px;height:${viewport.height}px;--scale-factor:1;--total-scale-factor:1;`;
|
|
308
|
+
owner.body.append(container);
|
|
309
|
+
let layer;
|
|
310
|
+
const cancel = () => layer?.cancel();
|
|
311
|
+
signal.addEventListener("abort", cancel, { once: true });
|
|
312
|
+
try {
|
|
313
|
+
layer = new TextLayer2({
|
|
314
|
+
container,
|
|
315
|
+
viewport,
|
|
316
|
+
textContentSource: proxy.streamTextContent()
|
|
317
|
+
});
|
|
318
|
+
await layer.render();
|
|
319
|
+
signal.throwIfAborted();
|
|
320
|
+
const unrotated = proxy.getViewport({ scale: 1, rotation: 0 });
|
|
321
|
+
container.style.width = `${unrotated.width}px`;
|
|
322
|
+
container.style.height = `${unrotated.height}px`;
|
|
323
|
+
return read(
|
|
324
|
+
new SelectionPage(
|
|
325
|
+
container,
|
|
326
|
+
proxy.pageNumber,
|
|
327
|
+
viewport.width,
|
|
328
|
+
viewport.height
|
|
329
|
+
)
|
|
330
|
+
);
|
|
331
|
+
} finally {
|
|
332
|
+
signal.removeEventListener("abort", cancel);
|
|
333
|
+
layer?.cancel();
|
|
334
|
+
container.remove();
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// src/lib/text-normalization.ts
|
|
339
|
+
function replace(source, replacements2) {
|
|
340
|
+
const chunks = [];
|
|
341
|
+
const edits = [];
|
|
342
|
+
let offset3 = 0;
|
|
343
|
+
let outputLength = 0;
|
|
344
|
+
for (const replacement of replacements2) {
|
|
345
|
+
const { index, length, text } = replacement;
|
|
346
|
+
if (source.text.slice(index, index + length) === text) continue;
|
|
347
|
+
const before = source.text.slice(offset3, index);
|
|
348
|
+
chunks.push(before, text);
|
|
349
|
+
outputLength += before.length;
|
|
350
|
+
edits.push({
|
|
351
|
+
start: outputLength,
|
|
352
|
+
end: outputLength + text.length,
|
|
353
|
+
sourceStart: index,
|
|
354
|
+
sourceEnd: index + length
|
|
355
|
+
});
|
|
356
|
+
outputLength += text.length;
|
|
357
|
+
offset3 = index + length;
|
|
358
|
+
}
|
|
359
|
+
if (!edits.length) return source;
|
|
360
|
+
chunks.push(source.text.slice(offset3));
|
|
361
|
+
return { text: chunks.join(""), maps: [...source.maps, edits] };
|
|
362
|
+
}
|
|
363
|
+
function* replacements(text, pattern, value) {
|
|
364
|
+
for (const match of text.matchAll(pattern)) {
|
|
365
|
+
yield { index: match.index, length: match[0].length, text: value };
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
function* insertLineBreaks(text, lineBreaks) {
|
|
369
|
+
let previous = -1;
|
|
370
|
+
for (const index of lineBreaks) {
|
|
371
|
+
if (!Number.isInteger(index) || index <= previous || index < 0 || index > text.length)
|
|
372
|
+
continue;
|
|
373
|
+
previous = index;
|
|
374
|
+
yield { index, length: 0, text: "\n" };
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
var ligatures = ["ff", "fi", "fl", "ffi", "ffl", "st", "st"];
|
|
378
|
+
var softHyphen = /\u00ad(?:[^\S\r\n]*\r?\n[^\S\r\n]*(?=\p{L}))?/gu;
|
|
379
|
+
var wrappedHyphen = /(?<=[\p{L}\p{M}])[-\u2010][^\S\r\n]*\r?\n[^\S\r\n]*(?=\p{Ll})/gu;
|
|
380
|
+
var cjkLineBreak = /(?<=[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}])\r?\n(?=[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}])/gu;
|
|
381
|
+
function normalizeSelectionText(text, options = {}) {
|
|
382
|
+
let result = normalizeUnicode(text).replace(softHyphen, "");
|
|
383
|
+
if (options.ignoreHyphenation) result = result.replace(wrappedHyphen, "");
|
|
384
|
+
if (options.lineBreaks === "space") {
|
|
385
|
+
result = result.replace(cjkLineBreak, "").replace(/(?<!\n)\n(?!\n)/g, " ");
|
|
386
|
+
}
|
|
387
|
+
return result;
|
|
388
|
+
}
|
|
389
|
+
function normalizeUnicode(text, matchDiacritics = true) {
|
|
390
|
+
let result = text.replace(
|
|
391
|
+
/[\uFB00-\uFB06]/g,
|
|
392
|
+
(char) => ligatures[char.charCodeAt(0) - 64256]
|
|
393
|
+
);
|
|
394
|
+
if (!matchDiacritics) {
|
|
395
|
+
result = result.normalize("NFD").replace(
|
|
396
|
+
/([\p{Script=Latin}\p{Script=Greek}\p{Script=Cyrillic}])\p{M}+/gu,
|
|
397
|
+
"$1"
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
return result.normalize("NFC");
|
|
401
|
+
}
|
|
402
|
+
var segmenter;
|
|
403
|
+
function* unicodeReplacements(text, matchDiacritics) {
|
|
404
|
+
segmenter ??= new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
405
|
+
for (const match of text.matchAll(/\p{ASCII}?\P{ASCII}+/gu)) {
|
|
406
|
+
if (normalizeUnicode(match[0], matchDiacritics) === match[0]) continue;
|
|
407
|
+
for (const { segment, index } of segmenter.segment(match[0])) {
|
|
408
|
+
yield {
|
|
409
|
+
index: match.index + index,
|
|
410
|
+
length: segment.length,
|
|
411
|
+
text: normalizeUnicode(segment, matchDiacritics)
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
function normalizeSearchText(text, options = {}, lineBreaks = []) {
|
|
417
|
+
let result = { text, maps: [] };
|
|
418
|
+
if (lineBreaks.length)
|
|
419
|
+
result = replace(result, insertLineBreaks(text, lineBreaks));
|
|
420
|
+
const matchDiacritics = options.matchDiacritics !== false;
|
|
421
|
+
if (normalizeUnicode(result.text, matchDiacritics) !== result.text) {
|
|
422
|
+
result = replace(result, unicodeReplacements(result.text, matchDiacritics));
|
|
423
|
+
}
|
|
424
|
+
result = replace(result, replacements(result.text, softHyphen, ""));
|
|
425
|
+
if (options.ignoreHyphenation) {
|
|
426
|
+
result = replace(result, replacements(result.text, wrappedHyphen, ""));
|
|
427
|
+
}
|
|
428
|
+
result = replace(result, replacements(result.text, cjkLineBreak, ""));
|
|
429
|
+
result = replace(result, replacements(result.text, /\s+/gu, " "));
|
|
430
|
+
const lower = result.text.toLowerCase();
|
|
431
|
+
if (lower.length !== result.text.length) {
|
|
432
|
+
function* caseReplacements(value) {
|
|
433
|
+
let index = 0;
|
|
434
|
+
for (const char of value) {
|
|
435
|
+
const folded = char.toLowerCase();
|
|
436
|
+
if (folded.length !== char.length)
|
|
437
|
+
yield { index, length: char.length, text: folded };
|
|
438
|
+
index += char.length;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
result = replace(result, caseReplacements(result.text));
|
|
442
|
+
}
|
|
443
|
+
return { ...result, text: lower };
|
|
444
|
+
}
|
|
445
|
+
function sourceOffset(edits, offset3, end) {
|
|
446
|
+
let low = 0;
|
|
447
|
+
let high = edits.length;
|
|
448
|
+
while (low < high) {
|
|
449
|
+
const middle = low + high >>> 1;
|
|
450
|
+
if (edits[middle].start <= offset3) low = middle + 1;
|
|
451
|
+
else high = middle;
|
|
452
|
+
}
|
|
453
|
+
const edit = edits[low - 1];
|
|
454
|
+
if (!edit) return offset3 + (end ? 1 : 0);
|
|
455
|
+
if (offset3 < edit.end) return end ? edit.sourceEnd : edit.sourceStart;
|
|
456
|
+
return offset3 + edit.sourceEnd - edit.end + (end ? 1 : 0);
|
|
457
|
+
}
|
|
458
|
+
function originalTextRange(normalized, start, end) {
|
|
459
|
+
for (let i = normalized.maps.length - 1; i >= 0; i--) {
|
|
460
|
+
const map = normalized.maps[i];
|
|
461
|
+
start = sourceOffset(map, start, false);
|
|
462
|
+
end = sourceOffset(map, end - 1, true);
|
|
463
|
+
}
|
|
464
|
+
return { start, end };
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// src/lib/pdf-selection.ts
|
|
468
|
+
var sameAnchor = (a, b) => a.pageNumber === b.pageNumber && a.offset === b.offset;
|
|
469
|
+
var compare = (a, b) => a.pageNumber - b.pageNumber || a.offset - b.offset;
|
|
470
|
+
var samePoint = (a, node, offset3) => a.node === node && a.offset === offset3;
|
|
471
|
+
var selectionOwners = /* @__PURE__ */ new WeakMap();
|
|
472
|
+
var PDFSelectionController = class {
|
|
473
|
+
constructor(options) {
|
|
474
|
+
this.options = options;
|
|
475
|
+
}
|
|
476
|
+
snapshot = null;
|
|
477
|
+
listeners = /* @__PURE__ */ new Set();
|
|
478
|
+
layers = /* @__PURE__ */ new Map();
|
|
479
|
+
pages = /* @__PURE__ */ new Map();
|
|
480
|
+
container = null;
|
|
481
|
+
projection = null;
|
|
482
|
+
frame = null;
|
|
483
|
+
work = null;
|
|
484
|
+
structuralChange = false;
|
|
485
|
+
drag = null;
|
|
486
|
+
connectedCleanup = null;
|
|
487
|
+
generation = 0;
|
|
488
|
+
keyboardWork = null;
|
|
489
|
+
keyboardMoves = [];
|
|
490
|
+
getSnapshot = () => this.snapshot;
|
|
491
|
+
subscribe = (listener) => {
|
|
492
|
+
this.listeners.add(listener);
|
|
493
|
+
return () => {
|
|
494
|
+
this.listeners.delete(listener);
|
|
495
|
+
};
|
|
496
|
+
};
|
|
497
|
+
get isDragging() {
|
|
498
|
+
return this.drag !== null;
|
|
499
|
+
}
|
|
500
|
+
includesPage(page) {
|
|
501
|
+
const range = this.ordered();
|
|
502
|
+
return Boolean(
|
|
503
|
+
range && page >= range.start.pageNumber && page <= range.end.pageNumber
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
publish(snapshot) {
|
|
507
|
+
this.snapshot = snapshot;
|
|
508
|
+
for (const listener of this.listeners) listener();
|
|
509
|
+
}
|
|
510
|
+
ordered() {
|
|
511
|
+
if (!this.snapshot) return null;
|
|
512
|
+
const { anchor, focus } = this.snapshot;
|
|
513
|
+
return compare(anchor, focus) <= 0 ? { start: anchor, end: focus } : { start: focus, end: anchor };
|
|
514
|
+
}
|
|
515
|
+
bounds(page, length) {
|
|
516
|
+
const range = this.ordered();
|
|
517
|
+
return {
|
|
518
|
+
from: page === range.start.pageNumber ? Math.min(range.start.offset, length) : 0,
|
|
519
|
+
to: page === range.end.pageNumber ? Math.min(range.end.offset, length) : length
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
remember(page) {
|
|
523
|
+
if (!this.includesPage(page.pageNumber)) return;
|
|
524
|
+
for (const point of [this.snapshot.anchor, this.snapshot.focus]) {
|
|
525
|
+
if (point.pageNumber === page.pageNumber && point.offset > page.content.text.length)
|
|
526
|
+
throw new RangeError("Selection offset exceeds page text length");
|
|
527
|
+
}
|
|
528
|
+
const { from, to } = this.bounds(page.pageNumber, page.content.text.length);
|
|
529
|
+
const cached = this.pages.get(page.pageNumber);
|
|
530
|
+
if (cached?.content.text === page.content.text && cached.from === from && cached.to === to)
|
|
531
|
+
return;
|
|
532
|
+
this.pages.set(page.pageNumber, {
|
|
533
|
+
content: page.content,
|
|
534
|
+
from,
|
|
535
|
+
to,
|
|
536
|
+
rects: page.rects(from, to)
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
complete() {
|
|
540
|
+
const range = this.ordered();
|
|
541
|
+
if (!range) return false;
|
|
542
|
+
for (let n = range.start.pageNumber; n <= range.end.pageNumber; n++) {
|
|
543
|
+
const page = this.pages.get(n);
|
|
544
|
+
if (!page) return false;
|
|
545
|
+
const bounds = this.bounds(n, page.content.text.length);
|
|
546
|
+
if (page.from !== bounds.from || page.to !== bounds.to) return false;
|
|
547
|
+
}
|
|
548
|
+
return true;
|
|
549
|
+
}
|
|
550
|
+
setSelection = (anchor, focus) => {
|
|
551
|
+
for (const point of [anchor, focus]) {
|
|
552
|
+
const text = this.layers.get(point.pageNumber)?.content.text ?? this.pages.get(point.pageNumber)?.content.text;
|
|
553
|
+
if (text !== void 0 && point.offset > text.length)
|
|
554
|
+
throw new RangeError("Selection offset exceeds page text length");
|
|
555
|
+
if (!Number.isInteger(point.pageNumber) || point.pageNumber < 1 || point.pageNumber > this.options.pageCount || !Number.isInteger(point.offset) || point.offset < 0)
|
|
556
|
+
throw new RangeError(
|
|
557
|
+
"Selection anchors need a valid page and nonnegative UTF-16 offset"
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
if (sameAnchor(anchor, focus)) {
|
|
561
|
+
const drag = this.drag;
|
|
562
|
+
this.clear();
|
|
563
|
+
this.drag = drag;
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
if (this.snapshot && this.snapshot.status !== "error" && sameAnchor(this.snapshot.anchor, anchor) && sameAnchor(this.snapshot.focus, focus))
|
|
567
|
+
return;
|
|
568
|
+
if (this.container) {
|
|
569
|
+
const owner = this.container.ownerDocument;
|
|
570
|
+
const previous = selectionOwners.get(owner);
|
|
571
|
+
if (previous !== this) previous?.clear();
|
|
572
|
+
selectionOwners.set(owner, this);
|
|
573
|
+
}
|
|
574
|
+
this.work?.abort();
|
|
575
|
+
this.keyboardWork?.abort();
|
|
576
|
+
this.keyboardWork = null;
|
|
577
|
+
this.generation++;
|
|
578
|
+
this.snapshot = {
|
|
579
|
+
anchor: { ...anchor },
|
|
580
|
+
focus: { ...focus },
|
|
581
|
+
status: "loading"
|
|
582
|
+
};
|
|
583
|
+
for (const n of this.pages.keys())
|
|
584
|
+
if (!this.includesPage(n)) this.pages.delete(n);
|
|
585
|
+
for (const page of this.layers.values()) this.remember(page);
|
|
586
|
+
this.publish({
|
|
587
|
+
...this.snapshot,
|
|
588
|
+
status: this.complete() ? "ready" : "loading"
|
|
589
|
+
});
|
|
590
|
+
this.project();
|
|
591
|
+
this.schedule();
|
|
592
|
+
this.hydrate();
|
|
593
|
+
};
|
|
594
|
+
clear = () => {
|
|
595
|
+
const owner = this.container?.ownerDocument;
|
|
596
|
+
if (owner && selectionOwners.get(owner) === this)
|
|
597
|
+
selectionOwners.delete(owner);
|
|
598
|
+
this.work?.abort();
|
|
599
|
+
this.keyboardWork?.abort();
|
|
600
|
+
this.keyboardWork = null;
|
|
601
|
+
this.generation++;
|
|
602
|
+
this.pages.clear();
|
|
603
|
+
this.keyboardMoves = [];
|
|
604
|
+
this.drag = null;
|
|
605
|
+
const selection = this.container?.ownerDocument.getSelection();
|
|
606
|
+
if (this.projection && selection && samePoint(
|
|
607
|
+
this.projection.anchor,
|
|
608
|
+
selection.anchorNode,
|
|
609
|
+
selection.anchorOffset
|
|
610
|
+
) && samePoint(
|
|
611
|
+
this.projection.focus,
|
|
612
|
+
selection.focusNode,
|
|
613
|
+
selection.focusOffset
|
|
614
|
+
))
|
|
615
|
+
selection.removeAllRanges();
|
|
616
|
+
this.projection = null;
|
|
617
|
+
if (this.snapshot) this.publish(null);
|
|
618
|
+
};
|
|
619
|
+
getText = (options = {}) => {
|
|
620
|
+
const range = this.ordered();
|
|
621
|
+
if (!range || !this.complete()) return null;
|
|
622
|
+
const chunks = [];
|
|
623
|
+
for (let n = range.start.pageNumber; n <= range.end.pageNumber; n++) {
|
|
624
|
+
const page = this.pages.get(n);
|
|
625
|
+
const pieces = [];
|
|
626
|
+
let offset3 = page.from;
|
|
627
|
+
for (const end of page.content.lineBreaks) {
|
|
628
|
+
if (end <= page.from || end >= page.to) continue;
|
|
629
|
+
pieces.push(page.content.text.slice(offset3, end), "\n");
|
|
630
|
+
offset3 = end;
|
|
631
|
+
}
|
|
632
|
+
pieces.push(page.content.text.slice(offset3, page.to));
|
|
633
|
+
chunks.push(pieces.join(""));
|
|
634
|
+
}
|
|
635
|
+
return normalizeSelectionText(chunks.join("\n\n"), options);
|
|
636
|
+
};
|
|
637
|
+
getSelection = (options) => {
|
|
638
|
+
const text = this.getText(options);
|
|
639
|
+
if (text === null || !this.snapshot) return null;
|
|
640
|
+
const range = this.ordered();
|
|
641
|
+
const highlights = [];
|
|
642
|
+
for (let n = range.start.pageNumber; n <= range.end.pageNumber; n++)
|
|
643
|
+
highlights.push(...this.pages.get(n).rects.map((rect) => ({ ...rect })));
|
|
644
|
+
return {
|
|
645
|
+
anchor: { ...this.snapshot.anchor },
|
|
646
|
+
focus: { ...this.snapshot.focus },
|
|
647
|
+
text,
|
|
648
|
+
highlights,
|
|
649
|
+
underlines: highlights.map((rect) => ({
|
|
650
|
+
...rect,
|
|
651
|
+
top: rect.top + rect.height - 1,
|
|
652
|
+
height: 1
|
|
653
|
+
})),
|
|
654
|
+
isCollapsed: false
|
|
655
|
+
};
|
|
656
|
+
};
|
|
657
|
+
getSelectionAsync = async (options) => {
|
|
658
|
+
const generation = this.generation;
|
|
659
|
+
await new Promise((resolve) => {
|
|
660
|
+
if (this.snapshot?.status !== "loading") {
|
|
661
|
+
resolve();
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
const unsubscribe = this.subscribe(() => {
|
|
665
|
+
if (generation !== this.generation || this.snapshot?.status !== "loading") {
|
|
666
|
+
unsubscribe();
|
|
667
|
+
resolve();
|
|
668
|
+
}
|
|
669
|
+
});
|
|
670
|
+
});
|
|
671
|
+
if (generation !== this.generation)
|
|
672
|
+
throw new DOMException("Selection changed", "AbortError");
|
|
673
|
+
if (this.snapshot?.status === "error") throw this.snapshot.error;
|
|
674
|
+
return this.getSelection(options);
|
|
675
|
+
};
|
|
676
|
+
finishSelection() {
|
|
677
|
+
if (this.snapshot?.status !== "loading" || !this.complete()) return;
|
|
678
|
+
this.work?.abort();
|
|
679
|
+
this.work = null;
|
|
680
|
+
this.publish({ ...this.snapshot, status: "ready" });
|
|
681
|
+
}
|
|
682
|
+
registerLayer(element, pageNumber, width, height) {
|
|
683
|
+
const page = new SelectionPage(element, pageNumber, width, height);
|
|
684
|
+
this.layers.set(pageNumber, page);
|
|
685
|
+
try {
|
|
686
|
+
this.remember(page);
|
|
687
|
+
this.finishSelection();
|
|
688
|
+
} catch (error) {
|
|
689
|
+
this.work?.abort();
|
|
690
|
+
if (this.snapshot)
|
|
691
|
+
this.publish({ ...this.snapshot, status: "error", error });
|
|
692
|
+
}
|
|
693
|
+
this.schedule();
|
|
694
|
+
return () => {
|
|
695
|
+
if (this.layers.get(pageNumber) !== page) return;
|
|
696
|
+
this.structuralChange = true;
|
|
697
|
+
this.layers.delete(pageNumber);
|
|
698
|
+
this.projection = null;
|
|
699
|
+
this.schedule();
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
anchorFor(node, offset3) {
|
|
703
|
+
if (!node) return null;
|
|
704
|
+
for (const [pageNumber, page] of this.layers) {
|
|
705
|
+
const index = page.offset(node, offset3);
|
|
706
|
+
if (index !== null) return { pageNumber, offset: index };
|
|
707
|
+
}
|
|
708
|
+
return null;
|
|
709
|
+
}
|
|
710
|
+
capture = () => {
|
|
711
|
+
const owner = this.container?.ownerDocument;
|
|
712
|
+
const active = owner?.activeElement;
|
|
713
|
+
if (active && active !== owner?.body && !this.container?.contains(active) && !active.closest(
|
|
714
|
+
'input, textarea, [contenteditable]:not([contenteditable="false"])'
|
|
715
|
+
))
|
|
716
|
+
return;
|
|
717
|
+
const selection = owner?.getSelection();
|
|
718
|
+
if (!selection || selection.rangeCount > 1) return;
|
|
719
|
+
if (this.projection && samePoint(
|
|
720
|
+
this.projection.anchor,
|
|
721
|
+
selection.anchorNode,
|
|
722
|
+
selection.anchorOffset
|
|
723
|
+
) && samePoint(
|
|
724
|
+
this.projection.focus,
|
|
725
|
+
selection.focusNode,
|
|
726
|
+
selection.focusOffset
|
|
727
|
+
))
|
|
728
|
+
return;
|
|
729
|
+
if (this.structuralChange) {
|
|
730
|
+
this.schedule();
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
if (selection.isCollapsed) {
|
|
734
|
+
if (this.structuralChange || this.drag || !this.projection && this.snapshot && (!this.layers.has(this.snapshot.anchor.pageNumber) || !this.layers.has(this.snapshot.focus.pageNumber))) {
|
|
735
|
+
this.schedule();
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
if (this.snapshot) this.clear();
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
const anchor = this.anchorFor(selection.anchorNode, selection.anchorOffset);
|
|
742
|
+
const focus = this.anchorFor(selection.focusNode, selection.focusOffset);
|
|
743
|
+
if (anchor && focus) {
|
|
744
|
+
if (this.drag && !this.drag.started) {
|
|
745
|
+
if (!this.drag.shift && !this.drag.moved) this.drag.anchor = anchor;
|
|
746
|
+
this.drag.started = true;
|
|
747
|
+
}
|
|
748
|
+
const savedAnchor = this.projection && samePoint(
|
|
749
|
+
this.projection.anchor,
|
|
750
|
+
selection.anchorNode,
|
|
751
|
+
selection.anchorOffset
|
|
752
|
+
) ? this.snapshot?.anchor : void 0;
|
|
753
|
+
this.setSelection(this.drag?.anchor ?? savedAnchor ?? anchor, focus);
|
|
754
|
+
this.projection = {
|
|
755
|
+
anchor: { node: selection.anchorNode, offset: selection.anchorOffset },
|
|
756
|
+
focus: { node: selection.focusNode, offset: selection.focusOffset }
|
|
757
|
+
};
|
|
758
|
+
} else if (!this.structuralChange && !this.drag && (selection.anchorNode || selection.focusNode))
|
|
759
|
+
this.clear();
|
|
760
|
+
};
|
|
761
|
+
schedule() {
|
|
762
|
+
if (!this.container || this.frame !== null) return;
|
|
763
|
+
this.frame = this.container.ownerDocument.defaultView.requestAnimationFrame(() => {
|
|
764
|
+
this.frame = null;
|
|
765
|
+
this.project();
|
|
766
|
+
this.structuralChange = false;
|
|
767
|
+
this.flushKeyboard();
|
|
768
|
+
if (this.drag) this.autoScroll();
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
project() {
|
|
772
|
+
const range = this.ordered();
|
|
773
|
+
if (!range || !this.container) return;
|
|
774
|
+
const visible = [...this.layers.values()].filter((page) => this.includesPage(page.pageNumber) && page.nodes.length).sort((a, b) => a.pageNumber - b.pageNumber);
|
|
775
|
+
const first = visible[0];
|
|
776
|
+
const last = visible.at(-1);
|
|
777
|
+
if (!first || !last) return;
|
|
778
|
+
const start = first.point(
|
|
779
|
+
this.bounds(first.pageNumber, first.content.text.length).from
|
|
780
|
+
);
|
|
781
|
+
const end = last.point(
|
|
782
|
+
this.bounds(last.pageNumber, last.content.text.length).to,
|
|
783
|
+
true
|
|
784
|
+
);
|
|
785
|
+
if (!start || !end) return;
|
|
786
|
+
const backwards = compare(this.snapshot.anchor, this.snapshot.focus) > 0;
|
|
787
|
+
const anchor = backwards ? end : start;
|
|
788
|
+
const focus = backwards ? start : end;
|
|
789
|
+
const selection = this.container.ownerDocument.getSelection();
|
|
790
|
+
this.projection = { anchor, focus };
|
|
791
|
+
if (!samePoint(anchor, selection.anchorNode, selection.anchorOffset) || !samePoint(focus, selection.focusNode, selection.focusOffset))
|
|
792
|
+
selection.setBaseAndExtent(
|
|
793
|
+
anchor.node,
|
|
794
|
+
anchor.offset,
|
|
795
|
+
focus.node,
|
|
796
|
+
focus.offset
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
hydrate() {
|
|
800
|
+
if (!this.snapshot || this.complete() || !this.container) {
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
const range = this.ordered();
|
|
804
|
+
const owner = this.container.ownerDocument;
|
|
805
|
+
const controller = new AbortController();
|
|
806
|
+
this.work = controller;
|
|
807
|
+
const generation = this.generation;
|
|
808
|
+
let next = range.start.pageNumber;
|
|
809
|
+
const worker = async () => {
|
|
810
|
+
while (next <= range.end.pageNumber) {
|
|
811
|
+
controller.signal.throwIfAborted();
|
|
812
|
+
const n = next++;
|
|
813
|
+
const cached = this.pages.get(n);
|
|
814
|
+
if (cached) {
|
|
815
|
+
const bounds = this.bounds(n, cached.content.text.length);
|
|
816
|
+
if (bounds.from === cached.from && bounds.to === cached.to) continue;
|
|
817
|
+
}
|
|
818
|
+
const page = await this.options.loadPage(n);
|
|
819
|
+
controller.signal.throwIfAborted();
|
|
820
|
+
await withSelectionPage(
|
|
821
|
+
page,
|
|
822
|
+
owner,
|
|
823
|
+
controller.signal,
|
|
824
|
+
(page2) => this.remember(page2)
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
};
|
|
828
|
+
void Promise.all([worker(), worker()]).then(() => {
|
|
829
|
+
if (!controller.signal.aborted && generation === this.generation) {
|
|
830
|
+
this.finishSelection();
|
|
831
|
+
this.schedule();
|
|
832
|
+
}
|
|
833
|
+
}).catch((error) => {
|
|
834
|
+
if (!controller.signal.aborted && generation === this.generation && this.snapshot) {
|
|
835
|
+
this.publish({ ...this.snapshot, status: "error", error });
|
|
836
|
+
controller.abort();
|
|
837
|
+
}
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
caret(x, y) {
|
|
841
|
+
const owner = this.container.ownerDocument;
|
|
842
|
+
const doc = owner;
|
|
843
|
+
const position = doc.caretPositionFromPoint?.(x, y);
|
|
844
|
+
if (position) return this.anchorFor(position.offsetNode, position.offset);
|
|
845
|
+
const range = doc.caretRangeFromPoint?.(x, y);
|
|
846
|
+
return range ? this.anchorFor(range.startContainer, range.startOffset) : null;
|
|
847
|
+
}
|
|
848
|
+
autoScroll() {
|
|
849
|
+
if (!this.drag?.moved || !this.container) return;
|
|
850
|
+
const bounds = this.container.getBoundingClientRect();
|
|
851
|
+
const { x, y } = this.drag;
|
|
852
|
+
const edge = Math.min(40, bounds.height / 4);
|
|
853
|
+
const delta = y < bounds.top + edge ? -Math.min(24, (bounds.top + edge - y) * 0.5) : y > bounds.bottom - edge ? Math.min(24, (y - bounds.bottom + edge) * 0.5) : 0;
|
|
854
|
+
if (delta) this.container.scrollTop += delta;
|
|
855
|
+
const focus = this.caret(
|
|
856
|
+
Math.max(bounds.left + 1, Math.min(x, bounds.right - 1)),
|
|
857
|
+
Math.max(bounds.top + 1, Math.min(y, bounds.bottom - 1))
|
|
858
|
+
);
|
|
859
|
+
if (focus) this.setSelection(this.drag.anchor, focus);
|
|
860
|
+
if (delta) this.schedule();
|
|
861
|
+
}
|
|
862
|
+
flushKeyboard() {
|
|
863
|
+
if (!this.snapshot || !this.keyboardMoves.length || this.keyboardWork)
|
|
864
|
+
return;
|
|
865
|
+
const page = this.layers.get(this.snapshot.focus.pageNumber);
|
|
866
|
+
if (!page) {
|
|
867
|
+
this.options.revealPage?.(this.snapshot.focus.pageNumber);
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
const move = this.keyboardMoves.shift();
|
|
871
|
+
const native = this.container.ownerDocument.getSelection();
|
|
872
|
+
if (!native?.modify) return;
|
|
873
|
+
const { anchor, focus } = this.snapshot;
|
|
874
|
+
native.modify("extend", move.direction, move.granularity);
|
|
875
|
+
const next = this.anchorFor(native.focusNode, native.focusOffset);
|
|
876
|
+
if (next && !sameAnchor(next, focus)) {
|
|
877
|
+
this.setSelection(anchor, next);
|
|
878
|
+
} else {
|
|
879
|
+
const point = page.point(focus.offset);
|
|
880
|
+
const rtl = point && this.container.ownerDocument.defaultView.getComputedStyle(
|
|
881
|
+
point.node.parentElement
|
|
882
|
+
).direction === "rtl";
|
|
883
|
+
const backwards = move.direction === "backward" || move.direction === (rtl ? "right" : "left");
|
|
884
|
+
if (move.granularity === "lineboundary" || (backwards ? focus.offset > (move.granularity === "line" ? page.content.lineBreaks[0] ?? 0 : 0) : focus.offset < (move.granularity === "line" ? page.content.lineBreaks.at(-1) ?? page.content.text.length : page.content.text.length))) {
|
|
885
|
+
this.schedule();
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
const n = focus.pageNumber + (backwards ? -1 : 1);
|
|
889
|
+
if (n >= 1 && n <= this.options.pageCount) {
|
|
890
|
+
if (!backwards) {
|
|
891
|
+
this.setSelection(anchor, { pageNumber: n, offset: 0 });
|
|
892
|
+
this.options.revealPage?.(n);
|
|
893
|
+
} else {
|
|
894
|
+
const generation = this.generation;
|
|
895
|
+
const controller = new AbortController();
|
|
896
|
+
this.keyboardWork = controller;
|
|
897
|
+
const owner = this.container.ownerDocument;
|
|
898
|
+
void this.options.loadPage(n).then(
|
|
899
|
+
(proxy) => withSelectionPage(
|
|
900
|
+
proxy,
|
|
901
|
+
owner,
|
|
902
|
+
controller.signal,
|
|
903
|
+
(page2) => page2.content.text.length
|
|
904
|
+
)
|
|
905
|
+
).then((length) => {
|
|
906
|
+
if (controller.signal.aborted || generation !== this.generation || !this.container)
|
|
907
|
+
return;
|
|
908
|
+
this.setSelection(anchor, { pageNumber: n, offset: length });
|
|
909
|
+
this.options.revealPage?.(n);
|
|
910
|
+
}).catch(() => {
|
|
911
|
+
}).finally(() => {
|
|
912
|
+
if (this.keyboardWork === controller) this.keyboardWork = null;
|
|
913
|
+
this.schedule();
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
if (this.keyboardMoves.length) this.schedule();
|
|
919
|
+
}
|
|
920
|
+
connect(container) {
|
|
921
|
+
this.connectedCleanup?.();
|
|
922
|
+
this.container = container;
|
|
923
|
+
const owner = container.ownerDocument;
|
|
924
|
+
const events = new AbortController();
|
|
925
|
+
const { signal } = events;
|
|
926
|
+
const down = (event) => {
|
|
927
|
+
if (event.defaultPrevented) return;
|
|
928
|
+
const target = event.target instanceof Element ? event.target : null;
|
|
929
|
+
if (target?.closest("button, a, [role=button], [data-selection-tooltip]"))
|
|
930
|
+
return;
|
|
931
|
+
if (!container.contains(event.target)) {
|
|
932
|
+
this.clear();
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
if (event.defaultPrevented || event.button !== 0 || event.pointerType === "touch" || !container.contains(event.target))
|
|
936
|
+
return;
|
|
937
|
+
const point = this.caret(event.clientX, event.clientY);
|
|
938
|
+
if (!point) return;
|
|
939
|
+
const anchor = event.shiftKey && this.snapshot ? this.snapshot.anchor : point;
|
|
940
|
+
if (!event.shiftKey) this.clear();
|
|
941
|
+
this.drag = {
|
|
942
|
+
anchor,
|
|
943
|
+
x: event.clientX,
|
|
944
|
+
y: event.clientY,
|
|
945
|
+
pointerId: event.pointerId,
|
|
946
|
+
moved: false,
|
|
947
|
+
started: false,
|
|
948
|
+
shift: event.shiftKey
|
|
949
|
+
};
|
|
950
|
+
this.schedule();
|
|
951
|
+
};
|
|
952
|
+
const move = (event) => {
|
|
953
|
+
if (!this.drag || event.pointerId !== this.drag.pointerId) return;
|
|
954
|
+
this.drag.moved ||= Math.abs(this.drag.x - event.clientX) + Math.abs(this.drag.y - event.clientY) > 2;
|
|
955
|
+
this.drag.x = event.clientX;
|
|
956
|
+
this.drag.y = event.clientY;
|
|
957
|
+
this.schedule();
|
|
958
|
+
};
|
|
959
|
+
const up = () => {
|
|
960
|
+
this.capture();
|
|
961
|
+
this.drag = null;
|
|
962
|
+
};
|
|
963
|
+
owner.addEventListener("selectionchange", this.capture, { signal });
|
|
964
|
+
owner.addEventListener("pointerdown", down, { signal });
|
|
965
|
+
owner.addEventListener("pointermove", move, { signal });
|
|
966
|
+
owner.addEventListener("pointerup", up, { signal });
|
|
967
|
+
owner.addEventListener("pointercancel", up, { signal });
|
|
968
|
+
owner.defaultView.addEventListener("blur", up, { signal });
|
|
969
|
+
const cancelKeyboard = () => {
|
|
970
|
+
this.keyboardMoves = [];
|
|
971
|
+
this.keyboardWork?.abort();
|
|
972
|
+
this.keyboardWork = null;
|
|
973
|
+
};
|
|
974
|
+
container.addEventListener(
|
|
975
|
+
"focusout",
|
|
976
|
+
(event) => {
|
|
977
|
+
if (!container.contains(event.relatedTarget))
|
|
978
|
+
cancelKeyboard();
|
|
979
|
+
},
|
|
980
|
+
{ signal }
|
|
981
|
+
);
|
|
982
|
+
owner.defaultView.addEventListener("blur", cancelKeyboard, { signal });
|
|
983
|
+
container.addEventListener(
|
|
984
|
+
"keydown",
|
|
985
|
+
(event) => {
|
|
986
|
+
if (event.defaultPrevented || event.target instanceof Element && event.target.closest(
|
|
987
|
+
'input, textarea, [contenteditable]:not([contenteditable="false"])'
|
|
988
|
+
))
|
|
989
|
+
return;
|
|
990
|
+
if (event.key === "Escape" && this.snapshot) {
|
|
991
|
+
this.clear();
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
if (!event.shiftKey || !this.snapshot) return;
|
|
995
|
+
const directions = {
|
|
996
|
+
ArrowLeft: "left",
|
|
997
|
+
ArrowRight: "right",
|
|
998
|
+
ArrowUp: "backward",
|
|
999
|
+
ArrowDown: "forward",
|
|
1000
|
+
Home: "backward",
|
|
1001
|
+
End: "forward"
|
|
1002
|
+
};
|
|
1003
|
+
const direction = directions[event.key];
|
|
1004
|
+
if (!direction) return;
|
|
1005
|
+
const native = owner.getSelection();
|
|
1006
|
+
if (!native?.modify) return;
|
|
1007
|
+
event.preventDefault();
|
|
1008
|
+
const boundary = event.key === "Home" || event.key === "End" || event.metaKey && (event.key === "ArrowLeft" || event.key === "ArrowRight");
|
|
1009
|
+
const granularity = boundary ? "lineboundary" : event.key === "ArrowUp" || event.key === "ArrowDown" ? "line" : event.ctrlKey || event.altKey ? "word" : "character";
|
|
1010
|
+
this.keyboardMoves.push({ direction, granularity });
|
|
1011
|
+
this.schedule();
|
|
1012
|
+
},
|
|
1013
|
+
{ signal }
|
|
1014
|
+
);
|
|
1015
|
+
const cleanup = () => {
|
|
1016
|
+
events.abort();
|
|
1017
|
+
if (this.frame !== null)
|
|
1018
|
+
owner.defaultView.cancelAnimationFrame(this.frame);
|
|
1019
|
+
this.frame = null;
|
|
1020
|
+
this.clear();
|
|
1021
|
+
this.container = null;
|
|
1022
|
+
this.connectedCleanup = null;
|
|
1023
|
+
};
|
|
1024
|
+
this.connectedCleanup = cleanup;
|
|
1025
|
+
this.hydrate();
|
|
1026
|
+
this.schedule();
|
|
1027
|
+
return cleanup;
|
|
1028
|
+
}
|
|
1029
|
+
};
|
|
1030
|
+
|
|
1031
|
+
// src/lib/viewport-zoom.ts
|
|
1032
|
+
var viewportZoomHandlers = /* @__PURE__ */ new WeakMap();
|
|
1033
|
+
function applyViewportZoom(viewport, zoom) {
|
|
1034
|
+
if (viewport) viewportZoomHandlers.get(viewport)?.(zoom);
|
|
1035
|
+
}
|
|
1036
|
+
function registerViewportZoom(viewport, apply) {
|
|
1037
|
+
viewportZoomHandlers.set(viewport, apply);
|
|
1038
|
+
return () => {
|
|
1039
|
+
if (viewportZoomHandlers.get(viewport) === apply)
|
|
1040
|
+
viewportZoomHandlers.delete(viewport);
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
|
|
222
1044
|
// src/lib/zoom.ts
|
|
223
1045
|
var USE_LAYOUT_ZOOM = typeof navigator !== "undefined" && /AppleWebKit/i.test(navigator.userAgent) && !/Chrome|Chromium|Android/i.test(navigator.userAgent);
|
|
224
1046
|
var getFitWidthZoom = (containerWidth, viewports, zoomOptions) => {
|
|
@@ -255,6 +1077,11 @@ var PDFStore = createZustandContext(
|
|
|
255
1077
|
renderColorMapRef.current = initialColorScheme === "dark" ? createDarkModeColorMap(initialDarkModeColors) : null;
|
|
256
1078
|
return createStore((set, get) => ({
|
|
257
1079
|
pdfDocumentProxy: initialState.pdfDocumentProxy,
|
|
1080
|
+
selection: new PDFSelectionController({
|
|
1081
|
+
pageCount: initialState.pdfDocumentProxy.numPages,
|
|
1082
|
+
loadPage: (page) => get().loadPdfPageProxy(page),
|
|
1083
|
+
revealPage: (page) => get().virtualizer?.scrollToIndex(page - 1, { align: "auto" })
|
|
1084
|
+
}),
|
|
258
1085
|
zoom: initialState.zoom,
|
|
259
1086
|
isZoomFitWidth: initialState.isZoomFitWidth ?? false,
|
|
260
1087
|
zoomOptions: {
|
|
@@ -271,6 +1098,7 @@ var PDFStore = createZustandContext(
|
|
|
271
1098
|
minZoom,
|
|
272
1099
|
maxZoom
|
|
273
1100
|
);
|
|
1101
|
+
applyViewportZoom(state.viewportRef.current, newZoom);
|
|
274
1102
|
return newZoom === state.zoom && isZoomFitWidth === state.isZoomFitWidth ? state : { zoom: newZoom, isZoomFitWidth };
|
|
275
1103
|
});
|
|
276
1104
|
},
|
|
@@ -285,7 +1113,8 @@ var PDFStore = createZustandContext(
|
|
|
285
1113
|
get().updateZoom(clampedZoom, true);
|
|
286
1114
|
return clampedZoom;
|
|
287
1115
|
},
|
|
288
|
-
currentPage: 1,
|
|
1116
|
+
currentPage: initialState.initialPage ?? 1,
|
|
1117
|
+
initialPage: initialState.initialPage ?? 1,
|
|
289
1118
|
setCurrentPage: (val) => {
|
|
290
1119
|
set(
|
|
291
1120
|
(state) => state.currentPage === val ? state : { currentPage: val }
|
|
@@ -310,9 +1139,18 @@ var PDFStore = createZustandContext(
|
|
|
310
1139
|
);
|
|
311
1140
|
},
|
|
312
1141
|
pageProxies: initialState.pageProxies,
|
|
1142
|
+
pagesLoaded: initialState.pageProxies.length === initialState.pdfDocumentProxy.numPages,
|
|
1143
|
+
loadPdfPageProxies: () => initialState.pageResources ? initialState.pageResources.loadAll() : Promise.resolve(get().pageProxies),
|
|
1144
|
+
isPageLoaded: (pageNumber) => Boolean(
|
|
1145
|
+
initialState.pageResources?.get(pageNumber) ?? get().pageProxies[pageNumber - 1]
|
|
1146
|
+
),
|
|
1147
|
+
loadPdfPageProxy: (pageNumber) => initialState.pageResources ? initialState.pageResources.load(pageNumber) : Promise.resolve().then(() => get().getPdfPageProxy(pageNumber)),
|
|
313
1148
|
getPdfPageProxy: (pageNumber) => {
|
|
314
|
-
const proxy = get().pageProxies[pageNumber - 1];
|
|
315
|
-
if (!proxy)
|
|
1149
|
+
const proxy = initialState.pageResources?.get(pageNumber) ?? get().pageProxies[pageNumber - 1];
|
|
1150
|
+
if (!proxy)
|
|
1151
|
+
throw new Error(
|
|
1152
|
+
initialState.pageResources && Number.isInteger(pageNumber) && pageNumber >= 1 && pageNumber <= initialState.pdfDocumentProxy.numPages ? `Page ${pageNumber} is not loaded. Await loadPdfPageProxy(${pageNumber}) first.` : `Page ${pageNumber} does not exist`
|
|
1153
|
+
);
|
|
316
1154
|
return proxy;
|
|
317
1155
|
},
|
|
318
1156
|
textContent: [],
|
|
@@ -665,23 +1503,44 @@ var AnnotationTooltip = ({
|
|
|
665
1503
|
};
|
|
666
1504
|
var createAnnotationStore = () => createStore((set) => ({
|
|
667
1505
|
annotations: [],
|
|
668
|
-
addAnnotation: (annotation) => set((state) =>
|
|
669
|
-
annotations
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
1506
|
+
addAnnotation: (annotation) => set((state) => {
|
|
1507
|
+
const annotations = [...state.annotations, annotation];
|
|
1508
|
+
updatePageIndex(state.annotations, annotations, /* @__PURE__ */ new Map(), [
|
|
1509
|
+
annotation
|
|
1510
|
+
]);
|
|
1511
|
+
return { annotations };
|
|
1512
|
+
}),
|
|
1513
|
+
updateAnnotation: (id, updates) => set((state) => {
|
|
1514
|
+
const changes = /* @__PURE__ */ new Map();
|
|
1515
|
+
const keys = Object.keys(updates);
|
|
1516
|
+
const annotations = state.annotations.map((annotation) => {
|
|
1517
|
+
if (annotation.id !== id || keys.every((key) => Object.is(annotation[key], updates[key])))
|
|
1518
|
+
return annotation;
|
|
1519
|
+
const updated = changes.get(annotation) ?? {
|
|
674
1520
|
...annotation,
|
|
675
1521
|
...updates
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
})
|
|
684
|
-
|
|
1522
|
+
};
|
|
1523
|
+
changes.set(annotation, updated);
|
|
1524
|
+
return updated;
|
|
1525
|
+
});
|
|
1526
|
+
if (!changes.size) return state;
|
|
1527
|
+
updatePageIndex(state.annotations, annotations, changes);
|
|
1528
|
+
return { annotations };
|
|
1529
|
+
}),
|
|
1530
|
+
deleteAnnotation: (id) => set((state) => {
|
|
1531
|
+
const changes = /* @__PURE__ */ new Map();
|
|
1532
|
+
const annotations = state.annotations.filter((annotation) => {
|
|
1533
|
+
if (annotation.id !== id) return true;
|
|
1534
|
+
changes.set(annotation, null);
|
|
1535
|
+
return false;
|
|
1536
|
+
});
|
|
1537
|
+
if (!changes.size) return state;
|
|
1538
|
+
updatePageIndex(state.annotations, annotations, changes);
|
|
1539
|
+
return { annotations };
|
|
1540
|
+
}),
|
|
1541
|
+
setAnnotations: (annotations) => set(
|
|
1542
|
+
(state) => state.annotations === annotations ? state : { annotations }
|
|
1543
|
+
)
|
|
685
1544
|
}));
|
|
686
1545
|
var AnnotationsStore = createZustandContext(
|
|
687
1546
|
() => createAnnotationStore()
|
|
@@ -713,9 +1572,7 @@ function annotationsByPage(annotations) {
|
|
|
713
1572
|
if (index) return index;
|
|
714
1573
|
index = /* @__PURE__ */ new Map();
|
|
715
1574
|
for (const annotation of annotations) {
|
|
716
|
-
const pages =
|
|
717
|
-
for (const rect of annotation.highlights) pages.add(rect.pageNumber);
|
|
718
|
-
for (const rect of annotation.underlines ?? []) pages.add(rect.pageNumber);
|
|
1575
|
+
const pages = annotationPages(annotation);
|
|
719
1576
|
for (const page of pages) {
|
|
720
1577
|
let values = index.get(page);
|
|
721
1578
|
if (!values) {
|
|
@@ -728,6 +1585,45 @@ function annotationsByPage(annotations) {
|
|
|
728
1585
|
indexes.set(annotations, index);
|
|
729
1586
|
return index;
|
|
730
1587
|
}
|
|
1588
|
+
function annotationPages(annotation) {
|
|
1589
|
+
const pages = /* @__PURE__ */ new Set([annotation.pageNumber]);
|
|
1590
|
+
for (const rect of annotation.highlights) pages.add(rect.pageNumber);
|
|
1591
|
+
for (const rect of annotation.underlines ?? []) pages.add(rect.pageNumber);
|
|
1592
|
+
return pages;
|
|
1593
|
+
}
|
|
1594
|
+
function isOnPage(annotation, page) {
|
|
1595
|
+
return annotation.pageNumber === page || annotation.highlights.some((rect) => rect.pageNumber === page) || annotation.underlines?.some((rect) => rect.pageNumber === page);
|
|
1596
|
+
}
|
|
1597
|
+
function updatePageIndex(previous, next, changes, appended = []) {
|
|
1598
|
+
const previousIndex = indexes.get(previous);
|
|
1599
|
+
if (!previousIndex) return;
|
|
1600
|
+
const index = new Map(previousIndex);
|
|
1601
|
+
const affected = /* @__PURE__ */ new Set();
|
|
1602
|
+
const movedTo = /* @__PURE__ */ new Set();
|
|
1603
|
+
for (const [before, after] of changes) {
|
|
1604
|
+
const oldPages = annotationPages(before);
|
|
1605
|
+
for (const page of oldPages) affected.add(page);
|
|
1606
|
+
if (after)
|
|
1607
|
+
for (const page of annotationPages(after)) {
|
|
1608
|
+
affected.add(page);
|
|
1609
|
+
if (!oldPages.has(page)) movedTo.add(page);
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
for (const page of affected) {
|
|
1613
|
+
const values = movedTo.has(page) ? next.filter((annotation) => isOnPage(annotation, page)) : (previousIndex.get(page) ?? []).flatMap((annotation) => {
|
|
1614
|
+
if (!changes.has(annotation)) return [annotation];
|
|
1615
|
+
const updated = changes.get(annotation);
|
|
1616
|
+
return updated && isOnPage(updated, page) ? [updated] : [];
|
|
1617
|
+
});
|
|
1618
|
+
if (values.length) index.set(page, values);
|
|
1619
|
+
else index.delete(page);
|
|
1620
|
+
}
|
|
1621
|
+
for (const annotation of appended)
|
|
1622
|
+
for (const page of annotationPages(annotation)) {
|
|
1623
|
+
index.set(page, [...index.get(page) ?? [], annotation]);
|
|
1624
|
+
}
|
|
1625
|
+
indexes.set(next, index);
|
|
1626
|
+
}
|
|
731
1627
|
function usePageAnnotations(pageNumber) {
|
|
732
1628
|
return useAnnotations(
|
|
733
1629
|
useShallow(
|
|
@@ -1035,7 +1931,7 @@ var usePdfJump = () => {
|
|
|
1035
1931
|
}
|
|
1036
1932
|
let scrollOffset;
|
|
1037
1933
|
if (align === "center") {
|
|
1038
|
-
const viewportHeight = virtualizer.
|
|
1934
|
+
const viewportHeight = virtualizer.scrollRect?.height ?? 0;
|
|
1039
1935
|
const rectCenter = pageStart + rectTop + rectHeight / 2;
|
|
1040
1936
|
scrollOffset = rectCenter - viewportHeight / 2;
|
|
1041
1937
|
} else {
|
|
@@ -2108,6 +3004,69 @@ function applyContextRecolor(ctx, map, options) {
|
|
|
2108
3004
|
function removeContextRecolor(ctx) {
|
|
2109
3005
|
ctx[RECOLOR_CLEANUP]?.();
|
|
2110
3006
|
}
|
|
3007
|
+
|
|
3008
|
+
// src/lib/viewport-invalidation.ts
|
|
3009
|
+
var viewportInvalidationRegistry = /* @__PURE__ */ new WeakMap();
|
|
3010
|
+
var notifyCallbacks = (entry) => {
|
|
3011
|
+
entry.rafId = null;
|
|
3012
|
+
entry.callbacks.forEach((callback) => {
|
|
3013
|
+
callback();
|
|
3014
|
+
});
|
|
3015
|
+
};
|
|
3016
|
+
var scheduleInvalidation = (entry) => {
|
|
3017
|
+
if (entry.rafId !== null) {
|
|
3018
|
+
return;
|
|
3019
|
+
}
|
|
3020
|
+
entry.rafId = requestAnimationFrame(() => {
|
|
3021
|
+
notifyCallbacks(entry);
|
|
3022
|
+
});
|
|
3023
|
+
};
|
|
3024
|
+
var subscribeToViewportInvalidation = (viewport, callback) => {
|
|
3025
|
+
let entry = viewportInvalidationRegistry.get(viewport);
|
|
3026
|
+
if (!entry) {
|
|
3027
|
+
const callbacks = /* @__PURE__ */ new Set();
|
|
3028
|
+
const schedule = () => {
|
|
3029
|
+
const currentEntry = viewportInvalidationRegistry.get(viewport);
|
|
3030
|
+
if (!currentEntry) {
|
|
3031
|
+
return;
|
|
3032
|
+
}
|
|
3033
|
+
scheduleInvalidation(currentEntry);
|
|
3034
|
+
};
|
|
3035
|
+
entry = {
|
|
3036
|
+
callbacks,
|
|
3037
|
+
scrollListener: schedule,
|
|
3038
|
+
windowResizeListener: schedule,
|
|
3039
|
+
resizeObserver: typeof ResizeObserver === "undefined" ? void 0 : new ResizeObserver(schedule),
|
|
3040
|
+
rafId: null
|
|
3041
|
+
};
|
|
3042
|
+
viewport.addEventListener("scroll", entry.scrollListener, {
|
|
3043
|
+
passive: true
|
|
3044
|
+
});
|
|
3045
|
+
window.addEventListener("resize", entry.windowResizeListener, {
|
|
3046
|
+
passive: true
|
|
3047
|
+
});
|
|
3048
|
+
entry.resizeObserver?.observe(viewport);
|
|
3049
|
+
viewportInvalidationRegistry.set(viewport, entry);
|
|
3050
|
+
}
|
|
3051
|
+
entry.callbacks.add(callback);
|
|
3052
|
+
return () => {
|
|
3053
|
+
const currentEntry = viewportInvalidationRegistry.get(viewport);
|
|
3054
|
+
if (!currentEntry) {
|
|
3055
|
+
return;
|
|
3056
|
+
}
|
|
3057
|
+
currentEntry.callbacks.delete(callback);
|
|
3058
|
+
if (currentEntry.callbacks.size > 0) {
|
|
3059
|
+
return;
|
|
3060
|
+
}
|
|
3061
|
+
if (currentEntry.rafId !== null) {
|
|
3062
|
+
cancelAnimationFrame(currentEntry.rafId);
|
|
3063
|
+
}
|
|
3064
|
+
viewport.removeEventListener("scroll", currentEntry.scrollListener);
|
|
3065
|
+
window.removeEventListener("resize", currentEntry.windowResizeListener);
|
|
3066
|
+
currentEntry.resizeObserver?.disconnect();
|
|
3067
|
+
viewportInvalidationRegistry.delete(viewport);
|
|
3068
|
+
};
|
|
3069
|
+
};
|
|
2111
3070
|
var listeners = /* @__PURE__ */ new Set();
|
|
2112
3071
|
var media;
|
|
2113
3072
|
var getSnapshot = () => Math.min(window.devicePixelRatio || 1, 3);
|
|
@@ -2369,7 +3328,53 @@ var useCanvasLayer = ({ background }) => {
|
|
|
2369
3328
|
releaseBuffer();
|
|
2370
3329
|
throw error;
|
|
2371
3330
|
}
|
|
3331
|
+
let continuation = null;
|
|
3332
|
+
let idleTimer = null;
|
|
3333
|
+
let unsubscribeViewport = null;
|
|
3334
|
+
let priorityStopped = false;
|
|
3335
|
+
const clearPriorityWait = () => {
|
|
3336
|
+
if (idleTimer !== null) clearTimeout(idleTimer);
|
|
3337
|
+
idleTimer = null;
|
|
3338
|
+
unsubscribeViewport?.();
|
|
3339
|
+
unsubscribeViewport = null;
|
|
3340
|
+
};
|
|
3341
|
+
const resumeWhenVisible = () => {
|
|
3342
|
+
if (priorityStopped || !continuation) return;
|
|
3343
|
+
const { virtualizer, viewportRef } = store.getState();
|
|
3344
|
+
const item = virtualizer?.getVirtualItems().find((candidate) => candidate.index === pageNumber - 1);
|
|
3345
|
+
const offset3 = virtualizer?.scrollOffset;
|
|
3346
|
+
const height = virtualizer?.scrollRect?.height;
|
|
3347
|
+
if (virtualizer?.isScrolling && item && offset3 != null && height && (item.end < offset3 || item.start > offset3 + height)) {
|
|
3348
|
+
if (!unsubscribeViewport && viewportRef.current) {
|
|
3349
|
+
unsubscribeViewport = subscribeToViewportInvalidation(
|
|
3350
|
+
viewportRef.current,
|
|
3351
|
+
resumeWhenVisible
|
|
3352
|
+
);
|
|
3353
|
+
}
|
|
3354
|
+
if (idleTimer === null) {
|
|
3355
|
+
idleTimer = setTimeout(() => {
|
|
3356
|
+
idleTimer = null;
|
|
3357
|
+
resumeWhenVisible();
|
|
3358
|
+
}, 160);
|
|
3359
|
+
}
|
|
3360
|
+
return;
|
|
3361
|
+
}
|
|
3362
|
+
clearPriorityWait();
|
|
3363
|
+
const proceed = continuation;
|
|
3364
|
+
continuation = null;
|
|
3365
|
+
proceed();
|
|
3366
|
+
};
|
|
3367
|
+
const stopPriority = () => {
|
|
3368
|
+
priorityStopped = true;
|
|
3369
|
+
continuation = null;
|
|
3370
|
+
clearPriorityWait();
|
|
3371
|
+
};
|
|
3372
|
+
renderingTask.onContinue = (proceed) => {
|
|
3373
|
+
continuation = proceed;
|
|
3374
|
+
resumeWhenVisible();
|
|
3375
|
+
};
|
|
2372
3376
|
renderingTask.promise.then(() => {
|
|
3377
|
+
stopPriority();
|
|
2373
3378
|
if (cancelled) {
|
|
2374
3379
|
restoreRecolor?.();
|
|
2375
3380
|
releaseBuffer();
|
|
@@ -2414,6 +3419,7 @@ var useCanvasLayer = ({ background }) => {
|
|
|
2414
3419
|
releaseBuffer();
|
|
2415
3420
|
}
|
|
2416
3421
|
}).catch((error) => {
|
|
3422
|
+
stopPriority();
|
|
2417
3423
|
restoreRecolor?.();
|
|
2418
3424
|
releaseBuffer();
|
|
2419
3425
|
if (cancelled) return;
|
|
@@ -2423,102 +3429,38 @@ var useCanvasLayer = ({ background }) => {
|
|
|
2423
3429
|
});
|
|
2424
3430
|
return () => {
|
|
2425
3431
|
cancelled = true;
|
|
3432
|
+
stopPriority();
|
|
2426
3433
|
void renderingTask.cancel();
|
|
2427
3434
|
};
|
|
2428
|
-
}, [
|
|
2429
|
-
pdfPageProxy,
|
|
2430
|
-
background,
|
|
2431
|
-
dpr,
|
|
2432
|
-
zoom,
|
|
2433
|
-
isResizing,
|
|
2434
|
-
isPinching,
|
|
2435
|
-
pageNumber,
|
|
2436
|
-
markPageRendered,
|
|
2437
|
-
docId,
|
|
2438
|
-
recolor,
|
|
2439
|
-
recolorKey,
|
|
2440
|
-
store
|
|
2441
|
-
]);
|
|
2442
|
-
useEffect(() => {
|
|
2443
|
-
const canvas = canvasRef.current;
|
|
2444
|
-
return () => {
|
|
2445
|
-
unmarkPageRendered(pageNumber);
|
|
2446
|
-
if (canvas) {
|
|
2447
|
-
canvas.width = 0;
|
|
2448
|
-
canvas.height = 0;
|
|
2449
|
-
}
|
|
2450
|
-
paintedRef.current = null;
|
|
2451
|
-
};
|
|
2452
|
-
}, [pageNumber, unmarkPageRendered]);
|
|
2453
|
-
return {
|
|
2454
|
-
canvasRef
|
|
2455
|
-
};
|
|
2456
|
-
};
|
|
2457
|
-
|
|
2458
|
-
// src/lib/viewport-invalidation.ts
|
|
2459
|
-
var viewportInvalidationRegistry = /* @__PURE__ */ new WeakMap();
|
|
2460
|
-
var notifyCallbacks = (entry) => {
|
|
2461
|
-
entry.rafId = null;
|
|
2462
|
-
entry.callbacks.forEach((callback) => {
|
|
2463
|
-
callback();
|
|
2464
|
-
});
|
|
2465
|
-
};
|
|
2466
|
-
var scheduleInvalidation = (entry) => {
|
|
2467
|
-
if (entry.rafId !== null) {
|
|
2468
|
-
return;
|
|
2469
|
-
}
|
|
2470
|
-
entry.rafId = requestAnimationFrame(() => {
|
|
2471
|
-
notifyCallbacks(entry);
|
|
2472
|
-
});
|
|
2473
|
-
};
|
|
2474
|
-
var subscribeToViewportInvalidation = (viewport, callback) => {
|
|
2475
|
-
let entry = viewportInvalidationRegistry.get(viewport);
|
|
2476
|
-
if (!entry) {
|
|
2477
|
-
const callbacks = /* @__PURE__ */ new Set();
|
|
2478
|
-
const schedule = () => {
|
|
2479
|
-
const currentEntry = viewportInvalidationRegistry.get(viewport);
|
|
2480
|
-
if (!currentEntry) {
|
|
2481
|
-
return;
|
|
2482
|
-
}
|
|
2483
|
-
scheduleInvalidation(currentEntry);
|
|
2484
|
-
};
|
|
2485
|
-
entry = {
|
|
2486
|
-
callbacks,
|
|
2487
|
-
scrollListener: schedule,
|
|
2488
|
-
windowResizeListener: schedule,
|
|
2489
|
-
resizeObserver: typeof ResizeObserver === "undefined" ? void 0 : new ResizeObserver(schedule),
|
|
2490
|
-
rafId: null
|
|
3435
|
+
}, [
|
|
3436
|
+
pdfPageProxy,
|
|
3437
|
+
background,
|
|
3438
|
+
dpr,
|
|
3439
|
+
zoom,
|
|
3440
|
+
isResizing,
|
|
3441
|
+
isPinching,
|
|
3442
|
+
pageNumber,
|
|
3443
|
+
markPageRendered,
|
|
3444
|
+
docId,
|
|
3445
|
+
recolor,
|
|
3446
|
+
recolorKey,
|
|
3447
|
+
store
|
|
3448
|
+
]);
|
|
3449
|
+
useEffect(() => {
|
|
3450
|
+
const canvas = canvasRef.current;
|
|
3451
|
+
return () => {
|
|
3452
|
+
unmarkPageRendered(pageNumber);
|
|
3453
|
+
if (canvas) {
|
|
3454
|
+
canvas.width = 0;
|
|
3455
|
+
canvas.height = 0;
|
|
3456
|
+
}
|
|
3457
|
+
paintedRef.current = null;
|
|
2491
3458
|
};
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
window.addEventListener("resize", entry.windowResizeListener, {
|
|
2496
|
-
passive: true
|
|
2497
|
-
});
|
|
2498
|
-
entry.resizeObserver?.observe(viewport);
|
|
2499
|
-
viewportInvalidationRegistry.set(viewport, entry);
|
|
2500
|
-
}
|
|
2501
|
-
entry.callbacks.add(callback);
|
|
2502
|
-
return () => {
|
|
2503
|
-
const currentEntry = viewportInvalidationRegistry.get(viewport);
|
|
2504
|
-
if (!currentEntry) {
|
|
2505
|
-
return;
|
|
2506
|
-
}
|
|
2507
|
-
currentEntry.callbacks.delete(callback);
|
|
2508
|
-
if (currentEntry.callbacks.size > 0) {
|
|
2509
|
-
return;
|
|
2510
|
-
}
|
|
2511
|
-
if (currentEntry.rafId !== null) {
|
|
2512
|
-
cancelAnimationFrame(currentEntry.rafId);
|
|
2513
|
-
}
|
|
2514
|
-
viewport.removeEventListener("scroll", currentEntry.scrollListener);
|
|
2515
|
-
window.removeEventListener("resize", currentEntry.windowResizeListener);
|
|
2516
|
-
currentEntry.resizeObserver?.disconnect();
|
|
2517
|
-
viewportInvalidationRegistry.delete(viewport);
|
|
3459
|
+
}, [pageNumber, unmarkPageRendered]);
|
|
3460
|
+
return {
|
|
3461
|
+
canvasRef
|
|
2518
3462
|
};
|
|
2519
3463
|
};
|
|
2520
|
-
|
|
2521
|
-
// src/hooks/layers/useDetailCanvasLayer.tsx
|
|
2522
3464
|
var DETAIL_RENDER_IDLE_MS = 80;
|
|
2523
3465
|
var SCROLL_POLL_MS = 160;
|
|
2524
3466
|
var ZOOM_DEBOUNCE_MS = 200;
|
|
@@ -2976,6 +3918,114 @@ var mergeTextRuns = (rects) => {
|
|
|
2976
3918
|
});
|
|
2977
3919
|
};
|
|
2978
3920
|
|
|
3921
|
+
// src/lib/selection-text.ts
|
|
3922
|
+
function textLayer(node) {
|
|
3923
|
+
const element = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
|
|
3924
|
+
return element?.closest(".textLayer[data-page-number]") ?? null;
|
|
3925
|
+
}
|
|
3926
|
+
function getPdfSelectionText(selection, container, options = {}) {
|
|
3927
|
+
if (!selection || selection.isCollapsed || selection.rangeCount !== 1)
|
|
3928
|
+
return null;
|
|
3929
|
+
const range = selection.getRangeAt(0);
|
|
3930
|
+
const first = textLayer(range.startContainer);
|
|
3931
|
+
const last = textLayer(range.endContainer);
|
|
3932
|
+
if (!first || !last || !container.contains(first) || !container.contains(last))
|
|
3933
|
+
return null;
|
|
3934
|
+
const layers = [
|
|
3935
|
+
...container.querySelectorAll(".textLayer[data-page-number]")
|
|
3936
|
+
].filter((layer) => range.intersectsNode(layer));
|
|
3937
|
+
let previousPage;
|
|
3938
|
+
for (const layer of layers) {
|
|
3939
|
+
const page = Number(layer.dataset.pageNumber);
|
|
3940
|
+
if (!Number.isInteger(page) || page < 1 || previousPage !== void 0 && page !== previousPage + 1)
|
|
3941
|
+
return null;
|
|
3942
|
+
previousPage = page;
|
|
3943
|
+
}
|
|
3944
|
+
const selectedLayers = new Set(layers);
|
|
3945
|
+
const root = range.commonAncestorContainer;
|
|
3946
|
+
const walker = container.ownerDocument.createTreeWalker(
|
|
3947
|
+
root,
|
|
3948
|
+
NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT,
|
|
3949
|
+
{
|
|
3950
|
+
acceptNode(node2) {
|
|
3951
|
+
if (node2.nodeType !== Node.TEXT_NODE && node2.tagName !== "BR")
|
|
3952
|
+
return NodeFilter.FILTER_SKIP;
|
|
3953
|
+
const layer = textLayer(node2);
|
|
3954
|
+
return layer && selectedLayers.has(layer) && range.intersectsNode(node2) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
|
|
3955
|
+
}
|
|
3956
|
+
}
|
|
3957
|
+
);
|
|
3958
|
+
const chunks = [];
|
|
3959
|
+
let previousLayer = null;
|
|
3960
|
+
let trailingLineBreaks = 0;
|
|
3961
|
+
let node = root.nodeType === Node.TEXT_NODE ? root : walker.nextNode();
|
|
3962
|
+
while (node) {
|
|
3963
|
+
const layer = textLayer(node);
|
|
3964
|
+
const start = node === range.startContainer ? range.startOffset : 0;
|
|
3965
|
+
const end = node === range.endContainer ? range.endOffset : node.textContent?.length ?? 0;
|
|
3966
|
+
const text = node.nodeType === Node.TEXT_NODE ? node.textContent.slice(start, end) : "\n";
|
|
3967
|
+
if (text) {
|
|
3968
|
+
if (previousLayer && previousLayer !== layer && trailingLineBreaks < 2) {
|
|
3969
|
+
chunks.push("\n".repeat(2 - trailingLineBreaks));
|
|
3970
|
+
trailingLineBreaks = 2;
|
|
3971
|
+
}
|
|
3972
|
+
chunks.push(text);
|
|
3973
|
+
let trailing = 0;
|
|
3974
|
+
for (let i = text.length - 1; i >= 0 && text[i] === "\n"; i--) trailing++;
|
|
3975
|
+
trailingLineBreaks = trailing === text.length ? trailingLineBreaks + trailing : trailing;
|
|
3976
|
+
previousLayer = layer;
|
|
3977
|
+
}
|
|
3978
|
+
node = walker.nextNode();
|
|
3979
|
+
}
|
|
3980
|
+
return normalizeSelectionText(chunks.join(""), options);
|
|
3981
|
+
}
|
|
3982
|
+
var documents = /* @__PURE__ */ new WeakMap();
|
|
3983
|
+
function registerPdfCopy(container, options, read) {
|
|
3984
|
+
const owner = container.ownerDocument;
|
|
3985
|
+
let binding = documents.get(owner);
|
|
3986
|
+
if (!binding) {
|
|
3987
|
+
const registrations = /* @__PURE__ */ new Set();
|
|
3988
|
+
const listener = (event) => {
|
|
3989
|
+
if (event.defaultPrevented || !event.clipboardData) return;
|
|
3990
|
+
const target = event.target;
|
|
3991
|
+
if (target instanceof Element && target.closest(
|
|
3992
|
+
'input, textarea, [contenteditable]:not([contenteditable="false"])'
|
|
3993
|
+
))
|
|
3994
|
+
return;
|
|
3995
|
+
for (const registration2 of registrations) {
|
|
3996
|
+
const settings = registration2.options();
|
|
3997
|
+
if (settings === false) continue;
|
|
3998
|
+
const persistent = registration2.read?.(settings);
|
|
3999
|
+
if (registration2.read && persistent === void 0) {
|
|
4000
|
+
event.preventDefault();
|
|
4001
|
+
break;
|
|
4002
|
+
}
|
|
4003
|
+
const text = persistent ?? getPdfSelectionText(
|
|
4004
|
+
owner.getSelection(),
|
|
4005
|
+
registration2.container,
|
|
4006
|
+
settings
|
|
4007
|
+
);
|
|
4008
|
+
if (text === null) continue;
|
|
4009
|
+
event.clipboardData.setData("text/plain", text);
|
|
4010
|
+
event.preventDefault();
|
|
4011
|
+
break;
|
|
4012
|
+
}
|
|
4013
|
+
};
|
|
4014
|
+
binding = { registrations, listener };
|
|
4015
|
+
documents.set(owner, binding);
|
|
4016
|
+
owner.addEventListener("copy", listener);
|
|
4017
|
+
}
|
|
4018
|
+
const registration = { container, options, read };
|
|
4019
|
+
binding.registrations.add(registration);
|
|
4020
|
+
return () => {
|
|
4021
|
+
binding.registrations.delete(registration);
|
|
4022
|
+
if (!binding.registrations.size) {
|
|
4023
|
+
owner.removeEventListener("copy", binding.listener);
|
|
4024
|
+
documents.delete(owner);
|
|
4025
|
+
}
|
|
4026
|
+
};
|
|
4027
|
+
}
|
|
4028
|
+
|
|
2979
4029
|
// src/hooks/useSelectionDimensions.tsx
|
|
2980
4030
|
var MERGE_THRESHOLD = 2;
|
|
2981
4031
|
var LAYER_ATTRIBUTION_TOLERANCE_PX = 4;
|
|
@@ -3023,7 +4073,19 @@ var mapSelectionRectsToLayers = (range) => {
|
|
|
3023
4073
|
};
|
|
3024
4074
|
var useSelectionDimensions = () => {
|
|
3025
4075
|
const store = PDFStore.useContext();
|
|
4076
|
+
const getText = (options) => {
|
|
4077
|
+
if (store.getState().selection?.getSnapshot())
|
|
4078
|
+
return store.getState().selection.getText(options);
|
|
4079
|
+
const container = store.getState().viewportRef.current;
|
|
4080
|
+
return container ? getPdfSelectionText(
|
|
4081
|
+
container.ownerDocument.getSelection(),
|
|
4082
|
+
container,
|
|
4083
|
+
options
|
|
4084
|
+
) : null;
|
|
4085
|
+
};
|
|
3026
4086
|
const getAnnotationDimension = () => {
|
|
4087
|
+
if (store.getState().selection?.getSnapshot())
|
|
4088
|
+
return store.getState().selection.getSelection() ?? void 0;
|
|
3027
4089
|
const selection = window.getSelection();
|
|
3028
4090
|
if (!selection || selection.isCollapsed) return;
|
|
3029
4091
|
const range = selection.getRangeAt(0);
|
|
@@ -3161,7 +4223,7 @@ var useSelectionDimensions = () => {
|
|
|
3161
4223
|
underlines: consolidateUnderlines(underlineRects).sort(
|
|
3162
4224
|
(a, b) => a.pageNumber - b.pageNumber
|
|
3163
4225
|
),
|
|
3164
|
-
text: range.toString().trim(),
|
|
4226
|
+
text: (getText() ?? range.toString()).trim(),
|
|
3165
4227
|
isCollapsed: false
|
|
3166
4228
|
};
|
|
3167
4229
|
};
|
|
@@ -3218,6 +4280,8 @@ var useSelectionDimensions = () => {
|
|
|
3218
4280
|
return consolidated;
|
|
3219
4281
|
};
|
|
3220
4282
|
const getDimension = () => {
|
|
4283
|
+
if (store.getState().selection?.getSnapshot())
|
|
4284
|
+
return store.getState().selection.getSelection() ?? void 0;
|
|
3221
4285
|
const selection = window.getSelection();
|
|
3222
4286
|
if (!selection || selection.isCollapsed) return;
|
|
3223
4287
|
const range = selection.getRangeAt(0);
|
|
@@ -3246,12 +4310,12 @@ var useSelectionDimensions = () => {
|
|
|
3246
4310
|
});
|
|
3247
4311
|
return {
|
|
3248
4312
|
highlights: highlights.sort((a, b) => a.pageNumber - b.pageNumber),
|
|
3249
|
-
text: range.toString().trim(),
|
|
4313
|
+
text: (getText() ?? range.toString()).trim(),
|
|
3250
4314
|
isCollapsed: false
|
|
3251
4315
|
};
|
|
3252
4316
|
};
|
|
3253
4317
|
const getSelection = () => getDimension();
|
|
3254
|
-
return { getDimension, getSelection, getAnnotationDimension };
|
|
4318
|
+
return { getDimension, getSelection, getAnnotationDimension, getText };
|
|
3255
4319
|
};
|
|
3256
4320
|
var SelectionTooltip = ({ children }) => {
|
|
3257
4321
|
const [isOpen, setIsOpen] = useState(false);
|
|
@@ -3386,6 +4450,7 @@ var SelectionTooltip = ({ children }) => {
|
|
|
3386
4450
|
return /* @__PURE__ */ jsx(Fragment, { children: isOpen && /* @__PURE__ */ jsx(
|
|
3387
4451
|
"div",
|
|
3388
4452
|
{
|
|
4453
|
+
"data-selection-tooltip": "",
|
|
3389
4454
|
ref: refs.setFloating,
|
|
3390
4455
|
style: {
|
|
3391
4456
|
...floatingStyles
|
|
@@ -3801,13 +4866,13 @@ var createTextSelectionManager = () => {
|
|
|
3801
4866
|
}
|
|
3802
4867
|
selectionChangeAbortController = new AbortController();
|
|
3803
4868
|
const { signal } = selectionChangeAbortController;
|
|
3804
|
-
const reset = (endDiv,
|
|
3805
|
-
if (endDiv.parentNode !==
|
|
3806
|
-
|
|
4869
|
+
const reset = (endDiv, textLayer2) => {
|
|
4870
|
+
if (endDiv.parentNode !== textLayer2) {
|
|
4871
|
+
textLayer2.appendChild(endDiv);
|
|
3807
4872
|
}
|
|
3808
4873
|
endDiv.style.width = "";
|
|
3809
4874
|
endDiv.style.height = "";
|
|
3810
|
-
|
|
4875
|
+
textLayer2.classList.remove("selecting");
|
|
3811
4876
|
};
|
|
3812
4877
|
document.addEventListener(
|
|
3813
4878
|
"pointerdown",
|
|
@@ -3933,12 +4998,14 @@ var useTextLayer = () => {
|
|
|
3933
4998
|
const pdfPageProxy = usePdf((state) => state.getPdfPageProxy(pageNumber));
|
|
3934
4999
|
const viewportRef = usePdf((state) => state.viewportRef);
|
|
3935
5000
|
const store = PDFStore.useContext();
|
|
5001
|
+
const selection = store.getState().selection;
|
|
3936
5002
|
useEffect(() => {
|
|
3937
5003
|
const textContainer = textContainerRef.current;
|
|
3938
5004
|
if (!textContainer) {
|
|
3939
5005
|
return;
|
|
3940
5006
|
}
|
|
3941
5007
|
let isCancelled = false;
|
|
5008
|
+
let unregisterSelection;
|
|
3942
5009
|
let buildTimer = null;
|
|
3943
5010
|
let unsubscribe = null;
|
|
3944
5011
|
let built = false;
|
|
@@ -3955,13 +5022,13 @@ var useTextLayer = () => {
|
|
|
3955
5022
|
void import('pdfjs-dist/legacy/build/pdf.mjs').then(({ TextLayer: TextLayer2 }) => {
|
|
3956
5023
|
if (isCancelled || gen !== buildGen || textContainerRef.current !== textContainer)
|
|
3957
5024
|
return;
|
|
3958
|
-
const
|
|
5025
|
+
const textLayer2 = new TextLayer2({
|
|
3959
5026
|
textContentSource: pdfPageProxy.streamTextContent(),
|
|
3960
5027
|
container: textContainer,
|
|
3961
5028
|
viewport: pdfPageProxy.getViewport({ scale: 1 })
|
|
3962
5029
|
});
|
|
3963
|
-
textLayerRef.current =
|
|
3964
|
-
return
|
|
5030
|
+
textLayerRef.current = textLayer2;
|
|
5031
|
+
return textLayer2.render();
|
|
3965
5032
|
}).then(() => {
|
|
3966
5033
|
if (isCancelled || gen !== buildGen || textContainerRef.current !== textContainer) {
|
|
3967
5034
|
return;
|
|
@@ -3970,6 +5037,16 @@ var useTextLayer = () => {
|
|
|
3970
5037
|
endOfContent.className = "endOfContent";
|
|
3971
5038
|
textContainer.appendChild(endOfContent);
|
|
3972
5039
|
bindMouseEvents(textContainer, endOfContent);
|
|
5040
|
+
const viewport = pdfPageProxy.getViewport({ scale: 1 });
|
|
5041
|
+
const unrotated = pdfPageProxy.getViewport({ scale: 1, rotation: 0 });
|
|
5042
|
+
textContainer.style.width = `${unrotated.width}px`;
|
|
5043
|
+
textContainer.style.height = `${unrotated.height}px`;
|
|
5044
|
+
unregisterSelection = selection?.registerLayer(
|
|
5045
|
+
textContainer,
|
|
5046
|
+
pageNumber,
|
|
5047
|
+
viewport.width,
|
|
5048
|
+
viewport.height
|
|
5049
|
+
);
|
|
3973
5050
|
built = true;
|
|
3974
5051
|
building = false;
|
|
3975
5052
|
unsubscribe?.();
|
|
@@ -3983,6 +5060,12 @@ var useTextLayer = () => {
|
|
|
3983
5060
|
};
|
|
3984
5061
|
const schedule = () => {
|
|
3985
5062
|
if (built) return;
|
|
5063
|
+
if (selection?.isDragging || selection?.includesPage(pageNumber)) {
|
|
5064
|
+
if (buildTimer) clearTimeout(buildTimer);
|
|
5065
|
+
buildTimer = null;
|
|
5066
|
+
if (!building) build();
|
|
5067
|
+
return;
|
|
5068
|
+
}
|
|
3986
5069
|
if (building) {
|
|
3987
5070
|
buildGen++;
|
|
3988
5071
|
building = false;
|
|
@@ -4012,9 +5095,15 @@ var useTextLayer = () => {
|
|
|
4012
5095
|
unsubscribe = subscribeToViewportInvalidation(scrollViewport, schedule);
|
|
4013
5096
|
};
|
|
4014
5097
|
attachScrollSubscription();
|
|
5098
|
+
const unsubscribeSelection = selection?.subscribe(() => {
|
|
5099
|
+
if (selection.isDragging || selection.includesPage(pageNumber))
|
|
5100
|
+
schedule();
|
|
5101
|
+
});
|
|
4015
5102
|
schedule();
|
|
4016
5103
|
return () => {
|
|
4017
5104
|
isCancelled = true;
|
|
5105
|
+
unregisterSelection?.();
|
|
5106
|
+
unsubscribeSelection?.();
|
|
4018
5107
|
if (buildTimer) clearTimeout(buildTimer);
|
|
4019
5108
|
if (attachRaf !== null) cancelAnimationFrame(attachRaf);
|
|
4020
5109
|
unsubscribe?.();
|
|
@@ -4027,7 +5116,13 @@ var useTextLayer = () => {
|
|
|
4027
5116
|
delete textContainer._cleanupTextSelection;
|
|
4028
5117
|
}
|
|
4029
5118
|
};
|
|
4030
|
-
}, [
|
|
5119
|
+
}, [
|
|
5120
|
+
pdfPageProxy.streamTextContent,
|
|
5121
|
+
pdfPageProxy.getViewport,
|
|
5122
|
+
viewportRef,
|
|
5123
|
+
selection,
|
|
5124
|
+
pageNumber
|
|
5125
|
+
]);
|
|
4031
5126
|
useEffect(() => {
|
|
4032
5127
|
const textContainer = textContainerRef.current;
|
|
4033
5128
|
if (!textContainer) return;
|
|
@@ -4046,7 +5141,10 @@ var useTextLayer = () => {
|
|
|
4046
5141
|
show();
|
|
4047
5142
|
};
|
|
4048
5143
|
const hideAndDebounce = () => {
|
|
4049
|
-
if (selecting)
|
|
5144
|
+
if (selecting || selection?.isDragging || selection?.includesPage(pageNumber)) {
|
|
5145
|
+
show();
|
|
5146
|
+
return;
|
|
5147
|
+
}
|
|
4050
5148
|
textContainer.style.visibility = "hidden";
|
|
4051
5149
|
if (settleTimer) clearTimeout(settleTimer);
|
|
4052
5150
|
settleTimer = setTimeout(restoreWhenSettled, TEXT_SCROLL_SETTLE_MS);
|
|
@@ -4114,7 +5212,7 @@ var useTextLayer = () => {
|
|
|
4114
5212
|
if (settleTimer) clearTimeout(settleTimer);
|
|
4115
5213
|
show();
|
|
4116
5214
|
};
|
|
4117
|
-
}, [viewportRef, store]);
|
|
5215
|
+
}, [viewportRef, store, selection, pageNumber]);
|
|
4118
5216
|
return {
|
|
4119
5217
|
textContainerRef,
|
|
4120
5218
|
pageNumber: pdfPageProxy.pageNumber
|
|
@@ -4311,6 +5409,37 @@ var Outline = ({
|
|
|
4311
5409
|
});
|
|
4312
5410
|
}) });
|
|
4313
5411
|
};
|
|
5412
|
+
function PageLoadingBoundary({
|
|
5413
|
+
pageNumber,
|
|
5414
|
+
children,
|
|
5415
|
+
fallback = null
|
|
5416
|
+
}) {
|
|
5417
|
+
const loaded = usePdf((state) => state.isPageLoaded(pageNumber));
|
|
5418
|
+
const loadPage = usePdf((state) => state.loadPdfPageProxy);
|
|
5419
|
+
const [failure, setFailure] = useState(null);
|
|
5420
|
+
const [attempt, setAttempt] = useState(0);
|
|
5421
|
+
useEffect(() => {
|
|
5422
|
+
if (loaded) return;
|
|
5423
|
+
let disposed = false;
|
|
5424
|
+
setFailure(null);
|
|
5425
|
+
void loadPage(pageNumber).catch((error) => {
|
|
5426
|
+
if (!disposed) setFailure({ pageNumber, error });
|
|
5427
|
+
});
|
|
5428
|
+
return () => {
|
|
5429
|
+
disposed = true;
|
|
5430
|
+
};
|
|
5431
|
+
}, [pageNumber, loaded, loadPage, attempt]);
|
|
5432
|
+
if (loaded) return children;
|
|
5433
|
+
if (failure?.pageNumber === pageNumber)
|
|
5434
|
+
return /* @__PURE__ */ jsxs("div", { role: "alert", children: [
|
|
5435
|
+
"Unable to load page ",
|
|
5436
|
+
pageNumber,
|
|
5437
|
+
".",
|
|
5438
|
+
" ",
|
|
5439
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => setAttempt((value) => value + 1), children: "Retry" })
|
|
5440
|
+
] });
|
|
5441
|
+
return fallback;
|
|
5442
|
+
}
|
|
4314
5443
|
var Page = memo(
|
|
4315
5444
|
({
|
|
4316
5445
|
children,
|
|
@@ -4318,14 +5447,16 @@ var Page = memo(
|
|
|
4318
5447
|
style,
|
|
4319
5448
|
...props
|
|
4320
5449
|
}) => {
|
|
4321
|
-
const
|
|
5450
|
+
const loaded = usePdf((state) => state.isPageLoaded(pageNumber));
|
|
5451
|
+
const getPage = usePdf((state) => state.getPdfPageProxy);
|
|
5452
|
+
const proxy = loaded ? getPage(pageNumber) : void 0;
|
|
4322
5453
|
const viewport = usePdf((state) => state.viewports[pageNumber - 1]);
|
|
4323
5454
|
const colorScheme = usePdf((state) => state.colorScheme);
|
|
4324
5455
|
const darkModeColors = usePdf((state) => state.darkModeColors);
|
|
4325
5456
|
const pageBackground = colorScheme === "dark" ? createDarkModeColorMap(darkModeColors)("#ffffff") : "white";
|
|
4326
|
-
const width = viewport?.width ?? (
|
|
4327
|
-
const height = viewport?.height ?? (
|
|
4328
|
-
return /* @__PURE__ */ jsx(PDFPageNumberContext.Provider, { value:
|
|
5457
|
+
const width = viewport?.width ?? (proxy?.view[2] ?? 0) - (proxy?.view[0] ?? 0);
|
|
5458
|
+
const height = viewport?.height ?? (proxy?.view[3] ?? 0) - (proxy?.view[1] ?? 0);
|
|
5459
|
+
return /* @__PURE__ */ jsx(PDFPageNumberContext.Provider, { value: pageNumber, children: /* @__PURE__ */ jsx(
|
|
4329
5460
|
Primitive.div,
|
|
4330
5461
|
{
|
|
4331
5462
|
style: {
|
|
@@ -4343,8 +5474,9 @@ var Page = memo(
|
|
|
4343
5474
|
height,
|
|
4344
5475
|
...style
|
|
4345
5476
|
},
|
|
5477
|
+
"aria-busy": !loaded || void 0,
|
|
4346
5478
|
...props,
|
|
4347
|
-
children
|
|
5479
|
+
children: /* @__PURE__ */ jsx(PageLoadingBoundary, { pageNumber, children })
|
|
4348
5480
|
}
|
|
4349
5481
|
)
|
|
4350
5482
|
}
|
|
@@ -4465,6 +5597,28 @@ var addEventListenerOptions = {
|
|
|
4465
5597
|
};
|
|
4466
5598
|
var useObserveElement = () => {
|
|
4467
5599
|
const store = PDFStore.useContext();
|
|
5600
|
+
const observeElementRect$1 = (instance, cb) => {
|
|
5601
|
+
let physicalRect;
|
|
5602
|
+
const publish = () => {
|
|
5603
|
+
if (!physicalRect) return;
|
|
5604
|
+
const zoom = store.getState().zoom;
|
|
5605
|
+
cb({
|
|
5606
|
+
width: physicalRect.width / zoom,
|
|
5607
|
+
height: physicalRect.height / zoom
|
|
5608
|
+
});
|
|
5609
|
+
};
|
|
5610
|
+
const stopObserving = observeElementRect(instance, (rect) => {
|
|
5611
|
+
physicalRect = rect;
|
|
5612
|
+
publish();
|
|
5613
|
+
});
|
|
5614
|
+
const unsubscribe = store.subscribe((state, previous) => {
|
|
5615
|
+
if (state.zoom !== previous.zoom) publish();
|
|
5616
|
+
});
|
|
5617
|
+
return () => {
|
|
5618
|
+
stopObserving?.();
|
|
5619
|
+
unsubscribe();
|
|
5620
|
+
};
|
|
5621
|
+
};
|
|
4468
5622
|
const observeElementOffset = (instance, cb) => {
|
|
4469
5623
|
const element = instance.scrollElement;
|
|
4470
5624
|
if (!element) {
|
|
@@ -4474,33 +5628,46 @@ var useObserveElement = () => {
|
|
|
4474
5628
|
if (!targetWindow) {
|
|
4475
5629
|
return;
|
|
4476
5630
|
}
|
|
4477
|
-
let
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
},
|
|
4483
|
-
instance.options.isScrollingResetDelay
|
|
4484
|
-
);
|
|
4485
|
-
const createHandler = (isScrolling) => () => {
|
|
5631
|
+
let isScrolling = false;
|
|
5632
|
+
let idleTimer;
|
|
5633
|
+
const needsIdleFallback = !instance.options.useScrollendEvent || !supportsScrollend;
|
|
5634
|
+
const publish = (scrolling) => {
|
|
5635
|
+
isScrolling = scrolling;
|
|
4486
5636
|
const { horizontal, isRtl } = instance.options;
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
5637
|
+
const physicalOffset = horizontal ? element.scrollLeft * (isRtl ? -1 : 1) : element.scrollTop;
|
|
5638
|
+
cb(physicalOffset / store.getState().zoom, isScrolling);
|
|
5639
|
+
};
|
|
5640
|
+
const clearIdleTimer = () => {
|
|
5641
|
+
if (idleTimer !== void 0) targetWindow.clearTimeout(idleTimer);
|
|
5642
|
+
idleTimer = void 0;
|
|
5643
|
+
};
|
|
5644
|
+
const createHandler = (scrolling) => () => {
|
|
5645
|
+
clearIdleTimer();
|
|
5646
|
+
publish(scrolling);
|
|
5647
|
+
if (scrolling && needsIdleFallback) {
|
|
5648
|
+
idleTimer = targetWindow.setTimeout(() => {
|
|
5649
|
+
idleTimer = void 0;
|
|
5650
|
+
publish(false);
|
|
5651
|
+
}, instance.options.isScrollingResetDelay);
|
|
5652
|
+
}
|
|
4492
5653
|
};
|
|
4493
5654
|
const handler = createHandler(true);
|
|
4494
5655
|
const endHandler = createHandler(false);
|
|
4495
5656
|
endHandler();
|
|
5657
|
+
const unsubscribe = store.subscribe((state, previous) => {
|
|
5658
|
+
if (state.zoom !== previous.zoom) publish(isScrolling);
|
|
5659
|
+
});
|
|
4496
5660
|
element.addEventListener("scroll", handler, addEventListenerOptions);
|
|
4497
5661
|
element.addEventListener("scrollend", endHandler, addEventListenerOptions);
|
|
4498
5662
|
return () => {
|
|
5663
|
+
unsubscribe();
|
|
5664
|
+
clearIdleTimer();
|
|
4499
5665
|
element.removeEventListener("scroll", handler);
|
|
4500
5666
|
element.removeEventListener("scrollend", endHandler);
|
|
4501
5667
|
};
|
|
4502
5668
|
};
|
|
4503
5669
|
return {
|
|
5670
|
+
observeElementRect: observeElementRect$1,
|
|
4504
5671
|
observeElementOffset
|
|
4505
5672
|
};
|
|
4506
5673
|
};
|
|
@@ -4510,7 +5677,11 @@ var useScrollFn = () => {
|
|
|
4510
5677
|
(_offset, canSmooth, instance) => {
|
|
4511
5678
|
const zoom = store.getState().zoom;
|
|
4512
5679
|
const offset3 = _offset * zoom;
|
|
4513
|
-
elementScroll(
|
|
5680
|
+
elementScroll(
|
|
5681
|
+
offset3,
|
|
5682
|
+
{ ...canSmooth, adjustments: (canSmooth.adjustments ?? 0) * zoom },
|
|
5683
|
+
instance
|
|
5684
|
+
);
|
|
4514
5685
|
},
|
|
4515
5686
|
[store]
|
|
4516
5687
|
);
|
|
@@ -4628,7 +5799,7 @@ function _toPrimitive(input, hint) {
|
|
|
4628
5799
|
if (typeof input !== "object" || input === null) return input;
|
|
4629
5800
|
var prim = input[Symbol.toPrimitive];
|
|
4630
5801
|
if (prim !== void 0) {
|
|
4631
|
-
var res = prim.call(input, hint
|
|
5802
|
+
var res = prim.call(input, hint);
|
|
4632
5803
|
if (typeof res !== "object") return res;
|
|
4633
5804
|
throw new TypeError("@@toPrimitive must return a primitive value.");
|
|
4634
5805
|
}
|
|
@@ -6485,6 +7656,8 @@ var useViewportContainer = ({
|
|
|
6485
7656
|
});
|
|
6486
7657
|
const zoomRafRef = useRef(null);
|
|
6487
7658
|
const lastPushedZoomRef = useRef(null);
|
|
7659
|
+
const initializedZoomRef = useRef(false);
|
|
7660
|
+
const appliedZoomRef = useRef(1);
|
|
6488
7661
|
const updateTransform = useCallback(
|
|
6489
7662
|
(zoomUpdate) => {
|
|
6490
7663
|
if (!elementRef.current || !containerRef.current || !elementWrapperRef.current) {
|
|
@@ -6514,17 +7687,36 @@ var useViewportContainer = ({
|
|
|
6514
7687
|
elementWrapperRef.current.style.height = `${naturalHeight * zoom2}px`;
|
|
6515
7688
|
containerRef.current.scrollTop = translateY;
|
|
6516
7689
|
containerRef.current.scrollLeft = translateX;
|
|
6517
|
-
|
|
6518
|
-
|
|
6519
|
-
|
|
6520
|
-
|
|
6521
|
-
lastPushedZoomRef.current = z;
|
|
6522
|
-
updateZoom(() => z);
|
|
6523
|
-
});
|
|
7690
|
+
appliedZoomRef.current = zoom2;
|
|
7691
|
+
if (zoomUpdate) {
|
|
7692
|
+
lastPushedZoomRef.current = zoom2;
|
|
7693
|
+
updateZoom(() => zoom2);
|
|
6524
7694
|
}
|
|
6525
7695
|
},
|
|
6526
7696
|
[containerRef, elementRef, elementWrapperRef, updateZoom, store]
|
|
6527
7697
|
);
|
|
7698
|
+
useLayoutEffect(() => {
|
|
7699
|
+
const viewport = containerRef.current;
|
|
7700
|
+
if (!viewport) return;
|
|
7701
|
+
return registerViewportZoom(viewport, (nextZoom) => {
|
|
7702
|
+
if (initializedZoomRef.current && appliedZoomRef.current === nextZoom && zoomRafRef.current === null)
|
|
7703
|
+
return;
|
|
7704
|
+
if (zoomRafRef.current !== null) {
|
|
7705
|
+
cancelAnimationFrame(zoomRafRef.current);
|
|
7706
|
+
zoomRafRef.current = null;
|
|
7707
|
+
}
|
|
7708
|
+
lastPushedZoomRef.current = null;
|
|
7709
|
+
const previousZoom = initializedZoomRef.current ? appliedZoomRef.current : store.getState().zoom;
|
|
7710
|
+
const ratio = previousZoom > 0 && Number.isFinite(previousZoom) ? nextZoom / previousZoom : 1;
|
|
7711
|
+
transformations.current = {
|
|
7712
|
+
zoom: nextZoom,
|
|
7713
|
+
translateX: viewport.scrollLeft * ratio,
|
|
7714
|
+
translateY: viewport.scrollTop * ratio
|
|
7715
|
+
};
|
|
7716
|
+
initializedZoomRef.current = true;
|
|
7717
|
+
updateTransform();
|
|
7718
|
+
});
|
|
7719
|
+
}, [containerRef, store, updateTransform]);
|
|
6528
7720
|
useEffect(() => {
|
|
6529
7721
|
if (USE_LAYOUT_ZOOM && gestureTransformAppliedRef.current && !isPinching)
|
|
6530
7722
|
updateTransform();
|
|
@@ -6537,6 +7729,16 @@ var useViewportContainer = ({
|
|
|
6537
7729
|
};
|
|
6538
7730
|
}, []);
|
|
6539
7731
|
useEffect(() => {
|
|
7732
|
+
if (!initializedZoomRef.current && containerRef.current) {
|
|
7733
|
+
initializedZoomRef.current = true;
|
|
7734
|
+
transformations.current = {
|
|
7735
|
+
zoom,
|
|
7736
|
+
translateX: containerRef.current.scrollLeft,
|
|
7737
|
+
translateY: containerRef.current.scrollTop
|
|
7738
|
+
};
|
|
7739
|
+
updateTransform();
|
|
7740
|
+
return;
|
|
7741
|
+
}
|
|
6540
7742
|
if (transformations.current.zoom === zoom || !containerRef.current) {
|
|
6541
7743
|
return;
|
|
6542
7744
|
}
|
|
@@ -6549,7 +7751,7 @@ var useViewportContainer = ({
|
|
|
6549
7751
|
zoomRafRef.current = null;
|
|
6550
7752
|
}
|
|
6551
7753
|
lastPushedZoomRef.current = null;
|
|
6552
|
-
const prevZoom =
|
|
7754
|
+
const prevZoom = appliedZoomRef.current;
|
|
6553
7755
|
if (!prevZoom || !Number.isFinite(prevZoom)) {
|
|
6554
7756
|
transformations.current = {
|
|
6555
7757
|
translateX: containerRef.current.scrollLeft,
|
|
@@ -6689,11 +7891,26 @@ var useViewportContainer = ({
|
|
|
6689
7891
|
translateY: newTranslateY
|
|
6690
7892
|
};
|
|
6691
7893
|
newMemo.lastZoom = newZoom;
|
|
6692
|
-
|
|
7894
|
+
if (zoomRafRef.current === null) {
|
|
7895
|
+
zoomRafRef.current = requestAnimationFrame(() => {
|
|
7896
|
+
zoomRafRef.current = null;
|
|
7897
|
+
updateTransform(true);
|
|
7898
|
+
});
|
|
7899
|
+
}
|
|
6693
7900
|
return newMemo;
|
|
6694
7901
|
},
|
|
6695
7902
|
onPinchStart: () => setIsPinching(true),
|
|
6696
|
-
onPinchEnd: () =>
|
|
7903
|
+
onPinchEnd: () => {
|
|
7904
|
+
const pendingFrame = zoomRafRef.current !== null;
|
|
7905
|
+
if (zoomRafRef.current !== null) {
|
|
7906
|
+
cancelAnimationFrame(zoomRafRef.current);
|
|
7907
|
+
zoomRafRef.current = null;
|
|
7908
|
+
}
|
|
7909
|
+
setIsPinching(false);
|
|
7910
|
+
if (pendingFrame || gestureTransformAppliedRef.current) {
|
|
7911
|
+
updateTransform(pendingFrame);
|
|
7912
|
+
}
|
|
7913
|
+
}
|
|
6697
7914
|
},
|
|
6698
7915
|
{
|
|
6699
7916
|
target: containerRef
|
|
@@ -6741,15 +7958,34 @@ var Pages = ({
|
|
|
6741
7958
|
virtualizerOptions = DEFAULT_VIRTUALIZER_OPTIONS,
|
|
6742
7959
|
initialOffset,
|
|
6743
7960
|
onOffsetChange,
|
|
7961
|
+
copyOptions,
|
|
6744
7962
|
...props
|
|
6745
7963
|
}) => {
|
|
6746
7964
|
const [tempItems, setTempItems] = useState([]);
|
|
6747
7965
|
const viewports = usePdf((state) => state.viewports);
|
|
7966
|
+
const selection = usePdf((state) => state.selection);
|
|
6748
7967
|
const numPages = usePdf((state) => state.pdfDocumentProxy.numPages);
|
|
7968
|
+
const initialPage = usePdf((state) => state.initialPage);
|
|
6749
7969
|
const isPinching = usePdf((state) => state.isPinching);
|
|
6750
7970
|
const elementWrapperRef = useRef(null);
|
|
6751
7971
|
const elementRef = useRef(null);
|
|
6752
7972
|
const containerRef = useRef(null);
|
|
7973
|
+
const copyOptionsRef = useRef(copyOptions);
|
|
7974
|
+
copyOptionsRef.current = copyOptions;
|
|
7975
|
+
useEffect(() => {
|
|
7976
|
+
const container = containerRef.current;
|
|
7977
|
+
if (!container) return;
|
|
7978
|
+
const disconnect = selection.connect(container);
|
|
7979
|
+
const unregisterCopy = registerPdfCopy(
|
|
7980
|
+
container,
|
|
7981
|
+
() => copyOptionsRef.current ?? {},
|
|
7982
|
+
(options) => selection.getSnapshot() ? selection.getText(options) ?? void 0 : null
|
|
7983
|
+
);
|
|
7984
|
+
return () => {
|
|
7985
|
+
unregisterCopy();
|
|
7986
|
+
disconnect();
|
|
7987
|
+
};
|
|
7988
|
+
}, [selection]);
|
|
6753
7989
|
useViewportContainer({
|
|
6754
7990
|
elementRef,
|
|
6755
7991
|
elementWrapperRef,
|
|
@@ -6757,7 +7993,7 @@ var Pages = ({
|
|
|
6757
7993
|
});
|
|
6758
7994
|
const setVirtualizer = usePdf((state) => state.setVirtualizer);
|
|
6759
7995
|
const { scrollToFn } = useScrollFn();
|
|
6760
|
-
const { observeElementOffset } = useObserveElement();
|
|
7996
|
+
const { observeElementOffset, observeElementRect } = useObserveElement();
|
|
6761
7997
|
const viewportsRef = useRef(viewports);
|
|
6762
7998
|
viewportsRef.current = viewports;
|
|
6763
7999
|
const estimateSize = useCallback(
|
|
@@ -6769,15 +8005,22 @@ var Pages = ({
|
|
|
6769
8005
|
[]
|
|
6770
8006
|
// Stable — reads from ref
|
|
6771
8007
|
);
|
|
8008
|
+
const [startingOffset] = useState(
|
|
8009
|
+
() => initialOffset ?? viewports.slice(0, initialPage - 1).reduce(
|
|
8010
|
+
(offset3, viewport) => offset3 + viewport.height + EXTRA_HEIGHT + gap,
|
|
8011
|
+
0
|
|
8012
|
+
)
|
|
8013
|
+
);
|
|
6772
8014
|
const virtualizer = useVirtualizer({
|
|
6773
8015
|
count: numPages || 0,
|
|
6774
8016
|
getScrollElement: () => containerRef.current,
|
|
6775
8017
|
estimateSize,
|
|
6776
8018
|
observeElementOffset,
|
|
8019
|
+
observeElementRect,
|
|
6777
8020
|
overscan: virtualizerOptions?.overscan ?? 0,
|
|
6778
8021
|
scrollToFn,
|
|
6779
8022
|
gap,
|
|
6780
|
-
initialOffset,
|
|
8023
|
+
initialOffset: startingOffset,
|
|
6781
8024
|
// Never trust scrollend alone to reset isScrolling: a single missed
|
|
6782
8025
|
// event (a documented browser flake — TanStack flipped this default to
|
|
6783
8026
|
// false in later 3.x) would pin isScrolling=true forever, and every
|
|
@@ -6785,6 +8028,21 @@ var Pages = ({
|
|
|
6785
8028
|
// false keeps the isScrollingResetDelay debounce active as a fallback.
|
|
6786
8029
|
useScrollendEvent: false
|
|
6787
8030
|
});
|
|
8031
|
+
const previousViewports = useRef(viewports);
|
|
8032
|
+
useLayoutEffect(() => {
|
|
8033
|
+
if (isPinching) return;
|
|
8034
|
+
const previous = previousViewports.current;
|
|
8035
|
+
previousViewports.current = viewports;
|
|
8036
|
+
if (previous === viewports) return;
|
|
8037
|
+
let resized = false;
|
|
8038
|
+
for (let index = 0; index < viewports.length; index++) {
|
|
8039
|
+
if (previous[index]?.height !== viewports[index]?.height) {
|
|
8040
|
+
virtualizer.resizeItem(index, viewports[index].height + EXTRA_HEIGHT);
|
|
8041
|
+
resized = true;
|
|
8042
|
+
}
|
|
8043
|
+
}
|
|
8044
|
+
if (resized) setTempItems([]);
|
|
8045
|
+
}, [viewports, virtualizer, isPinching]);
|
|
6788
8046
|
useEffect(() => {
|
|
6789
8047
|
if (onOffsetChange && virtualizer.scrollOffset)
|
|
6790
8048
|
onOffsetChange(virtualizer.scrollOffset);
|
|
@@ -6845,6 +8103,7 @@ var Pages = ({
|
|
|
6845
8103
|
Primitive.div,
|
|
6846
8104
|
{
|
|
6847
8105
|
ref: containerRef,
|
|
8106
|
+
tabIndex: 0,
|
|
6848
8107
|
...props,
|
|
6849
8108
|
style: {
|
|
6850
8109
|
display: "flex",
|
|
@@ -6931,6 +8190,164 @@ async function mapConcurrent(items, concurrency, map, isCancelled = () => false)
|
|
|
6931
8190
|
return results;
|
|
6932
8191
|
}
|
|
6933
8192
|
|
|
8193
|
+
// src/lib/page-resources.ts
|
|
8194
|
+
var PageResources = class {
|
|
8195
|
+
constructor(document2, firstPage, rotation, onError, concurrency = 16) {
|
|
8196
|
+
this.document = document2;
|
|
8197
|
+
this.rotation = rotation;
|
|
8198
|
+
this.onError = onError;
|
|
8199
|
+
this.concurrency = concurrency;
|
|
8200
|
+
this.pages.set(firstPage.pageNumber, firstPage);
|
|
8201
|
+
const viewport = this.viewportFor(firstPage);
|
|
8202
|
+
this.viewports = Array.from({ length: document2.numPages }, () => viewport);
|
|
8203
|
+
if (document2.numPages === 1) this.completePages = [firstPage];
|
|
8204
|
+
}
|
|
8205
|
+
pages = /* @__PURE__ */ new Map();
|
|
8206
|
+
requests = /* @__PURE__ */ new Map();
|
|
8207
|
+
queue = [];
|
|
8208
|
+
active = 0;
|
|
8209
|
+
disposed = false;
|
|
8210
|
+
listeners = /* @__PURE__ */ new Set();
|
|
8211
|
+
notification;
|
|
8212
|
+
viewports;
|
|
8213
|
+
completePages;
|
|
8214
|
+
viewportUpdates = /* @__PURE__ */ new Map();
|
|
8215
|
+
viewportFor(page) {
|
|
8216
|
+
return page.getViewport({
|
|
8217
|
+
scale: 1,
|
|
8218
|
+
rotation: this.rotation + (page.rotate || 0)
|
|
8219
|
+
});
|
|
8220
|
+
}
|
|
8221
|
+
get(pageNumber) {
|
|
8222
|
+
return this.pages.get(pageNumber);
|
|
8223
|
+
}
|
|
8224
|
+
getSnapshot = () => ({
|
|
8225
|
+
viewports: this.viewports,
|
|
8226
|
+
pageProxies: this.completePages
|
|
8227
|
+
});
|
|
8228
|
+
subscribe = (listener) => {
|
|
8229
|
+
this.listeners.add(listener);
|
|
8230
|
+
return () => {
|
|
8231
|
+
this.listeners.delete(listener);
|
|
8232
|
+
};
|
|
8233
|
+
};
|
|
8234
|
+
/** Visible pages move ahead of the background queue; in-flight requests are shared. */
|
|
8235
|
+
load = (pageNumber) => {
|
|
8236
|
+
if (this.disposed)
|
|
8237
|
+
return Promise.reject(new Error("Document page loading was cancelled"));
|
|
8238
|
+
if (!Number.isInteger(pageNumber) || pageNumber < 1 || pageNumber > this.document.numPages)
|
|
8239
|
+
return Promise.reject(
|
|
8240
|
+
new RangeError(`Page ${pageNumber} does not exist`)
|
|
8241
|
+
);
|
|
8242
|
+
const page = this.pages.get(pageNumber);
|
|
8243
|
+
if (page) return Promise.resolve(page);
|
|
8244
|
+
const existing = this.requests.get(pageNumber);
|
|
8245
|
+
if (existing) {
|
|
8246
|
+
const queued = this.queue.indexOf(pageNumber);
|
|
8247
|
+
if (queued > 0) {
|
|
8248
|
+
this.queue.splice(queued, 1);
|
|
8249
|
+
this.queue.unshift(pageNumber);
|
|
8250
|
+
}
|
|
8251
|
+
return existing.promise;
|
|
8252
|
+
}
|
|
8253
|
+
const request = this.createRequest(pageNumber);
|
|
8254
|
+
this.queue.unshift(pageNumber);
|
|
8255
|
+
this.pump();
|
|
8256
|
+
return request.promise;
|
|
8257
|
+
};
|
|
8258
|
+
createRequest(pageNumber) {
|
|
8259
|
+
let resolve;
|
|
8260
|
+
let reject;
|
|
8261
|
+
const promise = new Promise((res, rej) => {
|
|
8262
|
+
resolve = res;
|
|
8263
|
+
reject = rej;
|
|
8264
|
+
});
|
|
8265
|
+
const request = { promise, resolve, reject };
|
|
8266
|
+
this.requests.set(pageNumber, request);
|
|
8267
|
+
return request;
|
|
8268
|
+
}
|
|
8269
|
+
/** Start after the reader mounts, so unrelated page metadata cannot delay its first render. */
|
|
8270
|
+
start = () => {
|
|
8271
|
+
if (this.disposed) return;
|
|
8272
|
+
for (let pageNumber = 1; pageNumber <= this.document.numPages; pageNumber++) {
|
|
8273
|
+
if (this.pages.has(pageNumber) || this.requests.has(pageNumber)) continue;
|
|
8274
|
+
const request = this.createRequest(pageNumber);
|
|
8275
|
+
void request.promise.catch(() => {
|
|
8276
|
+
});
|
|
8277
|
+
this.queue.push(pageNumber);
|
|
8278
|
+
}
|
|
8279
|
+
this.pump();
|
|
8280
|
+
};
|
|
8281
|
+
loadAll = async () => {
|
|
8282
|
+
if (this.disposed) throw new Error("Document page loading was cancelled");
|
|
8283
|
+
if (this.completePages) return this.completePages;
|
|
8284
|
+
this.start();
|
|
8285
|
+
await Promise.all(
|
|
8286
|
+
Array.from({ length: this.document.numPages }, (_, index) => {
|
|
8287
|
+
const pageNumber = index + 1;
|
|
8288
|
+
return this.pages.get(pageNumber) ?? this.requests.get(pageNumber).promise;
|
|
8289
|
+
})
|
|
8290
|
+
);
|
|
8291
|
+
return this.completePages;
|
|
8292
|
+
};
|
|
8293
|
+
pump() {
|
|
8294
|
+
while (!this.disposed && this.active < this.concurrency && this.queue.length) {
|
|
8295
|
+
const pageNumber = this.queue.shift();
|
|
8296
|
+
const request = this.requests.get(pageNumber);
|
|
8297
|
+
this.active++;
|
|
8298
|
+
void Promise.resolve().then(() => {
|
|
8299
|
+
if (this.disposed)
|
|
8300
|
+
throw new Error("Document page loading was cancelled");
|
|
8301
|
+
return this.document.getPage(pageNumber);
|
|
8302
|
+
}).then((page) => {
|
|
8303
|
+
if (this.disposed) return;
|
|
8304
|
+
const viewport = this.viewportFor(page);
|
|
8305
|
+
this.pages.set(pageNumber, page);
|
|
8306
|
+
this.viewportUpdates.set(pageNumber - 1, viewport);
|
|
8307
|
+
if (this.pages.size === this.document.numPages) {
|
|
8308
|
+
this.completePages = Array.from(
|
|
8309
|
+
{ length: this.document.numPages },
|
|
8310
|
+
(_, index) => this.pages.get(index + 1)
|
|
8311
|
+
);
|
|
8312
|
+
}
|
|
8313
|
+
this.requests.delete(pageNumber);
|
|
8314
|
+
request.resolve(page);
|
|
8315
|
+
this.notify();
|
|
8316
|
+
}).catch((error) => {
|
|
8317
|
+
if (!this.disposed) {
|
|
8318
|
+
this.requests.delete(pageNumber);
|
|
8319
|
+
request.reject(error);
|
|
8320
|
+
this.onError(error);
|
|
8321
|
+
}
|
|
8322
|
+
}).finally(() => {
|
|
8323
|
+
this.active--;
|
|
8324
|
+
this.pump();
|
|
8325
|
+
});
|
|
8326
|
+
}
|
|
8327
|
+
}
|
|
8328
|
+
notify() {
|
|
8329
|
+
if (this.notification !== void 0) return;
|
|
8330
|
+
this.notification = setTimeout(() => {
|
|
8331
|
+
this.notification = void 0;
|
|
8332
|
+
if (this.disposed) return;
|
|
8333
|
+
this.viewports = this.viewports.slice();
|
|
8334
|
+
for (const [index, viewport] of this.viewportUpdates)
|
|
8335
|
+
this.viewports[index] = viewport;
|
|
8336
|
+
this.viewportUpdates.clear();
|
|
8337
|
+
for (const listener of this.listeners) listener();
|
|
8338
|
+
}, 0);
|
|
8339
|
+
}
|
|
8340
|
+
dispose = () => {
|
|
8341
|
+
this.disposed = true;
|
|
8342
|
+
clearTimeout(this.notification);
|
|
8343
|
+
this.listeners.clear();
|
|
8344
|
+
this.queue = [];
|
|
8345
|
+
for (const request of this.requests.values())
|
|
8346
|
+
request.reject(new Error("Document page loading was cancelled"));
|
|
8347
|
+
this.requests.clear();
|
|
8348
|
+
};
|
|
8349
|
+
};
|
|
8350
|
+
|
|
6934
8351
|
// src/lib/pdfjs.ts
|
|
6935
8352
|
var pdfJsPromise = null;
|
|
6936
8353
|
var loadPdfJs = () => {
|
|
@@ -7056,6 +8473,8 @@ var usePDFDocumentContext = ({
|
|
|
7056
8473
|
onError,
|
|
7057
8474
|
source,
|
|
7058
8475
|
initialRotation = 0,
|
|
8476
|
+
progressive = false,
|
|
8477
|
+
initialPage = 1,
|
|
7059
8478
|
isZoomFitWidth,
|
|
7060
8479
|
zoom = 1,
|
|
7061
8480
|
zoomOptions,
|
|
@@ -7078,7 +8497,32 @@ var usePDFDocumentContext = ({
|
|
|
7078
8497
|
darkModeColorsRef.current = darkModeColors;
|
|
7079
8498
|
useEffect(() => {
|
|
7080
8499
|
let isDisposed = false;
|
|
8500
|
+
let pageResources;
|
|
7081
8501
|
const generateViewports = async (pdf) => {
|
|
8502
|
+
const firstPageNumber = Number.isFinite(initialPage) ? Math.max(1, Math.min(pdf.numPages, Math.trunc(initialPage))) : 1;
|
|
8503
|
+
if (progressive) {
|
|
8504
|
+
const firstPage = await pdf.getPage(firstPageNumber);
|
|
8505
|
+
if (isDisposed) return;
|
|
8506
|
+
pageResources = new PageResources(pdf, firstPage, rotation, (error) => {
|
|
8507
|
+
if (isDisposed) return;
|
|
8508
|
+
console.error("Error generating PDF viewports", error);
|
|
8509
|
+
onErrorRef.current?.({ error, phase: "viewport-generation", source });
|
|
8510
|
+
});
|
|
8511
|
+
setInitialState({
|
|
8512
|
+
isZoomFitWidth,
|
|
8513
|
+
viewports: pageResources.getSnapshot().viewports,
|
|
8514
|
+
pageProxies: pageResources.getSnapshot().pageProxies ?? [],
|
|
8515
|
+
pdfDocumentProxy: pdf,
|
|
8516
|
+
zoom,
|
|
8517
|
+
zoomOptions,
|
|
8518
|
+
pageResources,
|
|
8519
|
+
initialPage: firstPageNumber,
|
|
8520
|
+
colorScheme: colorSchemeRef.current,
|
|
8521
|
+
darkModeColors: darkModeColorsRef.current,
|
|
8522
|
+
renderColorMapRef
|
|
8523
|
+
});
|
|
8524
|
+
return;
|
|
8525
|
+
}
|
|
7082
8526
|
const pageProxies = [];
|
|
7083
8527
|
const viewports = await mapConcurrent(
|
|
7084
8528
|
Array.from({ length: pdf.numPages }, (_, index) => index),
|
|
@@ -7104,6 +8548,7 @@ var usePDFDocumentContext = ({
|
|
|
7104
8548
|
pdfDocumentProxy: pdf,
|
|
7105
8549
|
zoom,
|
|
7106
8550
|
zoomOptions,
|
|
8551
|
+
initialPage: firstPageNumber,
|
|
7107
8552
|
colorScheme: colorSchemeRef.current,
|
|
7108
8553
|
darkModeColors: darkModeColorsRef.current,
|
|
7109
8554
|
renderColorMapRef
|
|
@@ -7166,6 +8611,7 @@ var usePDFDocumentContext = ({
|
|
|
7166
8611
|
});
|
|
7167
8612
|
return () => {
|
|
7168
8613
|
isDisposed = true;
|
|
8614
|
+
pageResources?.dispose();
|
|
7169
8615
|
void loadingTask?.destroy();
|
|
7170
8616
|
};
|
|
7171
8617
|
};
|
|
@@ -7207,6 +8653,28 @@ var ColorSchemeSync = ({
|
|
|
7207
8653
|
]);
|
|
7208
8654
|
return null;
|
|
7209
8655
|
};
|
|
8656
|
+
var PageResourcesSync = ({ resources }) => {
|
|
8657
|
+
const store = PDFStore.useContext();
|
|
8658
|
+
useEffect(() => {
|
|
8659
|
+
if (!resources) return;
|
|
8660
|
+
const update = () => {
|
|
8661
|
+
const snapshot = resources.getSnapshot();
|
|
8662
|
+
store.setState((state) => ({
|
|
8663
|
+
viewports: snapshot.viewports,
|
|
8664
|
+
pageProxies: snapshot.pageProxies ?? state.pageProxies,
|
|
8665
|
+
pagesLoaded: Boolean(snapshot.pageProxies)
|
|
8666
|
+
}));
|
|
8667
|
+
};
|
|
8668
|
+
const unsubscribe = resources.subscribe(update);
|
|
8669
|
+
update();
|
|
8670
|
+
const timer = setTimeout(resources.start, 0);
|
|
8671
|
+
return () => {
|
|
8672
|
+
clearTimeout(timer);
|
|
8673
|
+
unsubscribe();
|
|
8674
|
+
};
|
|
8675
|
+
}, [resources, store]);
|
|
8676
|
+
return null;
|
|
8677
|
+
};
|
|
7210
8678
|
var Root = forwardRef(
|
|
7211
8679
|
({
|
|
7212
8680
|
children,
|
|
@@ -7218,6 +8686,8 @@ var Root = forwardRef(
|
|
|
7218
8686
|
zoom,
|
|
7219
8687
|
zoomOptions,
|
|
7220
8688
|
documentOptions,
|
|
8689
|
+
progressive,
|
|
8690
|
+
initialPage,
|
|
7221
8691
|
colorScheme,
|
|
7222
8692
|
darkModeColors,
|
|
7223
8693
|
...props
|
|
@@ -7230,6 +8700,8 @@ var Root = forwardRef(
|
|
|
7230
8700
|
zoom,
|
|
7231
8701
|
zoomOptions,
|
|
7232
8702
|
documentOptions,
|
|
8703
|
+
progressive,
|
|
8704
|
+
initialPage,
|
|
7233
8705
|
colorScheme,
|
|
7234
8706
|
darkModeColors
|
|
7235
8707
|
});
|
|
@@ -7245,6 +8717,7 @@ var Root = forwardRef(
|
|
|
7245
8717
|
};
|
|
7246
8718
|
}, [documentId]);
|
|
7247
8719
|
return /* @__PURE__ */ jsx(Primitive.div, { ref, ...props, children: initialState ? /* @__PURE__ */ jsxs(PDFStore.Provider, { initialValue: initialState, children: [
|
|
8720
|
+
/* @__PURE__ */ jsx(PageResourcesSync, { resources: initialState.pageResources }),
|
|
7248
8721
|
/* @__PURE__ */ jsx(
|
|
7249
8722
|
ColorSchemeSync,
|
|
7250
8723
|
{
|
|
@@ -7275,6 +8748,8 @@ function acquireDocumentText(pages) {
|
|
|
7275
8748
|
const page = pages[index];
|
|
7276
8749
|
const reader = page.streamTextContent().getReader();
|
|
7277
8750
|
const chunks = [];
|
|
8751
|
+
const lineBreaks = [];
|
|
8752
|
+
let offset3 = 0;
|
|
7278
8753
|
let complete = false;
|
|
7279
8754
|
try {
|
|
7280
8755
|
while (true) {
|
|
@@ -7284,9 +8759,13 @@ function acquireDocumentText(pages) {
|
|
|
7284
8759
|
complete = true;
|
|
7285
8760
|
break;
|
|
7286
8761
|
}
|
|
7287
|
-
|
|
7288
|
-
|
|
7289
|
-
|
|
8762
|
+
for (const item of value.items) {
|
|
8763
|
+
if (!("str" in item)) continue;
|
|
8764
|
+
chunks.push(item.str);
|
|
8765
|
+
offset3 += item.str.length;
|
|
8766
|
+
if (item.hasEOL && lineBreaks.at(-1) !== offset3)
|
|
8767
|
+
lineBreaks.push(offset3);
|
|
8768
|
+
}
|
|
7290
8769
|
}
|
|
7291
8770
|
} finally {
|
|
7292
8771
|
if (!complete)
|
|
@@ -7294,7 +8773,11 @@ function acquireDocumentText(pages) {
|
|
|
7294
8773
|
});
|
|
7295
8774
|
reader.releaseLock();
|
|
7296
8775
|
}
|
|
7297
|
-
result[index] = {
|
|
8776
|
+
result[index] = {
|
|
8777
|
+
pageNumber: page.pageNumber,
|
|
8778
|
+
text: chunks.join(""),
|
|
8779
|
+
...lineBreaks.length ? { lineBreaks } : {}
|
|
8780
|
+
};
|
|
7298
8781
|
}
|
|
7299
8782
|
};
|
|
7300
8783
|
const created = {
|
|
@@ -7339,7 +8822,10 @@ var Search = ({
|
|
|
7339
8822
|
}) => {
|
|
7340
8823
|
const [result, setResult] = useState(null);
|
|
7341
8824
|
const [attempt, setAttempt] = useState(0);
|
|
8825
|
+
const document2 = usePdf((state) => state.pdfDocumentProxy);
|
|
7342
8826
|
const proxies = usePdf((state) => state.pageProxies);
|
|
8827
|
+
const pagesLoaded = usePdf((state) => state.pagesLoaded);
|
|
8828
|
+
const loadPages = usePdf((state) => state.loadPdfPageProxies);
|
|
7343
8829
|
const setTextContent = usePdf((state) => state.setTextContent);
|
|
7344
8830
|
const retry = useCallback(() => {
|
|
7345
8831
|
setResult(null);
|
|
@@ -7348,22 +8834,27 @@ var Search = ({
|
|
|
7348
8834
|
useEffect(() => {
|
|
7349
8835
|
let disposed = false;
|
|
7350
8836
|
setResult(null);
|
|
7351
|
-
|
|
7352
|
-
void
|
|
7353
|
-
if (disposed) return;
|
|
8837
|
+
let task;
|
|
8838
|
+
void loadPages().then((pages) => {
|
|
8839
|
+
if (disposed || !pagesLoaded) return;
|
|
8840
|
+
task = acquireDocumentText(pages);
|
|
8841
|
+
return task.promise;
|
|
8842
|
+
}).then((text) => {
|
|
8843
|
+
if (disposed || !text) return;
|
|
7354
8844
|
setTextContent(text);
|
|
7355
|
-
setResult({ pages: proxies, status: "ready" });
|
|
8845
|
+
setResult({ document: document2, pages: proxies, status: "ready" });
|
|
7356
8846
|
}).catch((error) => {
|
|
7357
8847
|
if (disposed) return;
|
|
7358
8848
|
console.error("Error extracting PDF text", error);
|
|
7359
|
-
setResult({ pages: proxies, status: "error", error });
|
|
8849
|
+
setResult({ document: document2, pages: proxies, status: "error", error });
|
|
7360
8850
|
});
|
|
7361
8851
|
return () => {
|
|
7362
8852
|
disposed = true;
|
|
7363
|
-
task
|
|
8853
|
+
task?.release();
|
|
7364
8854
|
};
|
|
7365
|
-
}, [proxies, setTextContent, attempt]);
|
|
7366
|
-
if (!result || result.pages !== proxies)
|
|
8855
|
+
}, [document2, proxies, pagesLoaded, loadPages, setTextContent, attempt]);
|
|
8856
|
+
if (!result || result.document !== document2 || result.pages !== proxies)
|
|
8857
|
+
return loading;
|
|
7367
8858
|
if (result.status === "error") {
|
|
7368
8859
|
if (errorFallback) return errorFallback({ error: result.error, retry });
|
|
7369
8860
|
return /* @__PURE__ */ jsxs("div", { role: "alert", children: [
|
|
@@ -7463,7 +8954,7 @@ var useThumbnail = (pageNumber, config = {}) => {
|
|
|
7463
8954
|
isVisible
|
|
7464
8955
|
};
|
|
7465
8956
|
};
|
|
7466
|
-
var
|
|
8957
|
+
var LoadedThumbnail = ({
|
|
7467
8958
|
pageNumber = 1,
|
|
7468
8959
|
eager = false,
|
|
7469
8960
|
...props
|
|
@@ -7472,30 +8963,65 @@ var Thumbnail = ({
|
|
|
7472
8963
|
isFirstPage: eager || pageNumber < 5
|
|
7473
8964
|
});
|
|
7474
8965
|
const { jumpToPage } = usePdfJump();
|
|
8966
|
+
const isCurrentPage = usePdf((state) => state.currentPage === pageNumber);
|
|
8967
|
+
const spacePressed = useRef(false);
|
|
7475
8968
|
return /* @__PURE__ */ jsx("div", { ref: containerRef, style: { minHeight: "150px", minWidth: "10px" }, children: isVisible && /* @__PURE__ */ jsx(
|
|
7476
8969
|
Primitive.canvas,
|
|
7477
8970
|
{
|
|
8971
|
+
"aria-label": `Page ${pageNumber}`,
|
|
8972
|
+
"aria-current": isCurrentPage ? "page" : void 0,
|
|
7478
8973
|
...props,
|
|
7479
8974
|
role: "button",
|
|
7480
8975
|
tabIndex: 0,
|
|
7481
|
-
onClick: (
|
|
7482
|
-
|
|
7483
|
-
|
|
7484
|
-
|
|
7485
|
-
jumpToPage(pageNumber, { behavior: "auto" });
|
|
8976
|
+
onClick: (event) => {
|
|
8977
|
+
props.onClick?.(event);
|
|
8978
|
+
if (!event.defaultPrevented)
|
|
8979
|
+
jumpToPage(pageNumber, { behavior: "auto" });
|
|
7486
8980
|
},
|
|
7487
|
-
onKeyDown: (
|
|
7488
|
-
|
|
7489
|
-
|
|
8981
|
+
onKeyDown: (event) => {
|
|
8982
|
+
props.onKeyDown?.(event);
|
|
8983
|
+
if (event.defaultPrevented) return;
|
|
8984
|
+
if (event.key === "Enter") {
|
|
8985
|
+
event.preventDefault();
|
|
8986
|
+
jumpToPage(pageNumber, { behavior: "auto" });
|
|
8987
|
+
} else if (event.key === " ") {
|
|
8988
|
+
event.preventDefault();
|
|
8989
|
+
spacePressed.current = true;
|
|
7490
8990
|
}
|
|
7491
|
-
|
|
8991
|
+
},
|
|
8992
|
+
onKeyUp: (event) => {
|
|
8993
|
+
props.onKeyUp?.(event);
|
|
8994
|
+
if (event.key !== " ") return;
|
|
8995
|
+
const activate = spacePressed.current && !event.defaultPrevented;
|
|
8996
|
+
spacePressed.current = false;
|
|
8997
|
+
if (activate) {
|
|
8998
|
+
event.preventDefault();
|
|
7492
8999
|
jumpToPage(pageNumber, { behavior: "auto" });
|
|
7493
9000
|
}
|
|
7494
9001
|
},
|
|
9002
|
+
onBlur: (event) => {
|
|
9003
|
+
spacePressed.current = false;
|
|
9004
|
+
props.onBlur?.(event);
|
|
9005
|
+
},
|
|
7495
9006
|
ref: canvasRef
|
|
7496
9007
|
}
|
|
7497
9008
|
) });
|
|
7498
9009
|
};
|
|
9010
|
+
var Thumbnail = (props) => /* @__PURE__ */ jsx(
|
|
9011
|
+
PageLoadingBoundary,
|
|
9012
|
+
{
|
|
9013
|
+
pageNumber: props.pageNumber ?? 1,
|
|
9014
|
+
fallback: /* @__PURE__ */ jsx(
|
|
9015
|
+
"div",
|
|
9016
|
+
{
|
|
9017
|
+
"aria-label": `Loading page ${props.pageNumber ?? 1}`,
|
|
9018
|
+
"aria-busy": "true",
|
|
9019
|
+
style: { minHeight: 150 }
|
|
9020
|
+
}
|
|
9021
|
+
),
|
|
9022
|
+
children: /* @__PURE__ */ jsx(LoadedThumbnail, { ...props })
|
|
9023
|
+
}
|
|
9024
|
+
);
|
|
7499
9025
|
function VirtualizedThumbnails({
|
|
7500
9026
|
children,
|
|
7501
9027
|
count,
|
|
@@ -7508,6 +9034,9 @@ function VirtualizedThumbnails({
|
|
|
7508
9034
|
const containerRef = useRef(null);
|
|
7509
9035
|
useImperativeHandle(ref, () => containerRef.current);
|
|
7510
9036
|
const [focusPage, setFocusPage] = useState(null);
|
|
9037
|
+
const focusPageLoaded = usePdf(
|
|
9038
|
+
(state) => focusPage === null ? false : state.isPageLoaded(focusPage)
|
|
9039
|
+
);
|
|
7511
9040
|
const itemHeight = Number.isFinite(virtualize.itemHeight) ? Math.max(1, virtualize.itemHeight) : 150;
|
|
7512
9041
|
const virtualizer = useVirtualizer({
|
|
7513
9042
|
count,
|
|
@@ -7525,7 +9054,7 @@ function VirtualizedThumbnails({
|
|
|
7525
9054
|
canvas.focus({ preventScroll: true });
|
|
7526
9055
|
setFocusPage(null);
|
|
7527
9056
|
}
|
|
7528
|
-
}, [focusPage, items]);
|
|
9057
|
+
}, [focusPage, focusPageLoaded, items]);
|
|
7529
9058
|
return /* @__PURE__ */ jsx(
|
|
7530
9059
|
Primitive.div,
|
|
7531
9060
|
{
|
|
@@ -7657,13 +9186,41 @@ var CurrentZoom = ({ ...props }) => {
|
|
|
7657
9186
|
|
|
7658
9187
|
// src/lib/search.ts
|
|
7659
9188
|
var normalizedPages = /* @__PURE__ */ new WeakMap();
|
|
7660
|
-
function normalize(page) {
|
|
9189
|
+
function normalize(page, options) {
|
|
7661
9190
|
let cached = normalizedPages.get(page);
|
|
7662
|
-
if (
|
|
7663
|
-
cached = {
|
|
9191
|
+
if (cached?.text !== page.text || cached.lineBreaks !== page.lineBreaks) {
|
|
9192
|
+
cached = {
|
|
9193
|
+
text: page.text,
|
|
9194
|
+
lineBreaks: page.lineBreaks,
|
|
9195
|
+
variants: /* @__PURE__ */ new Map()
|
|
9196
|
+
};
|
|
7664
9197
|
normalizedPages.set(page, cached);
|
|
7665
9198
|
}
|
|
7666
|
-
|
|
9199
|
+
const key = (options.matchDiacritics === false ? 1 : 0) | (options.ignoreHyphenation ? 2 : 0);
|
|
9200
|
+
let result = cached.variants.get(key);
|
|
9201
|
+
if (!result) {
|
|
9202
|
+
result = normalizeSearchText(page.text, options, page.lineBreaks);
|
|
9203
|
+
cached.variants.set(key, result);
|
|
9204
|
+
}
|
|
9205
|
+
return result;
|
|
9206
|
+
}
|
|
9207
|
+
function createMatch(page, normalized, start, length, searchText, textSize, score) {
|
|
9208
|
+
const end = Math.min(normalized.text.length, start + length);
|
|
9209
|
+
const { start: matchIndex, end: matchEnd } = originalTextRange(
|
|
9210
|
+
normalized,
|
|
9211
|
+
start,
|
|
9212
|
+
end
|
|
9213
|
+
);
|
|
9214
|
+
const matchLength = matchEnd - matchIndex;
|
|
9215
|
+
return {
|
|
9216
|
+
pageNumber: page.pageNumber,
|
|
9217
|
+
text: page.text.slice(matchIndex, matchEnd + textSize),
|
|
9218
|
+
score,
|
|
9219
|
+
matchIndex,
|
|
9220
|
+
isExactMatch: score === 1,
|
|
9221
|
+
searchText,
|
|
9222
|
+
...matchLength !== searchText.length ? { matchLength } : {}
|
|
9223
|
+
};
|
|
7667
9224
|
}
|
|
7668
9225
|
function createBoundedDistance(query, maxDistance) {
|
|
7669
9226
|
const length = query.length;
|
|
@@ -7716,7 +9273,8 @@ function* runSearch(pages, searchText, options = {}, cooperative = false, budget
|
|
|
7716
9273
|
const threshold = Math.max(0, Math.min(1, options.threshold ?? 0.7));
|
|
7717
9274
|
const limit = Number.isFinite(options.limit) ? Math.max(0, Math.floor(options.limit)) : 10;
|
|
7718
9275
|
const textSize = options.textSize ?? 100;
|
|
7719
|
-
const query = searchText.
|
|
9276
|
+
const query = normalizeSearchText(searchText, options).text;
|
|
9277
|
+
if (!query.trim()) return empty;
|
|
7720
9278
|
const maxDistance = Math.floor(query.length * (1 - threshold));
|
|
7721
9279
|
const distance = maxDistance > 0 ? createBoundedDistance(query, maxDistance) : null;
|
|
7722
9280
|
const exactMatches = [];
|
|
@@ -7726,9 +9284,12 @@ function* runSearch(pages, searchText, options = {}, cooperative = false, budget
|
|
|
7726
9284
|
let steps = 0;
|
|
7727
9285
|
let deadline = cooperative ? performance.now() + budget : 0;
|
|
7728
9286
|
for (const page of pages) {
|
|
7729
|
-
const
|
|
9287
|
+
const normalized = normalize(page, options);
|
|
9288
|
+
const lower = normalized.text;
|
|
7730
9289
|
const exactStarts = [];
|
|
7731
9290
|
let index = 0;
|
|
9291
|
+
let lastStart = -1;
|
|
9292
|
+
let lastEnd = -1;
|
|
7732
9293
|
while (true) {
|
|
7733
9294
|
if (cooperative && (++steps & 127) === 0 && performance.now() >= deadline) {
|
|
7734
9295
|
yield;
|
|
@@ -7736,19 +9297,31 @@ function* runSearch(pages, searchText, options = {}, cooperative = false, budget
|
|
|
7736
9297
|
}
|
|
7737
9298
|
const matchIndex = lower.indexOf(query, index);
|
|
7738
9299
|
if (matchIndex === -1) break;
|
|
7739
|
-
|
|
9300
|
+
const span = originalTextRange(
|
|
9301
|
+
normalized,
|
|
9302
|
+
matchIndex,
|
|
9303
|
+
matchIndex + query.length
|
|
9304
|
+
);
|
|
9305
|
+
index = matchIndex + query.length;
|
|
7740
9306
|
if (maxDistance > 0) exactStarts.push(matchIndex);
|
|
9307
|
+
if (span.start === lastStart && span.end === lastEnd) continue;
|
|
9308
|
+
lastStart = span.start;
|
|
9309
|
+
lastEnd = span.end;
|
|
9310
|
+
exactCount++;
|
|
7741
9311
|
if (exactMatches.length < limit) {
|
|
7742
|
-
exactMatches.push(
|
|
7743
|
-
|
|
7744
|
-
|
|
7745
|
-
|
|
7746
|
-
|
|
7747
|
-
|
|
7748
|
-
|
|
7749
|
-
|
|
9312
|
+
exactMatches.push(
|
|
9313
|
+
createMatch(
|
|
9314
|
+
page,
|
|
9315
|
+
normalized,
|
|
9316
|
+
matchIndex,
|
|
9317
|
+
query.length,
|
|
9318
|
+
searchText,
|
|
9319
|
+
textSize,
|
|
9320
|
+
1
|
|
9321
|
+
)
|
|
9322
|
+
);
|
|
7750
9323
|
}
|
|
7751
|
-
index = matchIndex +
|
|
9324
|
+
index = matchIndex + query.length;
|
|
7752
9325
|
}
|
|
7753
9326
|
if (!(maxDistance > 0)) continue;
|
|
7754
9327
|
index = 0;
|
|
@@ -7758,11 +9331,11 @@ function* runSearch(pages, searchText, options = {}, cooperative = false, budget
|
|
|
7758
9331
|
yield;
|
|
7759
9332
|
deadline = performance.now() + budget;
|
|
7760
9333
|
}
|
|
7761
|
-
while (exactIndex < exactStarts.length && exactStarts[exactIndex] +
|
|
9334
|
+
while (exactIndex < exactStarts.length && exactStarts[exactIndex] + query.length <= index)
|
|
7762
9335
|
exactIndex++;
|
|
7763
9336
|
const exactStart = exactStarts[exactIndex];
|
|
7764
9337
|
if (exactStart !== void 0 && index >= exactStart) {
|
|
7765
|
-
index = exactStart +
|
|
9338
|
+
index = exactStart + query.length;
|
|
7766
9339
|
continue;
|
|
7767
9340
|
}
|
|
7768
9341
|
const edits = distance(lower, index);
|
|
@@ -7772,14 +9345,15 @@ function* runSearch(pages, searchText, options = {}, cooperative = false, budget
|
|
|
7772
9345
|
if (fuzzyMatches.length < limit || score > (fuzzyMatches[limit - 1]?.score ?? Infinity)) {
|
|
7773
9346
|
insertMatch(
|
|
7774
9347
|
fuzzyMatches,
|
|
7775
|
-
|
|
7776
|
-
|
|
7777
|
-
|
|
7778
|
-
|
|
7779
|
-
|
|
7780
|
-
|
|
7781
|
-
|
|
7782
|
-
|
|
9348
|
+
createMatch(
|
|
9349
|
+
page,
|
|
9350
|
+
normalized,
|
|
9351
|
+
index,
|
|
9352
|
+
query.length,
|
|
9353
|
+
searchText,
|
|
9354
|
+
textSize,
|
|
9355
|
+
score
|
|
9356
|
+
),
|
|
7783
9357
|
limit
|
|
7784
9358
|
);
|
|
7785
9359
|
}
|
|
@@ -7885,32 +9459,37 @@ var useSearch = () => {
|
|
|
7885
9459
|
|
|
7886
9460
|
// src/hooks/search/useSearchPosition.tsx
|
|
7887
9461
|
async function calculateHighlightRects(pageProxy, textMatch) {
|
|
7888
|
-
const matchLength = textMatch.searchText ? textMatch.searchText.length : textMatch.text.length;
|
|
7889
|
-
if (matchLength
|
|
9462
|
+
const matchLength = textMatch.matchLength ?? (textMatch.searchText ? textMatch.searchText.length : textMatch.text.length);
|
|
9463
|
+
if (!Number.isFinite(matchLength) || matchLength <= 0 || !Number.isFinite(textMatch.matchIndex) || textMatch.matchIndex < 0)
|
|
7890
9464
|
return [];
|
|
7891
9465
|
const matchEnd = textMatch.matchIndex + matchLength;
|
|
7892
9466
|
const reader = pageProxy.streamTextContent().getReader();
|
|
7893
9467
|
const matchRects = [];
|
|
7894
9468
|
const viewport = pageProxy.getViewport({ scale: 1 });
|
|
9469
|
+
const styles = {};
|
|
7895
9470
|
let offset3 = 0;
|
|
7896
9471
|
let foundEnd = false;
|
|
7897
9472
|
try {
|
|
7898
9473
|
read: while (true) {
|
|
7899
9474
|
const { value, done } = await reader.read();
|
|
7900
9475
|
if (done) break;
|
|
9476
|
+
Object.assign(styles, value.styles);
|
|
7901
9477
|
for (const item of value.items) {
|
|
7902
9478
|
if (!("str" in item) || item.str.length === 0) continue;
|
|
7903
9479
|
const length = item.str.length;
|
|
7904
9480
|
const start = Math.max(0, textMatch.matchIndex - offset3);
|
|
7905
9481
|
const end = Math.min(length, matchEnd - offset3);
|
|
7906
|
-
if (end > start) {
|
|
7907
|
-
matchRects.push(
|
|
7908
|
-
|
|
7909
|
-
|
|
7910
|
-
|
|
7911
|
-
|
|
7912
|
-
|
|
7913
|
-
|
|
9482
|
+
if (end > start && item.str.slice(start, end).trim()) {
|
|
9483
|
+
matchRects.push(
|
|
9484
|
+
getItemRect(
|
|
9485
|
+
item,
|
|
9486
|
+
styles[item.fontName],
|
|
9487
|
+
viewport,
|
|
9488
|
+
start,
|
|
9489
|
+
end,
|
|
9490
|
+
textMatch.pageNumber
|
|
9491
|
+
)
|
|
9492
|
+
);
|
|
7914
9493
|
}
|
|
7915
9494
|
offset3 += length;
|
|
7916
9495
|
if (offset3 >= matchEnd) {
|
|
@@ -7927,6 +9506,61 @@ async function calculateHighlightRects(pageProxy, textMatch) {
|
|
|
7927
9506
|
}
|
|
7928
9507
|
return mergeAdjacentRects(matchRects);
|
|
7929
9508
|
}
|
|
9509
|
+
var measurementContext;
|
|
9510
|
+
function getTextFractions(item, style, start, end) {
|
|
9511
|
+
const length = item.str.length;
|
|
9512
|
+
let from = start / length;
|
|
9513
|
+
let to = end / length;
|
|
9514
|
+
if ((start > 0 || end < length) && style?.fontFamily && !style.vertical && typeof document !== "undefined") {
|
|
9515
|
+
if (measurementContext === void 0) {
|
|
9516
|
+
const canvas = document.createElement("canvas");
|
|
9517
|
+
canvas.width = canvas.height = 0;
|
|
9518
|
+
measurementContext = canvas.getContext("2d");
|
|
9519
|
+
}
|
|
9520
|
+
if (measurementContext) {
|
|
9521
|
+
measurementContext.font = `100px ${style.fontFamily}`;
|
|
9522
|
+
const width = measurementContext.measureText(item.str).width;
|
|
9523
|
+
if (width > 0) {
|
|
9524
|
+
from = start === 0 ? 0 : measurementContext.measureText(item.str.slice(0, start)).width / width;
|
|
9525
|
+
to = end === length ? 1 : measurementContext.measureText(item.str.slice(0, end)).width / width;
|
|
9526
|
+
}
|
|
9527
|
+
}
|
|
9528
|
+
}
|
|
9529
|
+
return item.dir === "rtl" ? [1 - to, 1 - from] : [from, to];
|
|
9530
|
+
}
|
|
9531
|
+
function getItemRect(item, style, viewport, start, end, pageNumber) {
|
|
9532
|
+
const angle = Math.atan2(item.transform[1], item.transform[0]) - (style?.vertical ? Math.PI / 2 : 0);
|
|
9533
|
+
const cosine = Math.cos(angle);
|
|
9534
|
+
const sine = Math.sin(angle);
|
|
9535
|
+
const height = Math.hypot(item.transform[2], item.transform[3]);
|
|
9536
|
+
const ascent = (style?.ascent ?? (style?.descent ? 1 + style.descent : 1)) * height;
|
|
9537
|
+
const width = style?.vertical ? item.height : item.width;
|
|
9538
|
+
const [startFraction, endFraction] = getTextFractions(
|
|
9539
|
+
item,
|
|
9540
|
+
style,
|
|
9541
|
+
start,
|
|
9542
|
+
end
|
|
9543
|
+
);
|
|
9544
|
+
const from = startFraction * width;
|
|
9545
|
+
const to = endFraction * width;
|
|
9546
|
+
let left = Infinity;
|
|
9547
|
+
let top = Infinity;
|
|
9548
|
+
let right = -Infinity;
|
|
9549
|
+
let bottom = -Infinity;
|
|
9550
|
+
for (const along of [from, to]) {
|
|
9551
|
+
for (const above of [ascent, ascent - height]) {
|
|
9552
|
+
const [x, y] = viewport.convertToViewportPoint(
|
|
9553
|
+
item.transform[4] + along * cosine - above * sine,
|
|
9554
|
+
item.transform[5] + along * sine + above * cosine
|
|
9555
|
+
);
|
|
9556
|
+
left = Math.min(left, x);
|
|
9557
|
+
top = Math.min(top, y);
|
|
9558
|
+
right = Math.max(right, x);
|
|
9559
|
+
bottom = Math.max(bottom, y);
|
|
9560
|
+
}
|
|
9561
|
+
}
|
|
9562
|
+
return { pageNumber, left, top, width: right - left, height: bottom - top };
|
|
9563
|
+
}
|
|
7930
9564
|
function mergeAdjacentRects(rects) {
|
|
7931
9565
|
if (rects.length <= 1) return rects;
|
|
7932
9566
|
const merged = [];
|
|
@@ -7935,10 +9569,13 @@ function mergeAdjacentRects(rects) {
|
|
|
7935
9569
|
for (let i = 1; i < rects.length; i++) {
|
|
7936
9570
|
const next = rects[i];
|
|
7937
9571
|
if (!next) continue;
|
|
7938
|
-
if (Math.abs(current.top - next.top) < 2 && Math.abs(current.height - next.height) < 2) {
|
|
9572
|
+
if (Math.abs(current.top - next.top) < 2 && Math.abs(current.height - next.height) < 2 && Math.max(current.left, next.left) <= Math.min(current.left + current.width, next.left + next.width) + 2) {
|
|
7939
9573
|
current = {
|
|
7940
9574
|
...current,
|
|
7941
|
-
|
|
9575
|
+
left: Math.min(current.left, next.left),
|
|
9576
|
+
top: Math.min(current.top, next.top),
|
|
9577
|
+
width: Math.max(current.left + current.width, next.left + next.width) - Math.min(current.left, next.left),
|
|
9578
|
+
height: Math.max(current.top + current.height, next.top + next.height) - Math.min(current.top, next.top)
|
|
7942
9579
|
};
|
|
7943
9580
|
} else {
|
|
7944
9581
|
merged.push(current);
|
|
@@ -7951,5 +9588,24 @@ function mergeAdjacentRects(rects) {
|
|
|
7951
9588
|
|
|
7952
9589
|
// src/hooks/usePageRendered.ts
|
|
7953
9590
|
var usePageRendered = (pageNumber) => usePdf((state) => !!state.renderedPages[pageNumber]);
|
|
9591
|
+
function usePdfSelection() {
|
|
9592
|
+
const controller = usePdf((state) => state.selection);
|
|
9593
|
+
const selection = useSyncExternalStore(
|
|
9594
|
+
controller.subscribe,
|
|
9595
|
+
controller.getSnapshot,
|
|
9596
|
+
controller.getSnapshot
|
|
9597
|
+
);
|
|
9598
|
+
return useMemo(
|
|
9599
|
+
() => ({
|
|
9600
|
+
selection,
|
|
9601
|
+
setSelection: controller.setSelection,
|
|
9602
|
+
clearSelection: controller.clear,
|
|
9603
|
+
getText: controller.getText,
|
|
9604
|
+
getSelection: controller.getSelection,
|
|
9605
|
+
getSelectionAsync: controller.getSelectionAsync
|
|
9606
|
+
}),
|
|
9607
|
+
[controller, selection]
|
|
9608
|
+
);
|
|
9609
|
+
}
|
|
7954
9610
|
|
|
7955
|
-
export { AnnotationHighlightLayer, AnnotationLayer, AnnotationTooltip, AnnotationsStoreProvider, CanvasLayer, ColoredHighlightLayer, CurrentPage, CurrentZoom, CustomLayer, DEFAULT_DARK_MODE_COLORS, HighlightLayer, LinkService, NextPage, Outline, OutlineChildItems, OutlineItem, PDFLinkServiceContext, Page, Pages, PreviousPage, Root, Search, SelectionTooltip, TextLayer, Thumbnail, Thumbnails, TotalPages, ZoomIn, ZoomOut, calculateHighlightRects, createDarkModeColorMap, useAnnotations, useCreatePDFLinkService, usePDFLinkService, usePDFPageNumber, usePageAnnotations, usePageRendered, usePdf, usePdfJump, useSearch, useSelectionDimensions };
|
|
9611
|
+
export { AnnotationHighlightLayer, AnnotationLayer, AnnotationTooltip, AnnotationsStoreProvider, CanvasLayer, ColoredHighlightLayer, CurrentPage, CurrentZoom, CustomLayer, DEFAULT_DARK_MODE_COLORS, HighlightLayer, LinkService, NextPage, Outline, OutlineChildItems, OutlineItem, PDFLinkServiceContext, Page, Pages, PreviousPage, Root, Search, SelectionTooltip, TextLayer, Thumbnail, Thumbnails, TotalPages, ZoomIn, ZoomOut, calculateHighlightRects, createDarkModeColorMap, useAnnotations, useCreatePDFLinkService, usePDFLinkService, usePDFPageNumber, usePageAnnotations, usePageRendered, usePdf, usePdfJump, usePdfSelection, useSearch, useSelectionDimensions };
|