@embedpdf/plugin-selection 3.0.0-next.3 → 3.0.0-next.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +104 -257
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +70 -52
- package/dist/index.d.ts +70 -52
- package/dist/index.js +102 -254
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.cjs
CHANGED
|
@@ -2,251 +2,81 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
2
2
|
let _embedpdf_core = require("@embedpdf/core");
|
|
3
3
|
let _embedpdf_plugin_interaction = require("@embedpdf/plugin-interaction");
|
|
4
4
|
let _embedpdf_core_geometry = require("@embedpdf/core-geometry");
|
|
5
|
+
let _embedpdf_engine_core_runtime = require("@embedpdf/engine-core/runtime");
|
|
5
6
|
//#region src/geometry.ts
|
|
6
7
|
/**
|
|
7
|
-
* The
|
|
8
|
-
* viewer content space (y-down), glyph hit-testing, word/line expansion, and the
|
|
9
|
-
* line-merge. No DOM, no engine, no React.
|
|
8
|
+
* The selection plugin's COORDINATE SEAM — and nothing else.
|
|
10
9
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* The PDF↔content y-flip is the
|
|
17
|
-
* that math lives), never
|
|
10
|
+
* Segmentation, hit-testing, and word/line expansion live in the canonical
|
|
11
|
+
* engine-core text layout (`@embedpdf/engine-core` `text/layout`): a text
|
|
12
|
+
* range has exactly one segmentation whether it came from a drag or a
|
|
13
|
+
* search. This module only converts between the viewer's content space
|
|
14
|
+
* (y-down, crop-relative) and the engine's PDF space (y-up) — pointer
|
|
15
|
+
* points inward, canonical segments outward. The PDF↔content y-flip is the
|
|
16
|
+
* geometry package's `pageGeometry` (the one place that math lives), never
|
|
17
|
+
* hand-rolled here.
|
|
18
18
|
*/
|
|
19
|
-
const FLAG_EMPTY = 2;
|
|
20
|
-
const isBoundary = (flags) => (flags & 3) !== 0;
|
|
21
|
-
const isEmpty = (flags) => (flags & FLAG_EMPTY) !== 0;
|
|
22
|
-
const toContent = (m, b) => (0, _embedpdf_core_geometry.applyRect)(m, {
|
|
23
|
-
x: b.left,
|
|
24
|
-
y: b.bottom,
|
|
25
|
-
width: b.right - b.left,
|
|
26
|
-
height: b.top - b.bottom
|
|
27
|
-
});
|
|
28
19
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* un-rotated
|
|
20
|
+
* Build a page's selection geometry. Page display rotation does NOT enter
|
|
21
|
+
* here — the overlay rides the page's CSS rotation, so content space stays
|
|
22
|
+
* un-rotated (the crop-relative y-flip is the only conversion; zoom = 1,
|
|
23
|
+
* the viewer's zoom is applied later by `PageTransform.pageToContent`).
|
|
32
24
|
*/
|
|
33
|
-
function
|
|
25
|
+
function buildSelectionPageGeometry(snapshot, crop, rotation, userUnit) {
|
|
34
26
|
const { pdfToContent } = (0, _embedpdf_core_geometry.pageGeometry)({
|
|
35
27
|
crop,
|
|
36
28
|
rotation,
|
|
37
29
|
userUnit
|
|
38
30
|
}, 1);
|
|
39
|
-
const glyphs = [];
|
|
40
|
-
const runs = [];
|
|
41
|
-
for (const run of snapshot.runs) {
|
|
42
|
-
const start = glyphs.length;
|
|
43
|
-
for (const g of run.glyphs) glyphs.push({
|
|
44
|
-
loose: toContent(pdfToContent, g.looseBox),
|
|
45
|
-
tight: g.tightBox ? toContent(pdfToContent, g.tightBox) : void 0,
|
|
46
|
-
flags: g.flags
|
|
47
|
-
});
|
|
48
|
-
runs.push({
|
|
49
|
-
start,
|
|
50
|
-
count: run.glyphs.length,
|
|
51
|
-
rect: toContent(pdfToContent, run.rect),
|
|
52
|
-
fontSize: run.fontSize
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
31
|
return {
|
|
56
|
-
|
|
57
|
-
|
|
32
|
+
layout: (0, _embedpdf_engine_core_runtime.buildPageTextLayout)(snapshot),
|
|
33
|
+
toContent: pdfToContent,
|
|
34
|
+
fromContent: (0, _embedpdf_core_geometry.invert)(pdfToContent)
|
|
58
35
|
};
|
|
59
36
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
for (const g of text.glyphs) {
|
|
64
|
-
if (isEmpty(g.flags)) continue;
|
|
65
|
-
total += g.loose.height;
|
|
66
|
-
count++;
|
|
67
|
-
}
|
|
68
|
-
return count === 0 ? 0 : total / count;
|
|
37
|
+
/** A content-space pointer point in the layout's PDF space. */
|
|
38
|
+
function contentPointToPdf(geom, p) {
|
|
39
|
+
return (0, _embedpdf_core_geometry.applyPoint)(geom.fromContent, p);
|
|
69
40
|
}
|
|
70
41
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
42
|
+
* A canonical PDF quad in content space. Engine quads carry frame-geometric
|
|
43
|
+
* slot semantics (`p1..p4` = upper-start, upper-end, lower-start,
|
|
44
|
+
* lower-end) — a trusted producer, so the y-flip maps corners straight onto
|
|
45
|
+
* their names; imported-annotation normalization does NOT apply here.
|
|
75
46
|
*/
|
|
76
|
-
function
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
if (toleranceFactor <= 0) return null;
|
|
86
|
-
const half = avgGlyphHeight(text) * toleranceFactor / 2;
|
|
87
|
-
let best = -1;
|
|
88
|
-
let bestDist = Infinity;
|
|
89
|
-
for (const run of text.runs) {
|
|
90
|
-
const r = run.rect;
|
|
91
|
-
if (p.y < r.y - half || p.y > r.y + r.height + half || p.x < r.x - half || p.x > r.x + r.width + half) continue;
|
|
92
|
-
for (let i = 0; i < run.count; i++) {
|
|
93
|
-
const g = text.glyphs[run.start + i];
|
|
94
|
-
if (isEmpty(g.flags)) continue;
|
|
95
|
-
const b = g.tight ?? g.loose;
|
|
96
|
-
if (p.x < b.x - half || p.x > b.x + b.width + half || p.y < b.y - half || p.y > b.y + b.height + half) continue;
|
|
97
|
-
const dx = Math.min(Math.abs(p.x - b.x), Math.abs(p.x - (b.x + b.width)));
|
|
98
|
-
const dy = Math.min(Math.abs(p.y - b.y), Math.abs(p.y - (b.y + b.height)));
|
|
99
|
-
if (dx + dy < bestDist) {
|
|
100
|
-
bestDist = dx + dy;
|
|
101
|
-
best = run.start + i;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return best >= 0 ? best : null;
|
|
106
|
-
}
|
|
107
|
-
/** Double-click: the word around `i` (walk to space/empty glyphs both ways). */
|
|
108
|
-
function expandToWord(text, i) {
|
|
109
|
-
const n = text.glyphs.length;
|
|
110
|
-
if (i < 0 || i >= n) return [i, i];
|
|
111
|
-
let from = i;
|
|
112
|
-
while (from > 0 && !isBoundary(text.glyphs[from - 1].flags)) from--;
|
|
113
|
-
let to = i;
|
|
114
|
-
while (to < n - 1 && !isBoundary(text.glyphs[to + 1].flags)) to++;
|
|
115
|
-
return [from, to];
|
|
116
|
-
}
|
|
117
|
-
/** Triple-click: the full visual line — runs whose vertical extent overlaps the anchor run's. */
|
|
118
|
-
function expandToLine(text, i) {
|
|
119
|
-
const ri = text.runs.findIndex((r) => i >= r.start && i < r.start + r.count);
|
|
120
|
-
if (ri < 0) return [i, i];
|
|
121
|
-
const anchor = text.runs[ri];
|
|
122
|
-
const top = anchor.rect.y;
|
|
123
|
-
const bottom = anchor.rect.y + anchor.rect.height;
|
|
124
|
-
let from = anchor.start;
|
|
125
|
-
let to = anchor.start + anchor.count - 1;
|
|
126
|
-
for (let r = ri - 1; r >= 0; r--) {
|
|
127
|
-
const run = text.runs[r];
|
|
128
|
-
if (isZero(run.rect)) continue;
|
|
129
|
-
if (!overlapV(run.rect.y, run.rect.y + run.rect.height, top, bottom)) break;
|
|
130
|
-
from = run.start;
|
|
131
|
-
}
|
|
132
|
-
for (let r = ri + 1; r < text.runs.length; r++) {
|
|
133
|
-
const run = text.runs[r];
|
|
134
|
-
if (isZero(run.rect)) continue;
|
|
135
|
-
if (!overlapV(run.rect.y, run.rect.y + run.rect.height, top, bottom)) break;
|
|
136
|
-
to = run.start + run.count - 1;
|
|
137
|
-
}
|
|
138
|
-
return [from, to];
|
|
139
|
-
}
|
|
140
|
-
const CHAR_DISTANCE_FACTOR = 2.5;
|
|
141
|
-
const FONT_SIZE_RATIO_THRESHOLD = 1.5;
|
|
142
|
-
const VERTICAL_OVERLAP_THRESHOLD = .8;
|
|
143
|
-
const LINE_OVERLAP_THRESHOLD = .5;
|
|
144
|
-
function rectsForRange(text, from, to) {
|
|
145
|
-
const lo = Math.max(0, Math.min(from, to));
|
|
146
|
-
const hi = Math.min(text.glyphs.length - 1, Math.max(from, to));
|
|
147
|
-
const subRuns = [];
|
|
148
|
-
for (const run of text.runs) {
|
|
149
|
-
const runEnd = run.start + run.count - 1;
|
|
150
|
-
if (runEnd < lo || run.start > hi) continue;
|
|
151
|
-
const s = Math.max(lo, run.start);
|
|
152
|
-
const e = Math.min(hi, runEnd);
|
|
153
|
-
let minX = Infinity;
|
|
154
|
-
let maxX = -Infinity;
|
|
155
|
-
let minY = Infinity;
|
|
156
|
-
let maxY = -Infinity;
|
|
157
|
-
let charCount = 0;
|
|
158
|
-
let widthSum = 0;
|
|
159
|
-
let prevRight = -Infinity;
|
|
160
|
-
const flush = () => {
|
|
161
|
-
if (minX !== Infinity && charCount > 0) subRuns.push({
|
|
162
|
-
rect: {
|
|
163
|
-
x: minX,
|
|
164
|
-
y: minY,
|
|
165
|
-
width: maxX - minX,
|
|
166
|
-
height: maxY - minY
|
|
167
|
-
},
|
|
168
|
-
charCount,
|
|
169
|
-
fontSize: run.fontSize
|
|
170
|
-
});
|
|
171
|
-
minX = Infinity;
|
|
172
|
-
maxX = -Infinity;
|
|
173
|
-
minY = Infinity;
|
|
174
|
-
maxY = -Infinity;
|
|
175
|
-
charCount = 0;
|
|
176
|
-
widthSum = 0;
|
|
177
|
-
prevRight = -Infinity;
|
|
178
|
-
};
|
|
179
|
-
for (let gi = s; gi <= e; gi++) {
|
|
180
|
-
const g = text.glyphs[gi];
|
|
181
|
-
if (isEmpty(g.flags)) continue;
|
|
182
|
-
const b = g.loose;
|
|
183
|
-
if (charCount > 0 && prevRight > -Infinity) {
|
|
184
|
-
const avg = widthSum / charCount;
|
|
185
|
-
if (avg > 0 && Math.abs(b.x - prevRight) > CHAR_DISTANCE_FACTOR * avg) flush();
|
|
186
|
-
}
|
|
187
|
-
minX = Math.min(minX, b.x);
|
|
188
|
-
maxX = Math.max(maxX, b.x + b.width);
|
|
189
|
-
minY = Math.min(minY, b.y);
|
|
190
|
-
maxY = Math.max(maxY, b.y + b.height);
|
|
191
|
-
charCount++;
|
|
192
|
-
widthSum += b.width;
|
|
193
|
-
prevRight = b.x + b.width;
|
|
194
|
-
}
|
|
195
|
-
flush();
|
|
196
|
-
}
|
|
197
|
-
return mergeAdjacentRects(subRuns);
|
|
198
|
-
}
|
|
199
|
-
function mergeAdjacentRects(runs) {
|
|
200
|
-
const out = [];
|
|
201
|
-
let prev = null;
|
|
202
|
-
let cur = null;
|
|
203
|
-
for (const run of runs) {
|
|
204
|
-
if (prev && cur && shouldMerge(prev, run)) cur = (0, _embedpdf_core_geometry.boundsOfRects)([cur, run.rect]);
|
|
205
|
-
else {
|
|
206
|
-
if (cur) out.push(cur);
|
|
207
|
-
cur = run.rect;
|
|
208
|
-
}
|
|
209
|
-
prev = run;
|
|
210
|
-
}
|
|
211
|
-
if (cur && cur.width > 0 && cur.height > 0) out.push(cur);
|
|
212
|
-
return out;
|
|
213
|
-
}
|
|
214
|
-
function shouldMerge(a, b) {
|
|
215
|
-
if (a.fontSize != null && b.fontSize != null && a.fontSize > 0 && b.fontSize > 0) {
|
|
216
|
-
if (Math.max(a.fontSize, b.fontSize) / Math.min(a.fontSize, b.fontSize) > FONT_SIZE_RATIO_THRESHOLD) return false;
|
|
217
|
-
}
|
|
218
|
-
if (verticalOverlap(a.rect, b.rect) < VERTICAL_OVERLAP_THRESHOLD) return false;
|
|
219
|
-
const aw = a.rect.width / a.charCount;
|
|
220
|
-
const bw = b.rect.width / b.charCount;
|
|
221
|
-
const aL = a.rect.x - aw;
|
|
222
|
-
const aR = a.rect.x + a.rect.width + aw;
|
|
223
|
-
const bL = b.rect.x - bw;
|
|
224
|
-
return aL < b.rect.x + b.rect.width + bw && aR > bL;
|
|
225
|
-
}
|
|
226
|
-
function verticalOverlap(a, b) {
|
|
227
|
-
if (a.height <= 0 || b.height <= 0) return 0;
|
|
228
|
-
const u = Math.max(a.y + a.height, b.y + b.height) - Math.min(a.y, b.y);
|
|
229
|
-
if (u === a.height || u === b.height) return 1;
|
|
230
|
-
return Math.max(0, Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y)) / u;
|
|
47
|
+
function toContentTextQuad(geom, quad) {
|
|
48
|
+
const pt = (p) => (0, _embedpdf_core_geometry.applyPoint)(geom.toContent, p);
|
|
49
|
+
return {
|
|
50
|
+
upperStart: pt(quad.p1),
|
|
51
|
+
upperEnd: pt(quad.p2),
|
|
52
|
+
lowerStart: pt(quad.p3),
|
|
53
|
+
lowerEnd: pt(quad.p4)
|
|
54
|
+
};
|
|
231
55
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
return
|
|
56
|
+
/** A canonical segment in content space (`rect` recomputed from the mapped
|
|
57
|
+
* quad so the pair can never drift). */
|
|
58
|
+
function toContentSegment(geom, segment) {
|
|
59
|
+
const quad = toContentTextQuad(geom, segment.quad);
|
|
60
|
+
return {
|
|
61
|
+
quad,
|
|
62
|
+
rect: (0, _embedpdf_core_geometry.textQuadBounds)(quad),
|
|
63
|
+
advance: segment.advance
|
|
64
|
+
};
|
|
237
65
|
}
|
|
238
66
|
//#endregion
|
|
239
67
|
//#region src/capability.ts
|
|
240
|
-
const
|
|
68
|
+
const EMPTY_SEGMENTS = [];
|
|
241
69
|
/**
|
|
242
70
|
* The selection capability. The reducer state holds the selection range, the
|
|
243
|
-
* derived content-space
|
|
244
|
-
* non-serializable) per-page
|
|
71
|
+
* derived content-space segments (per page), and per-page loaded flags; the
|
|
72
|
+
* (large, non-serializable) per-page canonical layout is cached HERE in the
|
|
73
|
+
* closure. Segmentation itself is the engine-core text layout — selection
|
|
74
|
+
* owns gestures and state, never how glyphs become lines.
|
|
245
75
|
*
|
|
246
|
-
* Selection is cross-page: glyphs are ordered globally by (pageIndex, glyph),
|
|
247
|
-
* a drag from page 2 into page 4 selects the tail of 2, all of 3, and the
|
|
248
|
-
* 4. `recompute` rebuilds the merged line
|
|
249
|
-
* and re-runs whenever a mid-span page finishes loading.
|
|
76
|
+
* Selection is cross-page: glyphs are ordered globally by (pageIndex, glyph),
|
|
77
|
+
* so a drag from page 2 into page 4 selects the tail of 2, all of 3, and the
|
|
78
|
+
* head of 4. `recompute` rebuilds the merged line segments for every loaded
|
|
79
|
+
* page in the span and re-runs whenever a mid-span page finishes loading.
|
|
250
80
|
*/
|
|
251
81
|
function createSelectionCapability(ctx) {
|
|
252
82
|
const cache = /* @__PURE__ */ new Map();
|
|
@@ -256,6 +86,7 @@ function createSelectionCapability(ctx) {
|
|
|
256
86
|
const fireChange = () => changeCbs.forEach((cb) => cb());
|
|
257
87
|
const pageIndexOf = (pon) => ctx.document()?.pages.findIndex((p) => p.pageObjectNumber === pon) ?? -1;
|
|
258
88
|
const ponAtIndex = (i) => ctx.document()?.pages[i]?.pageObjectNumber;
|
|
89
|
+
const glyphAt = (geom, point) => (0, _embedpdf_engine_core_runtime.textGlyphAt)(geom.layout, contentPointToPdf(geom, point));
|
|
259
90
|
function orderedEnds(sel) {
|
|
260
91
|
const ai = pageIndexOf(sel.anchor.pon);
|
|
261
92
|
const fi = pageIndexOf(sel.focus.pon);
|
|
@@ -270,18 +101,32 @@ function createSelectionCapability(ctx) {
|
|
|
270
101
|
};
|
|
271
102
|
}
|
|
272
103
|
function endpointFor(ptr, which) {
|
|
273
|
-
const
|
|
274
|
-
if (!
|
|
104
|
+
const segments = ctx.getState().segments[ptr.pon] ?? EMPTY_SEGMENTS;
|
|
105
|
+
if (!segments.length) return null;
|
|
106
|
+
const segment = which === "start" ? segments[0] : segments[segments.length - 1];
|
|
107
|
+
const geom = cache.get(ptr.pon);
|
|
108
|
+
const cell = geom ? (0, _embedpdf_engine_core_runtime.textGlyphQuad)(geom.layout, ptr.glyph) : null;
|
|
109
|
+
if (geom && cell) {
|
|
110
|
+
const glyphQuad = toContentTextQuad(geom, cell);
|
|
111
|
+
return {
|
|
112
|
+
pon: ptr.pon,
|
|
113
|
+
glyphQuad,
|
|
114
|
+
advance: segment.advance,
|
|
115
|
+
rect: (0, _embedpdf_core_geometry.textQuadBounds)(glyphQuad)
|
|
116
|
+
};
|
|
117
|
+
}
|
|
275
118
|
return {
|
|
276
119
|
pon: ptr.pon,
|
|
277
|
-
|
|
120
|
+
glyphQuad: segment.quad,
|
|
121
|
+
advance: segment.advance,
|
|
122
|
+
rect: segment.rect
|
|
278
123
|
};
|
|
279
124
|
}
|
|
280
125
|
function snapshot() {
|
|
281
|
-
const { selection,
|
|
282
|
-
const pages = Object.keys(
|
|
126
|
+
const { selection, segments } = ctx.getState();
|
|
127
|
+
const pages = Object.keys(segments).map(Number).filter((pon) => (segments[pon]?.length ?? 0) > 0).map((pon) => ({
|
|
283
128
|
pon,
|
|
284
|
-
|
|
129
|
+
segments: segments[pon]
|
|
285
130
|
}));
|
|
286
131
|
if (!selection) return {
|
|
287
132
|
pages,
|
|
@@ -305,7 +150,7 @@ function createSelectionCapability(ctx) {
|
|
|
305
150
|
pending.add(pon);
|
|
306
151
|
doc.page(pon).geometry.read().then((snapshot) => {
|
|
307
152
|
pending.delete(pon);
|
|
308
|
-
cache.set(pon,
|
|
153
|
+
cache.set(pon, buildSelectionPageGeometry(snapshot, layout.boxes.crop, layout.rotation, layout.userUnit));
|
|
309
154
|
ctx.dispatch({
|
|
310
155
|
type: "PAGE_LOADED",
|
|
311
156
|
pon
|
|
@@ -321,30 +166,32 @@ function createSelectionCapability(ctx) {
|
|
|
321
166
|
const si = pageIndexOf(start.pon);
|
|
322
167
|
const ei = pageIndexOf(end.pon);
|
|
323
168
|
if (si < 0 || ei < 0) return;
|
|
324
|
-
const
|
|
169
|
+
const segments = {};
|
|
325
170
|
for (let i = si; i <= ei; i++) {
|
|
326
171
|
const pon = ponAtIndex(i);
|
|
327
172
|
if (pon == null) continue;
|
|
328
|
-
const
|
|
329
|
-
if (!
|
|
173
|
+
const geom = cache.get(pon);
|
|
174
|
+
if (!geom) {
|
|
330
175
|
ensurePage(pon);
|
|
331
176
|
continue;
|
|
332
177
|
}
|
|
333
|
-
|
|
178
|
+
const from = i === si ? start.glyph : 0;
|
|
179
|
+
const to = i === ei ? end.glyph : geom.layout.glyphs.length - 1;
|
|
180
|
+
segments[pon] = (0, _embedpdf_engine_core_runtime.textSegmentsForRange)(geom.layout, from, to - from + 1).map((s) => toContentSegment(geom, s));
|
|
334
181
|
}
|
|
335
182
|
ctx.dispatch({
|
|
336
183
|
type: "SET",
|
|
337
184
|
selection: sel,
|
|
338
|
-
|
|
185
|
+
segments
|
|
339
186
|
});
|
|
340
187
|
fireChange();
|
|
341
188
|
}
|
|
342
189
|
function selectSpan(pon, point, expand) {
|
|
343
|
-
const
|
|
344
|
-
if (!
|
|
345
|
-
const i = glyphAt(
|
|
190
|
+
const geom = cache.get(pon);
|
|
191
|
+
if (!geom) return;
|
|
192
|
+
const i = glyphAt(geom, point);
|
|
346
193
|
if (i == null) return;
|
|
347
|
-
const [from, to] = expand === "word" ?
|
|
194
|
+
const [from, to] = expand === "word" ? (0, _embedpdf_engine_core_runtime.expandTextRangeToWord)(geom.layout, i) : (0, _embedpdf_engine_core_runtime.expandTextRangeToLine)(geom.layout, i);
|
|
348
195
|
recompute({
|
|
349
196
|
anchor: {
|
|
350
197
|
pon,
|
|
@@ -360,13 +207,13 @@ function createSelectionCapability(ctx) {
|
|
|
360
207
|
ensurePage,
|
|
361
208
|
isLoaded: (pon) => !!ctx.getState().loaded[pon],
|
|
362
209
|
isOverText: (pon, point) => {
|
|
363
|
-
const
|
|
364
|
-
return
|
|
210
|
+
const geom = cache.get(pon);
|
|
211
|
+
return geom ? glyphAt(geom, point) != null : false;
|
|
365
212
|
},
|
|
366
213
|
beginAt: (pon, point) => {
|
|
367
|
-
const
|
|
368
|
-
if (!
|
|
369
|
-
const i = glyphAt(
|
|
214
|
+
const geom = cache.get(pon);
|
|
215
|
+
if (!geom) return false;
|
|
216
|
+
const i = glyphAt(geom, point);
|
|
370
217
|
if (i == null) return false;
|
|
371
218
|
recompute({
|
|
372
219
|
anchor: {
|
|
@@ -385,12 +232,12 @@ function createSelectionCapability(ctx) {
|
|
|
385
232
|
extendTo: (pon, point) => {
|
|
386
233
|
const cur = ctx.getState().selection;
|
|
387
234
|
if (!cur) return;
|
|
388
|
-
const
|
|
389
|
-
if (!
|
|
235
|
+
const geom = cache.get(pon);
|
|
236
|
+
if (!geom) {
|
|
390
237
|
ensurePage(pon);
|
|
391
238
|
return;
|
|
392
239
|
}
|
|
393
|
-
const i = glyphAt(
|
|
240
|
+
const i = glyphAt(geom, point);
|
|
394
241
|
if (i == null) return;
|
|
395
242
|
recompute({
|
|
396
243
|
anchor: cur.anchor,
|
|
@@ -406,11 +253,12 @@ function createSelectionCapability(ctx) {
|
|
|
406
253
|
fireChange();
|
|
407
254
|
},
|
|
408
255
|
snapshot,
|
|
409
|
-
|
|
256
|
+
segmentsForPage: (pon) => ctx.getState().segments[pon] ?? EMPTY_SEGMENTS,
|
|
257
|
+
rectsForPage: (pon) => (ctx.getState().segments[pon] ?? EMPTY_SEGMENTS).map((s) => s.rect),
|
|
410
258
|
hasSelection: () => ctx.getState().selection != null,
|
|
411
259
|
selectedPages: () => {
|
|
412
|
-
const {
|
|
413
|
-
return Object.keys(
|
|
260
|
+
const { segments } = ctx.getState();
|
|
261
|
+
return Object.keys(segments).map(Number).filter((pon) => (segments[pon]?.length ?? 0) > 0);
|
|
414
262
|
},
|
|
415
263
|
onChange: (cb) => {
|
|
416
264
|
changeCbs.add(cb);
|
|
@@ -504,7 +352,7 @@ function createTextSelectHandler(selection, interaction) {
|
|
|
504
352
|
//#region src/reducer.ts
|
|
505
353
|
const initialSelectionState = {
|
|
506
354
|
selection: null,
|
|
507
|
-
|
|
355
|
+
segments: {},
|
|
508
356
|
loaded: {},
|
|
509
357
|
highlightHidden: false
|
|
510
358
|
};
|
|
@@ -520,12 +368,12 @@ const selectionReducer = (state, action) => {
|
|
|
520
368
|
case "SET": return {
|
|
521
369
|
...state,
|
|
522
370
|
selection: action.selection,
|
|
523
|
-
|
|
371
|
+
segments: action.segments
|
|
524
372
|
};
|
|
525
|
-
case "CLEAR": return state.selection === null && Object.keys(state.
|
|
373
|
+
case "CLEAR": return state.selection === null && Object.keys(state.segments).length === 0 ? state : {
|
|
526
374
|
...state,
|
|
527
375
|
selection: null,
|
|
528
|
-
|
|
376
|
+
segments: {}
|
|
529
377
|
};
|
|
530
378
|
case "SET_HIGHLIGHT_HIDDEN": return state.highlightHidden === action.hidden ? state : {
|
|
531
379
|
...state,
|
|
@@ -561,15 +409,14 @@ const selectionPlugin = () => (0, _embedpdf_core.definePlugin)({
|
|
|
561
409
|
});
|
|
562
410
|
//#endregion
|
|
563
411
|
exports.SelectionToken = SelectionToken;
|
|
564
|
-
exports.
|
|
412
|
+
exports.buildSelectionPageGeometry = buildSelectionPageGeometry;
|
|
413
|
+
exports.contentPointToPdf = contentPointToPdf;
|
|
565
414
|
exports.createSelectionCapability = createSelectionCapability;
|
|
566
415
|
exports.createTextSelectHandler = createTextSelectHandler;
|
|
567
|
-
exports.expandToLine = expandToLine;
|
|
568
|
-
exports.expandToWord = expandToWord;
|
|
569
|
-
exports.glyphAt = glyphAt;
|
|
570
416
|
exports.initialSelectionState = initialSelectionState;
|
|
571
|
-
exports.rectsForRange = rectsForRange;
|
|
572
417
|
exports.selectionPlugin = selectionPlugin;
|
|
573
418
|
exports.selectionReducer = selectionReducer;
|
|
419
|
+
exports.toContentSegment = toContentSegment;
|
|
420
|
+
exports.toContentTextQuad = toContentTextQuad;
|
|
574
421
|
|
|
575
422
|
//# sourceMappingURL=index.cjs.map
|