react-pdf-highlighter-plus 1.1.3 → 1.2.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 +235 -52
- package/dist/esm/export-pdf-W2QGWADM.js +403 -0
- package/dist/esm/export-pdf-W2QGWADM.js.map +1 -0
- package/dist/esm/index.d.ts +309 -86
- package/dist/esm/index.js +2417 -820
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/pdf.worker.min.mjs +21 -0
- package/dist/esm/style/AreaHighlight.css +9 -2
- package/dist/esm/style/DrawingHighlight.css +10 -0
- package/dist/esm/style/FreetextHighlight.css +60 -0
- package/dist/esm/style/ImageHighlight.css +2 -1
- package/dist/esm/style/PdfHighlighter.css +103 -12
- package/dist/esm/style/TextHighlight.css +9 -2
- package/package.json +1 -1
package/dist/esm/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// src/components/PdfHighlighter.tsx
|
|
2
|
-
import debounce from "lodash.debounce";
|
|
3
2
|
import React6, {
|
|
4
3
|
useEffect as useEffect4,
|
|
5
4
|
useLayoutEffect as useLayoutEffect2,
|
|
@@ -88,6 +87,323 @@ var scaledPositionToViewport = ({ boundingRect, rects, usePdfCoordinates }, view
|
|
|
88
87
|
};
|
|
89
88
|
};
|
|
90
89
|
|
|
90
|
+
// src/lib/dark-mode.ts
|
|
91
|
+
var DEFAULT_DARK_MODE_COLORS = {
|
|
92
|
+
background: "#141210",
|
|
93
|
+
foreground: "#eae6e0"
|
|
94
|
+
};
|
|
95
|
+
var NAMED_COLORS = {
|
|
96
|
+
white: [1, 1, 1, 1],
|
|
97
|
+
black: [0, 0, 0, 1],
|
|
98
|
+
transparent: [0, 0, 0, 0]
|
|
99
|
+
};
|
|
100
|
+
function clamp01(value) {
|
|
101
|
+
return value < 0 ? 0 : value > 1 ? 1 : value;
|
|
102
|
+
}
|
|
103
|
+
function parseColor(input) {
|
|
104
|
+
const str = input.trim().toLowerCase();
|
|
105
|
+
const named = Object.hasOwn(NAMED_COLORS, str) ? NAMED_COLORS[str] : void 0;
|
|
106
|
+
if (named) return named;
|
|
107
|
+
if (str.startsWith("#")) {
|
|
108
|
+
const hex = str.slice(1);
|
|
109
|
+
if (!/^[0-9a-f]{3,8}$/.test(hex)) return null;
|
|
110
|
+
if (hex.length === 3 || hex.length === 4) {
|
|
111
|
+
const r = Number.parseInt(hex[0], 16) / 15;
|
|
112
|
+
const g = Number.parseInt(hex[1], 16) / 15;
|
|
113
|
+
const b = Number.parseInt(hex[2], 16) / 15;
|
|
114
|
+
const a = hex.length === 4 ? Number.parseInt(hex[3], 16) / 15 : 1;
|
|
115
|
+
return [r, g, b, a];
|
|
116
|
+
}
|
|
117
|
+
if (hex.length === 6 || hex.length === 8) {
|
|
118
|
+
const r = Number.parseInt(hex.slice(0, 2), 16) / 255;
|
|
119
|
+
const g = Number.parseInt(hex.slice(2, 4), 16) / 255;
|
|
120
|
+
const b = Number.parseInt(hex.slice(4, 6), 16) / 255;
|
|
121
|
+
const a = hex.length === 8 ? Number.parseInt(hex.slice(6, 8), 16) / 255 : 1;
|
|
122
|
+
return [r, g, b, a];
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
const fn = /^rgba?\(([^)]+)\)$/.exec(str);
|
|
127
|
+
if (fn) {
|
|
128
|
+
const parts = fn[1].split(/[,\s/]+/).filter(Boolean);
|
|
129
|
+
if (parts.length < 3) return null;
|
|
130
|
+
const channel = (part) => part.endsWith("%") ? Number.parseFloat(part) / 100 : Number.parseFloat(part) / 255;
|
|
131
|
+
const alpha = (part) => part.endsWith("%") ? Number.parseFloat(part) / 100 : Number.parseFloat(part);
|
|
132
|
+
const r = channel(parts[0]);
|
|
133
|
+
const g = channel(parts[1]);
|
|
134
|
+
const b = channel(parts[2]);
|
|
135
|
+
const a = parts[3] !== void 0 ? alpha(parts[3]) : 1;
|
|
136
|
+
if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b) || Number.isNaN(a))
|
|
137
|
+
return null;
|
|
138
|
+
return [clamp01(r), clamp01(g), clamp01(b), clamp01(a)];
|
|
139
|
+
}
|
|
140
|
+
const normalized = normalizeCssColor(str);
|
|
141
|
+
if (normalized !== null && normalized !== str) return parseColor(normalized);
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
var normalizeCtx;
|
|
145
|
+
function normalizeCssColor(input) {
|
|
146
|
+
if (normalizeCtx === void 0) {
|
|
147
|
+
normalizeCtx = typeof document === "undefined" ? null : document.createElement("canvas").getContext("2d", {
|
|
148
|
+
willReadFrequently: true
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
const ctx = normalizeCtx;
|
|
152
|
+
if (!ctx) return null;
|
|
153
|
+
ctx.fillStyle = "#000000";
|
|
154
|
+
ctx.fillStyle = input;
|
|
155
|
+
const first = ctx.fillStyle;
|
|
156
|
+
ctx.fillStyle = "#ffffff";
|
|
157
|
+
ctx.fillStyle = input;
|
|
158
|
+
return first === ctx.fillStyle ? first : null;
|
|
159
|
+
}
|
|
160
|
+
function srgbToLinear(c) {
|
|
161
|
+
return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
|
162
|
+
}
|
|
163
|
+
function linearToSrgb(c) {
|
|
164
|
+
return c <= 31308e-7 ? 12.92 * c : 1.055 * c ** (1 / 2.4) - 0.055;
|
|
165
|
+
}
|
|
166
|
+
function rgbToOklab(r, g, b) {
|
|
167
|
+
const lr = srgbToLinear(r);
|
|
168
|
+
const lg = srgbToLinear(g);
|
|
169
|
+
const lb = srgbToLinear(b);
|
|
170
|
+
const l = Math.cbrt(0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb);
|
|
171
|
+
const m = Math.cbrt(0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb);
|
|
172
|
+
const s = Math.cbrt(0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb);
|
|
173
|
+
return [
|
|
174
|
+
0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,
|
|
175
|
+
1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,
|
|
176
|
+
0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s
|
|
177
|
+
];
|
|
178
|
+
}
|
|
179
|
+
function oklabToLinearRgb(L, a, b) {
|
|
180
|
+
const l = (L + 0.3963377774 * a + 0.2158037573 * b) ** 3;
|
|
181
|
+
const m = (L - 0.1055613458 * a - 0.0638541728 * b) ** 3;
|
|
182
|
+
const s = (L - 0.0894841775 * a - 1.291485548 * b) ** 3;
|
|
183
|
+
return [
|
|
184
|
+
4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
|
|
185
|
+
-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
|
|
186
|
+
-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s
|
|
187
|
+
];
|
|
188
|
+
}
|
|
189
|
+
var GAMUT_EPSILON = 5e-4;
|
|
190
|
+
function oklabToSrgbExact(L, a, b) {
|
|
191
|
+
const [lr, lg, lb] = oklabToLinearRgb(L, a, b);
|
|
192
|
+
if (lr < -GAMUT_EPSILON || lr > 1 + GAMUT_EPSILON || lg < -GAMUT_EPSILON || lg > 1 + GAMUT_EPSILON || lb < -GAMUT_EPSILON || lb > 1 + GAMUT_EPSILON) {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
return [
|
|
196
|
+
linearToSrgb(clamp01(lr)),
|
|
197
|
+
linearToSrgb(clamp01(lg)),
|
|
198
|
+
linearToSrgb(clamp01(lb))
|
|
199
|
+
];
|
|
200
|
+
}
|
|
201
|
+
function oklabToSrgbGamutMapped(L, a, b) {
|
|
202
|
+
const exact = oklabToSrgbExact(L, a, b);
|
|
203
|
+
if (exact) return exact;
|
|
204
|
+
let lo = 0;
|
|
205
|
+
let hi = 1;
|
|
206
|
+
for (let i = 0; i < 12; i++) {
|
|
207
|
+
const mid = (lo + hi) / 2;
|
|
208
|
+
if (oklabToSrgbExact(L, a * mid, b * mid)) lo = mid;
|
|
209
|
+
else hi = mid;
|
|
210
|
+
}
|
|
211
|
+
const [lr, lg, lb] = oklabToLinearRgb(L, a * lo, b * lo);
|
|
212
|
+
return [
|
|
213
|
+
linearToSrgb(clamp01(lr)),
|
|
214
|
+
linearToSrgb(clamp01(lg)),
|
|
215
|
+
linearToSrgb(clamp01(lb))
|
|
216
|
+
];
|
|
217
|
+
}
|
|
218
|
+
function toHexByte(channel) {
|
|
219
|
+
return Math.round(clamp01(channel) * 255).toString(16).padStart(2, "0");
|
|
220
|
+
}
|
|
221
|
+
function toCssColor(r, g, b, a) {
|
|
222
|
+
const rgb = `#${toHexByte(r)}${toHexByte(g)}${toHexByte(b)}`;
|
|
223
|
+
return a >= 1 ? rgb : `${rgb}${toHexByte(a)}`;
|
|
224
|
+
}
|
|
225
|
+
var NEUTRAL_CHROMA_THRESHOLD = 0.04;
|
|
226
|
+
var COLOR_CACHE_MAX = 4096;
|
|
227
|
+
var mapInstances = /* @__PURE__ */ new Map();
|
|
228
|
+
function createDarkModeColorMap(colors) {
|
|
229
|
+
const background = colors?.background ?? DEFAULT_DARK_MODE_COLORS.background;
|
|
230
|
+
const foreground = colors?.foreground ?? DEFAULT_DARK_MODE_COLORS.foreground;
|
|
231
|
+
const instanceKey = `${background}|${foreground}`;
|
|
232
|
+
const existing = mapInstances.get(instanceKey);
|
|
233
|
+
if (existing) {
|
|
234
|
+
mapInstances.delete(instanceKey);
|
|
235
|
+
mapInstances.set(instanceKey, existing);
|
|
236
|
+
return existing;
|
|
237
|
+
}
|
|
238
|
+
const bgRgba = parseColor(background) ?? parseColor(DEFAULT_DARK_MODE_COLORS.background);
|
|
239
|
+
const fgRgba = parseColor(foreground) ?? parseColor(DEFAULT_DARK_MODE_COLORS.foreground);
|
|
240
|
+
const bgLab = rgbToOklab(bgRgba[0], bgRgba[1], bgRgba[2]);
|
|
241
|
+
const fgLab = rgbToOklab(fgRgba[0], fgRgba[1], fgRgba[2]);
|
|
242
|
+
const cache = /* @__PURE__ */ new Map();
|
|
243
|
+
const backgroundCss = toCssColor(bgRgba[0], bgRgba[1], bgRgba[2], 1);
|
|
244
|
+
const foregroundCss = toCssColor(fgRgba[0], fgRgba[1], fgRgba[2], 1);
|
|
245
|
+
const seedPoles = () => {
|
|
246
|
+
for (const white of ["#ffffff", "#fff", "white"])
|
|
247
|
+
cache.set(white, backgroundCss);
|
|
248
|
+
for (const black of ["#000000", "#000", "black"])
|
|
249
|
+
cache.set(black, foregroundCss);
|
|
250
|
+
};
|
|
251
|
+
seedPoles();
|
|
252
|
+
const transform = (input) => {
|
|
253
|
+
const parsed = parseColor(input);
|
|
254
|
+
if (!parsed || parsed[3] === 0) return input;
|
|
255
|
+
const [r, g, b, alpha] = parsed;
|
|
256
|
+
const [, labA, labB] = rgbToOklab(r, g, b);
|
|
257
|
+
const luma = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
258
|
+
const rampL = fgLab[0] + (bgLab[0] - fgLab[0]) * luma;
|
|
259
|
+
const rampA = fgLab[1] + (bgLab[1] - fgLab[1]) * luma;
|
|
260
|
+
const rampB = fgLab[2] + (bgLab[2] - fgLab[2]) * luma;
|
|
261
|
+
const chroma = Math.hypot(labA, labB);
|
|
262
|
+
const hueWeight = Math.min(1, chroma / NEUTRAL_CHROMA_THRESHOLD);
|
|
263
|
+
const outA = rampA + (labA - rampA) * hueWeight;
|
|
264
|
+
const outB = rampB + (labB - rampB) * hueWeight;
|
|
265
|
+
const [sr, sg, sb] = oklabToSrgbGamutMapped(rampL, outA, outB);
|
|
266
|
+
return toCssColor(sr, sg, sb, alpha);
|
|
267
|
+
};
|
|
268
|
+
const map = (input) => {
|
|
269
|
+
const hit = cache.get(input);
|
|
270
|
+
if (hit !== void 0) return hit;
|
|
271
|
+
const out = transform(input);
|
|
272
|
+
if (cache.size >= COLOR_CACHE_MAX) {
|
|
273
|
+
cache.clear();
|
|
274
|
+
seedPoles();
|
|
275
|
+
}
|
|
276
|
+
cache.set(input, out);
|
|
277
|
+
return out;
|
|
278
|
+
};
|
|
279
|
+
if (mapInstances.size >= 16) {
|
|
280
|
+
const oldest = mapInstances.keys().next().value;
|
|
281
|
+
if (oldest !== void 0) mapInstances.delete(oldest);
|
|
282
|
+
}
|
|
283
|
+
mapInstances.set(instanceKey, map);
|
|
284
|
+
return map;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// src/lib/recolor-context.ts
|
|
288
|
+
var RECOLOR_CLEANUP = /* @__PURE__ */ Symbol("recolorCleanup");
|
|
289
|
+
var RECOLOR_PAINTED = /* @__PURE__ */ Symbol("recolorPainted");
|
|
290
|
+
var FILL_METHODS = ["fill", "fillRect", "fillText"];
|
|
291
|
+
var STROKE_METHODS = ["stroke", "strokeRect", "strokeText"];
|
|
292
|
+
var GRADIENT_METHODS = [
|
|
293
|
+
"createLinearGradient",
|
|
294
|
+
"createRadialGradient"
|
|
295
|
+
];
|
|
296
|
+
function ntscLuma(r, g, b) {
|
|
297
|
+
return 0.3 * r + 0.59 * g + 0.11 * b;
|
|
298
|
+
}
|
|
299
|
+
function parseHexLuma(color) {
|
|
300
|
+
if (!/^#[0-9a-f]{6}$/i.test(color)) return null;
|
|
301
|
+
return ntscLuma(
|
|
302
|
+
Number.parseInt(color.slice(1, 3), 16),
|
|
303
|
+
Number.parseInt(color.slice(3, 5), 16),
|
|
304
|
+
Number.parseInt(color.slice(5, 7), 16)
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
function correctLuminosityMask(source, mappedWhiteLuma, mappedBlackLuma) {
|
|
308
|
+
if (typeof document === "undefined") return null;
|
|
309
|
+
if (!(typeof HTMLCanvasElement !== "undefined" && source instanceof HTMLCanvasElement) && !(typeof OffscreenCanvas !== "undefined" && source instanceof OffscreenCanvas)) {
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
const width = source.width;
|
|
313
|
+
const height = source.height;
|
|
314
|
+
if (!width || !height) return null;
|
|
315
|
+
const span = mappedWhiteLuma - mappedBlackLuma;
|
|
316
|
+
if (Math.abs(span) < 1) return null;
|
|
317
|
+
const tmp = document.createElement("canvas");
|
|
318
|
+
tmp.width = width;
|
|
319
|
+
tmp.height = height;
|
|
320
|
+
const tmpCtx = tmp.getContext("2d", { willReadFrequently: true });
|
|
321
|
+
if (!tmpCtx) return null;
|
|
322
|
+
tmpCtx.drawImage(source, 0, 0);
|
|
323
|
+
let image;
|
|
324
|
+
try {
|
|
325
|
+
image = tmpCtx.getImageData(0, 0, width, height);
|
|
326
|
+
} catch {
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
const data = image.data;
|
|
330
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
331
|
+
const luma = ntscLuma(data[i], data[i + 1], data[i + 2]);
|
|
332
|
+
const t = (luma - mappedBlackLuma) / span;
|
|
333
|
+
const gray = t <= 0 ? 0 : t >= 1 ? 255 : Math.round(t * 255);
|
|
334
|
+
data[i] = data[i + 1] = data[i + 2] = gray;
|
|
335
|
+
}
|
|
336
|
+
tmpCtx.putImageData(image, 0, 0);
|
|
337
|
+
return tmp;
|
|
338
|
+
}
|
|
339
|
+
function applyContextRecolor(ctx, map) {
|
|
340
|
+
const target = ctx;
|
|
341
|
+
target[RECOLOR_CLEANUP]?.();
|
|
342
|
+
const mappedWhiteLuma = parseHexLuma(map("#ffffff"));
|
|
343
|
+
const mappedBlackLuma = parseHexLuma(map("#000000"));
|
|
344
|
+
const withMaskCorrection = (original) => function(...args) {
|
|
345
|
+
if (mappedWhiteLuma !== null && mappedBlackLuma !== null && this.globalCompositeOperation === "destination-in" && typeof this.filter === "string" && this.filter.includes("luminosity")) {
|
|
346
|
+
const source = args[0];
|
|
347
|
+
const sourcePainted = (typeof HTMLCanvasElement !== "undefined" && source instanceof HTMLCanvasElement || typeof OffscreenCanvas !== "undefined" && source instanceof OffscreenCanvas) && source.getContext("2d")?.[RECOLOR_PAINTED] === true;
|
|
348
|
+
const corrected = sourcePainted ? correctLuminosityMask(source, mappedWhiteLuma, mappedBlackLuma) : null;
|
|
349
|
+
if (corrected) {
|
|
350
|
+
const next = [corrected, ...args.slice(1)];
|
|
351
|
+
return original.apply(this, next);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return original.apply(this, args);
|
|
355
|
+
};
|
|
356
|
+
const withMappedStyle = (original, styleProp) => function(...args) {
|
|
357
|
+
const style = this[styleProp];
|
|
358
|
+
if (typeof style === "string") {
|
|
359
|
+
const mapped = map(style);
|
|
360
|
+
if (mapped !== style) {
|
|
361
|
+
this[RECOLOR_PAINTED] = true;
|
|
362
|
+
this[styleProp] = mapped;
|
|
363
|
+
try {
|
|
364
|
+
return original.apply(this, args);
|
|
365
|
+
} finally {
|
|
366
|
+
this[styleProp] = style;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return original.apply(this, args);
|
|
371
|
+
};
|
|
372
|
+
const withMappedStops = (original) => function(...args) {
|
|
373
|
+
const gradient = original.apply(this, args);
|
|
374
|
+
const addColorStop = gradient.addColorStop.bind(gradient);
|
|
375
|
+
const context = this;
|
|
376
|
+
gradient.addColorStop = (offset, color) => {
|
|
377
|
+
const mapped = map(color);
|
|
378
|
+
if (mapped !== color) context[RECOLOR_PAINTED] = true;
|
|
379
|
+
addColorStop(offset, mapped);
|
|
380
|
+
};
|
|
381
|
+
return gradient;
|
|
382
|
+
};
|
|
383
|
+
const record = target;
|
|
384
|
+
for (const name of FILL_METHODS)
|
|
385
|
+
record[name] = withMappedStyle(target[name], "fillStyle");
|
|
386
|
+
for (const name of STROKE_METHODS)
|
|
387
|
+
record[name] = withMappedStyle(target[name], "strokeStyle");
|
|
388
|
+
for (const name of GRADIENT_METHODS)
|
|
389
|
+
record[name] = withMappedStops(target[name]);
|
|
390
|
+
record.drawImage = withMaskCorrection(target.drawImage);
|
|
391
|
+
const cleanup = () => {
|
|
392
|
+
if (target[RECOLOR_CLEANUP] !== cleanup) return;
|
|
393
|
+
for (const name of [
|
|
394
|
+
...FILL_METHODS,
|
|
395
|
+
...STROKE_METHODS,
|
|
396
|
+
...GRADIENT_METHODS,
|
|
397
|
+
"drawImage"
|
|
398
|
+
])
|
|
399
|
+
delete record[name];
|
|
400
|
+
delete target[RECOLOR_CLEANUP];
|
|
401
|
+
delete target[RECOLOR_PAINTED];
|
|
402
|
+
};
|
|
403
|
+
target[RECOLOR_CLEANUP] = cleanup;
|
|
404
|
+
return cleanup;
|
|
405
|
+
}
|
|
406
|
+
|
|
91
407
|
// src/lib/get-bounding-rect.ts
|
|
92
408
|
var getBoundingRect = (clientRects) => {
|
|
93
409
|
const rects = Array.from(clientRects).map((rect) => {
|
|
@@ -293,16 +609,6 @@ var getPagesFromRange = (range) => {
|
|
|
293
609
|
}
|
|
294
610
|
return pages;
|
|
295
611
|
};
|
|
296
|
-
var findOrCreateContainerLayer = (container, className) => {
|
|
297
|
-
const doc = getDocument(container);
|
|
298
|
-
let layer = container.querySelector(`.${className}`);
|
|
299
|
-
if (!layer && container.children.length) {
|
|
300
|
-
layer = doc.createElement("div");
|
|
301
|
-
layer.className = className;
|
|
302
|
-
container.appendChild(layer);
|
|
303
|
-
}
|
|
304
|
-
return layer;
|
|
305
|
-
};
|
|
306
612
|
|
|
307
613
|
// src/components/DrawingCanvas.tsx
|
|
308
614
|
import React, {
|
|
@@ -659,9 +965,12 @@ var HighlightLayer = ({
|
|
|
659
965
|
scrolledToHighlightId,
|
|
660
966
|
viewer,
|
|
661
967
|
highlightBindings,
|
|
662
|
-
children
|
|
968
|
+
children,
|
|
969
|
+
shouldRenderHighlight
|
|
663
970
|
}) => {
|
|
664
|
-
const currentHighlights = highlightsByPage[pageNumber] || []
|
|
971
|
+
const currentHighlights = (highlightsByPage[pageNumber] || []).filter(
|
|
972
|
+
(highlight) => shouldRenderHighlight?.(highlight) ?? true
|
|
973
|
+
);
|
|
665
974
|
return /* @__PURE__ */ React2.createElement("div", null, currentHighlights.map((highlight, index) => {
|
|
666
975
|
const viewportHighlight = {
|
|
667
976
|
...highlight,
|
|
@@ -1111,38 +1420,94 @@ var TipContainer = ({
|
|
|
1111
1420
|
|
|
1112
1421
|
// src/components/PdfHighlighter.tsx
|
|
1113
1422
|
var EventBus;
|
|
1423
|
+
var PDFFindController;
|
|
1114
1424
|
var PDFLinkService;
|
|
1115
1425
|
var PDFViewer;
|
|
1116
1426
|
(async () => {
|
|
1117
1427
|
const pdfjs = await import("pdfjs-dist/web/pdf_viewer.mjs");
|
|
1118
1428
|
EventBus = pdfjs.EventBus;
|
|
1429
|
+
PDFFindController = pdfjs.PDFFindController;
|
|
1119
1430
|
PDFLinkService = pdfjs.PDFLinkService;
|
|
1120
1431
|
PDFViewer = pdfjs.PDFViewer;
|
|
1121
1432
|
})();
|
|
1122
1433
|
var SCROLL_MARGIN = 10;
|
|
1123
1434
|
var DEFAULT_SCALE_VALUE = "auto";
|
|
1124
1435
|
var DEFAULT_TEXT_SELECTION_COLOR = "rgba(153,193,218,255)";
|
|
1436
|
+
var DEFAULT_DARK_MODE_COLORS2 = {
|
|
1437
|
+
background: "#141210",
|
|
1438
|
+
foreground: "#eae6e0"
|
|
1439
|
+
};
|
|
1440
|
+
var unmountReactRoot = (root) => {
|
|
1441
|
+
if (!root) return;
|
|
1442
|
+
queueMicrotask(() => {
|
|
1443
|
+
try {
|
|
1444
|
+
root.unmount();
|
|
1445
|
+
} catch {
|
|
1446
|
+
}
|
|
1447
|
+
});
|
|
1448
|
+
};
|
|
1449
|
+
var RECOLOR_PATCHED = /* @__PURE__ */ Symbol("pdfRecolorPatched");
|
|
1450
|
+
var patchPageRenderRecolor = (pdfDocument, getMap) => {
|
|
1451
|
+
const doc = pdfDocument;
|
|
1452
|
+
if (doc[RECOLOR_PATCHED]) return;
|
|
1453
|
+
doc[RECOLOR_PATCHED] = true;
|
|
1454
|
+
const origGetPage = doc.getPage.bind(doc);
|
|
1455
|
+
doc.getPage = (pageNumber) => origGetPage(pageNumber).then((page) => {
|
|
1456
|
+
const p = page;
|
|
1457
|
+
if (p[RECOLOR_PATCHED]) return p;
|
|
1458
|
+
p[RECOLOR_PATCHED] = true;
|
|
1459
|
+
const origRender = p.render.bind(p);
|
|
1460
|
+
p.render = ((params) => {
|
|
1461
|
+
const map = getMap();
|
|
1462
|
+
const ctx = params?.canvasContext;
|
|
1463
|
+
if (map && ctx) {
|
|
1464
|
+
const cleanup = applyContextRecolor(ctx, map);
|
|
1465
|
+
const task = origRender(params);
|
|
1466
|
+
task.promise.then(cleanup, cleanup);
|
|
1467
|
+
return task;
|
|
1468
|
+
}
|
|
1469
|
+
return origRender(params);
|
|
1470
|
+
});
|
|
1471
|
+
return p;
|
|
1472
|
+
});
|
|
1473
|
+
};
|
|
1125
1474
|
var defaultLightTheme = {
|
|
1126
1475
|
mode: "light",
|
|
1127
1476
|
containerBackgroundColor: "#e5e5e5",
|
|
1128
1477
|
scrollbarThumbColor: "#9f9f9f",
|
|
1129
1478
|
scrollbarTrackColor: "#d1d1d1",
|
|
1130
|
-
darkModeInvertIntensity: 0.9
|
|
1479
|
+
darkModeInvertIntensity: 0.9,
|
|
1480
|
+
darkModeColors: DEFAULT_DARK_MODE_COLORS2
|
|
1131
1481
|
};
|
|
1132
1482
|
var defaultDarkTheme = {
|
|
1133
1483
|
mode: "dark",
|
|
1134
1484
|
containerBackgroundColor: "#3a3a3a",
|
|
1135
|
-
// Lighter than PDF page
|
|
1485
|
+
// Lighter than PDF page for contrast
|
|
1136
1486
|
scrollbarThumbColor: "#6b6b6b",
|
|
1137
1487
|
scrollbarTrackColor: "#2c2c2c",
|
|
1138
|
-
darkModeInvertIntensity: 0.9
|
|
1488
|
+
darkModeInvertIntensity: 0.9,
|
|
1489
|
+
darkModeColors: DEFAULT_DARK_MODE_COLORS2
|
|
1139
1490
|
};
|
|
1140
|
-
var
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1491
|
+
var ensurePersistentLayer = (bindings, pageNumber, parent, className) => {
|
|
1492
|
+
let binding = bindings[pageNumber];
|
|
1493
|
+
if (!binding) {
|
|
1494
|
+
const doc = getDocument(parent);
|
|
1495
|
+
const layer = doc.createElement("div");
|
|
1496
|
+
layer.className = className;
|
|
1497
|
+
parent.appendChild(layer);
|
|
1498
|
+
binding = {
|
|
1499
|
+
reactRoot: createRoot(layer),
|
|
1500
|
+
container: layer,
|
|
1501
|
+
textLayer: parent
|
|
1502
|
+
};
|
|
1503
|
+
bindings[pageNumber] = binding;
|
|
1504
|
+
} else if (binding.container.parentNode !== parent) {
|
|
1505
|
+
parent.appendChild(binding.container);
|
|
1506
|
+
binding.textLayer = parent;
|
|
1507
|
+
}
|
|
1508
|
+
return binding;
|
|
1145
1509
|
};
|
|
1510
|
+
var isFreetextHighlight = (highlight) => "type" in highlight && highlight.type === "freetext";
|
|
1146
1511
|
var disableTextSelection = (viewer, flag) => {
|
|
1147
1512
|
viewer.viewer?.classList.toggle("PdfHighlighter--disable-selection", flag);
|
|
1148
1513
|
};
|
|
@@ -1150,6 +1515,9 @@ var PdfHighlighter = ({
|
|
|
1150
1515
|
highlights,
|
|
1151
1516
|
onScrollAway,
|
|
1152
1517
|
pdfScaleValue = DEFAULT_SCALE_VALUE,
|
|
1518
|
+
onZoomChange,
|
|
1519
|
+
initialPage,
|
|
1520
|
+
onPageChange,
|
|
1153
1521
|
onSelection: onSelectionFinished,
|
|
1154
1522
|
onCreateGhostHighlight,
|
|
1155
1523
|
onRemoveGhostHighlight,
|
|
@@ -1169,12 +1537,12 @@ var PdfHighlighter = ({
|
|
|
1169
1537
|
enableDrawingMode,
|
|
1170
1538
|
onDrawingComplete,
|
|
1171
1539
|
onDrawingCancel,
|
|
1172
|
-
drawingStrokeColor
|
|
1540
|
+
drawingStrokeColor: drawingStrokeColorProp,
|
|
1173
1541
|
drawingStrokeWidth = 3,
|
|
1174
1542
|
enableShapeMode,
|
|
1175
1543
|
onShapeComplete,
|
|
1176
1544
|
onShapeCancel,
|
|
1177
|
-
shapeStrokeColor
|
|
1545
|
+
shapeStrokeColor: shapeStrokeColorProp,
|
|
1178
1546
|
shapeStrokeWidth = 2,
|
|
1179
1547
|
theme: userTheme
|
|
1180
1548
|
}) => {
|
|
@@ -1183,12 +1551,27 @@ var PdfHighlighter = ({
|
|
|
1183
1551
|
const defaults = mode === "light" ? defaultLightTheme : defaultDarkTheme;
|
|
1184
1552
|
return { ...defaults, ...userTheme, mode };
|
|
1185
1553
|
}, [userTheme]);
|
|
1554
|
+
const recolorKey = resolvedTheme.mode === "dark" ? `${resolvedTheme.darkModeColors.background}|${resolvedTheme.darkModeColors.foreground}` : "light";
|
|
1555
|
+
const recolorMap = useMemo(
|
|
1556
|
+
() => resolvedTheme.mode === "dark" ? createDarkModeColorMap(resolvedTheme.darkModeColors) : null,
|
|
1557
|
+
// recolorKey captures the palette + mode; map identity is stable per key.
|
|
1558
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1559
|
+
[recolorKey]
|
|
1560
|
+
);
|
|
1561
|
+
const recolorMapRef = useRef5(recolorMap);
|
|
1562
|
+
recolorMapRef.current = recolorMap;
|
|
1563
|
+
const defaultStrokeColor = resolvedTheme.mode === "dark" ? "#ffffff" : "#000000";
|
|
1564
|
+
const drawingStrokeColor = drawingStrokeColorProp ?? defaultStrokeColor;
|
|
1565
|
+
const shapeStrokeColor = shapeStrokeColorProp ?? defaultStrokeColor;
|
|
1186
1566
|
const [tip, setTip] = useState5(null);
|
|
1187
1567
|
const [isViewerReady, setIsViewerReady] = useState5(false);
|
|
1188
1568
|
const containerNodeRef = useRef5(null);
|
|
1189
1569
|
const highlightBindingsRef = useRef5(
|
|
1190
1570
|
{}
|
|
1191
1571
|
);
|
|
1572
|
+
const noteBindingsRef = useRef5({});
|
|
1573
|
+
const highlightsRef = useRef5(highlights);
|
|
1574
|
+
const childrenRef = useRef5(children);
|
|
1192
1575
|
const ghostHighlightRef = useRef5(null);
|
|
1193
1576
|
const selectionRef = useRef5(null);
|
|
1194
1577
|
const scrolledToHighlightIdRef = useRef5(null);
|
|
@@ -1204,43 +1587,324 @@ var PdfHighlighter = ({
|
|
|
1204
1587
|
})
|
|
1205
1588
|
);
|
|
1206
1589
|
const resizeObserverRef = useRef5(null);
|
|
1590
|
+
const renderRetryTimeoutsRef = useRef5(
|
|
1591
|
+
[]
|
|
1592
|
+
);
|
|
1593
|
+
const resumeScrollAwayTimeoutRef = useRef5(
|
|
1594
|
+
null
|
|
1595
|
+
);
|
|
1596
|
+
const findControllerRef = useRef5(null);
|
|
1207
1597
|
const viewerRef = useRef5(null);
|
|
1598
|
+
const prevDocRef = useRef5(null);
|
|
1599
|
+
const lastRenderKeyRef = useRef5(null);
|
|
1600
|
+
highlightsRef.current = highlights;
|
|
1601
|
+
childrenRef.current = children;
|
|
1208
1602
|
useLayoutEffect2(() => {
|
|
1209
1603
|
if (!containerNodeRef.current) return;
|
|
1210
|
-
const
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
};
|
|
1227
|
-
|
|
1604
|
+
const renderKey = `${pdfDocument.fingerprints ?? pdfDocument.numPages}::${recolorKey}`;
|
|
1605
|
+
if (lastRenderKeyRef.current === renderKey) {
|
|
1606
|
+
console.log("[PdfHighlighter] skip duplicate setDocument", renderKey);
|
|
1607
|
+
return;
|
|
1608
|
+
}
|
|
1609
|
+
findControllerRef.current = findControllerRef.current || new PDFFindController({
|
|
1610
|
+
eventBus: eventBusRef.current,
|
|
1611
|
+
linkService: linkServiceRef.current
|
|
1612
|
+
});
|
|
1613
|
+
viewerRef.current = viewerRef.current || new PDFViewer({
|
|
1614
|
+
container: containerNodeRef.current,
|
|
1615
|
+
eventBus: eventBusRef.current,
|
|
1616
|
+
findController: findControllerRef.current,
|
|
1617
|
+
textLayerMode: 2,
|
|
1618
|
+
removePageBorders: true,
|
|
1619
|
+
linkService: linkServiceRef.current
|
|
1620
|
+
});
|
|
1621
|
+
patchPageRenderRecolor(pdfDocument, () => recolorMapRef.current);
|
|
1622
|
+
const isToggle = prevDocRef.current === pdfDocument;
|
|
1623
|
+
prevDocRef.current = pdfDocument;
|
|
1624
|
+
let scrollFraction = 0;
|
|
1625
|
+
let savedScaleValue;
|
|
1626
|
+
if (isToggle && viewerRef.current.container) {
|
|
1627
|
+
const c = viewerRef.current.container;
|
|
1628
|
+
const range = c.scrollHeight - c.clientHeight;
|
|
1629
|
+
scrollFraction = range > 0 ? c.scrollTop / range : 0;
|
|
1630
|
+
savedScaleValue = viewerRef.current.currentScaleValue ?? viewerRef.current.currentScale;
|
|
1631
|
+
}
|
|
1632
|
+
console.log("[PdfHighlighter] recolor active?", {
|
|
1633
|
+
mode: resolvedTheme.mode,
|
|
1634
|
+
hasMap: !!recolorMapRef.current,
|
|
1635
|
+
palette: resolvedTheme.darkModeColors,
|
|
1636
|
+
isToggle,
|
|
1637
|
+
scrollFraction,
|
|
1638
|
+
savedScaleValue
|
|
1639
|
+
});
|
|
1640
|
+
if (isToggle) {
|
|
1641
|
+
const restoreView = () => {
|
|
1642
|
+
eventBusRef.current.off("pagesinit", restoreView);
|
|
1643
|
+
if (savedScaleValue != null && viewerRef.current) {
|
|
1644
|
+
viewerRef.current.currentScaleValue = String(savedScaleValue);
|
|
1645
|
+
}
|
|
1646
|
+
requestAnimationFrame(() => {
|
|
1647
|
+
const c = viewerRef.current?.container;
|
|
1648
|
+
if (!c) return;
|
|
1649
|
+
const range = c.scrollHeight - c.clientHeight;
|
|
1650
|
+
c.scrollTop = scrollFraction * Math.max(0, range);
|
|
1651
|
+
console.log("[PdfHighlighter] restored view after toggle", {
|
|
1652
|
+
scrollFraction,
|
|
1653
|
+
scrollTop: c.scrollTop,
|
|
1654
|
+
scaleValue: viewerRef.current?.currentScaleValue
|
|
1655
|
+
});
|
|
1656
|
+
});
|
|
1657
|
+
};
|
|
1658
|
+
eventBusRef.current.on("pagesinit", restoreView);
|
|
1659
|
+
}
|
|
1660
|
+
lastRenderKeyRef.current = renderKey;
|
|
1661
|
+
viewerRef.current.setDocument(pdfDocument);
|
|
1662
|
+
linkServiceRef.current.setDocument(pdfDocument);
|
|
1663
|
+
linkServiceRef.current.setViewer(viewerRef.current);
|
|
1664
|
+
setIsViewerReady(true);
|
|
1665
|
+
}, [pdfDocument, recolorKey]);
|
|
1228
1666
|
useLayoutEffect2(() => {
|
|
1229
1667
|
if (!containerNodeRef.current) return;
|
|
1230
1668
|
resizeObserverRef.current = new ResizeObserver(handleScaleValue);
|
|
1231
1669
|
resizeObserverRef.current.observe(containerNodeRef.current);
|
|
1232
1670
|
const doc = containerNodeRef.current.ownerDocument;
|
|
1233
|
-
eventBusRef.current.on("textlayerrendered",
|
|
1671
|
+
eventBusRef.current.on("textlayerrendered", scheduleRenderHighlightLayers);
|
|
1672
|
+
eventBusRef.current.on("pagerendered", scheduleRenderHighlightLayers);
|
|
1234
1673
|
eventBusRef.current.on("pagesinit", handleScaleValue);
|
|
1235
1674
|
doc.addEventListener("keydown", handleKeyDown);
|
|
1236
|
-
|
|
1675
|
+
doc.addEventListener("copy", handleCopy, true);
|
|
1676
|
+
scheduleRenderHighlightLayers();
|
|
1237
1677
|
return () => {
|
|
1238
1678
|
eventBusRef.current.off("pagesinit", handleScaleValue);
|
|
1239
|
-
eventBusRef.current.off("
|
|
1679
|
+
eventBusRef.current.off("pagerendered", scheduleRenderHighlightLayers);
|
|
1680
|
+
eventBusRef.current.off("textlayerrendered", scheduleRenderHighlightLayers);
|
|
1240
1681
|
doc.removeEventListener("keydown", handleKeyDown);
|
|
1682
|
+
doc.removeEventListener("copy", handleCopy, true);
|
|
1241
1683
|
resizeObserverRef.current?.disconnect();
|
|
1684
|
+
renderRetryTimeoutsRef.current.forEach(clearTimeout);
|
|
1685
|
+
renderRetryTimeoutsRef.current = [];
|
|
1686
|
+
if (resumeScrollAwayTimeoutRef.current) {
|
|
1687
|
+
clearTimeout(resumeScrollAwayTimeoutRef.current);
|
|
1688
|
+
resumeScrollAwayTimeoutRef.current = null;
|
|
1689
|
+
}
|
|
1242
1690
|
};
|
|
1243
1691
|
}, [selectionTip, highlights, onSelectionFinished]);
|
|
1692
|
+
useEffect4(() => {
|
|
1693
|
+
return () => {
|
|
1694
|
+
for (const binding of Object.values(highlightBindingsRef.current))
|
|
1695
|
+
unmountReactRoot(binding?.reactRoot);
|
|
1696
|
+
for (const binding of Object.values(noteBindingsRef.current))
|
|
1697
|
+
unmountReactRoot(binding?.reactRoot);
|
|
1698
|
+
};
|
|
1699
|
+
}, []);
|
|
1700
|
+
const onPageChangeRef = useRef5(onPageChange);
|
|
1701
|
+
onPageChangeRef.current = onPageChange;
|
|
1702
|
+
const initialPageRef = useRef5(initialPage);
|
|
1703
|
+
initialPageRef.current = initialPage;
|
|
1704
|
+
const initialPageAppliedRef = useRef5(false);
|
|
1705
|
+
const pendingInitialPageRef = useRef5(null);
|
|
1706
|
+
useEffect4(() => {
|
|
1707
|
+
const eventBus = eventBusRef.current;
|
|
1708
|
+
const handlePageChanging = (evt) => {
|
|
1709
|
+
if (pendingInitialPageRef.current != null) return;
|
|
1710
|
+
onPageChangeRef.current?.(evt.pageNumber);
|
|
1711
|
+
};
|
|
1712
|
+
eventBus.on("pagechanging", handlePageChanging);
|
|
1713
|
+
const handlePagesInit = () => {
|
|
1714
|
+
if (initialPageAppliedRef.current) return;
|
|
1715
|
+
initialPageAppliedRef.current = true;
|
|
1716
|
+
const page = initialPageRef.current;
|
|
1717
|
+
console.log("[PdfHighlighter] pagesinit, initialPage =", page);
|
|
1718
|
+
if (!page || page <= 1) return;
|
|
1719
|
+
pendingInitialPageRef.current = page;
|
|
1720
|
+
let tries = 0;
|
|
1721
|
+
let matches = 0;
|
|
1722
|
+
const tick = () => {
|
|
1723
|
+
const viewer = viewerRef.current;
|
|
1724
|
+
if (!viewer || pendingInitialPageRef.current == null) return;
|
|
1725
|
+
tries++;
|
|
1726
|
+
if (viewer.currentPageNumber === page) {
|
|
1727
|
+
matches++;
|
|
1728
|
+
} else {
|
|
1729
|
+
matches = 0;
|
|
1730
|
+
try {
|
|
1731
|
+
viewer.scrollPageIntoView({ pageNumber: page });
|
|
1732
|
+
} catch (e) {
|
|
1733
|
+
console.log("[PdfHighlighter] scrollPageIntoView threw", e);
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
if (matches >= 3) {
|
|
1737
|
+
pendingInitialPageRef.current = null;
|
|
1738
|
+
onPageChangeRef.current?.(page);
|
|
1739
|
+
console.log("[PdfHighlighter] initialPage settled at", page);
|
|
1740
|
+
return;
|
|
1741
|
+
}
|
|
1742
|
+
if (tries > 40) {
|
|
1743
|
+
pendingInitialPageRef.current = null;
|
|
1744
|
+
console.log("[PdfHighlighter] initialPage gave up at", page);
|
|
1745
|
+
return;
|
|
1746
|
+
}
|
|
1747
|
+
setTimeout(tick, 50);
|
|
1748
|
+
};
|
|
1749
|
+
setTimeout(tick, 0);
|
|
1750
|
+
};
|
|
1751
|
+
eventBus.on("pagesinit", handlePagesInit);
|
|
1752
|
+
return () => {
|
|
1753
|
+
eventBus.off("pagechanging", handlePageChanging);
|
|
1754
|
+
eventBus.off("pagesinit", handlePagesInit);
|
|
1755
|
+
pendingInitialPageRef.current = null;
|
|
1756
|
+
};
|
|
1757
|
+
}, []);
|
|
1758
|
+
const onZoomChangeRef = useRef5(onZoomChange);
|
|
1759
|
+
onZoomChangeRef.current = onZoomChange;
|
|
1760
|
+
useEffect4(() => {
|
|
1761
|
+
const container = containerNodeRef.current;
|
|
1762
|
+
if (!container || !isViewerReady) return;
|
|
1763
|
+
const MIN_SCALE = 0.25;
|
|
1764
|
+
const MAX_SCALE = 10;
|
|
1765
|
+
const COMMIT_DELAY = 140;
|
|
1766
|
+
const g = {
|
|
1767
|
+
active: false,
|
|
1768
|
+
startScale: 1,
|
|
1769
|
+
startScrollLeft: 0,
|
|
1770
|
+
startScrollTop: 0,
|
|
1771
|
+
anchorX: 0,
|
|
1772
|
+
anchorY: 0,
|
|
1773
|
+
originX: 0,
|
|
1774
|
+
originY: 0,
|
|
1775
|
+
k: 1,
|
|
1776
|
+
raf: 0,
|
|
1777
|
+
commitTimer: 0
|
|
1778
|
+
};
|
|
1779
|
+
const pdfViewerEl = () => container.querySelector(".pdfViewer");
|
|
1780
|
+
const clampK = (k) => Math.min(
|
|
1781
|
+
MAX_SCALE / g.startScale,
|
|
1782
|
+
Math.max(MIN_SCALE / g.startScale, k)
|
|
1783
|
+
);
|
|
1784
|
+
const begin = (anchorX, anchorY) => {
|
|
1785
|
+
const viewer = viewerRef.current;
|
|
1786
|
+
const el = pdfViewerEl();
|
|
1787
|
+
if (!viewer || !el) return false;
|
|
1788
|
+
g.active = true;
|
|
1789
|
+
g.startScale = viewer.currentScale;
|
|
1790
|
+
g.startScrollLeft = container.scrollLeft;
|
|
1791
|
+
g.startScrollTop = container.scrollTop;
|
|
1792
|
+
g.anchorX = anchorX;
|
|
1793
|
+
g.anchorY = anchorY;
|
|
1794
|
+
g.originX = container.scrollLeft + anchorX;
|
|
1795
|
+
g.originY = container.scrollTop + anchorY;
|
|
1796
|
+
g.k = 1;
|
|
1797
|
+
el.style.transformOrigin = `${g.originX}px ${g.originY}px`;
|
|
1798
|
+
el.style.willChange = "transform";
|
|
1799
|
+
return true;
|
|
1800
|
+
};
|
|
1801
|
+
const previewRaf = () => {
|
|
1802
|
+
g.raf = 0;
|
|
1803
|
+
const el = pdfViewerEl();
|
|
1804
|
+
if (el) el.style.transform = `scale(${g.k})`;
|
|
1805
|
+
};
|
|
1806
|
+
const preview = () => {
|
|
1807
|
+
if (!g.raf) g.raf = requestAnimationFrame(previewRaf);
|
|
1808
|
+
};
|
|
1809
|
+
const commit = () => {
|
|
1810
|
+
if (!g.active) return;
|
|
1811
|
+
g.active = false;
|
|
1812
|
+
if (g.raf) {
|
|
1813
|
+
cancelAnimationFrame(g.raf);
|
|
1814
|
+
g.raf = 0;
|
|
1815
|
+
}
|
|
1816
|
+
const viewer = viewerRef.current;
|
|
1817
|
+
const el = pdfViewerEl();
|
|
1818
|
+
if (el) {
|
|
1819
|
+
el.style.transform = "";
|
|
1820
|
+
el.style.transformOrigin = "";
|
|
1821
|
+
el.style.willChange = "";
|
|
1822
|
+
}
|
|
1823
|
+
if (!viewer) return;
|
|
1824
|
+
const finalScale = Math.min(
|
|
1825
|
+
MAX_SCALE,
|
|
1826
|
+
Math.max(MIN_SCALE, g.startScale * g.k)
|
|
1827
|
+
);
|
|
1828
|
+
const ratio = finalScale / g.startScale;
|
|
1829
|
+
viewer.currentScaleValue = String(finalScale);
|
|
1830
|
+
container.scrollLeft = g.originX * ratio - g.anchorX;
|
|
1831
|
+
container.scrollTop = g.originY * ratio - g.anchorY;
|
|
1832
|
+
onZoomChangeRef.current?.(finalScale);
|
|
1833
|
+
console.log("[PdfHighlighter] pinch commit", {
|
|
1834
|
+
from: g.startScale,
|
|
1835
|
+
to: finalScale
|
|
1836
|
+
});
|
|
1837
|
+
};
|
|
1838
|
+
const scheduleCommit = () => {
|
|
1839
|
+
clearTimeout(g.commitTimer);
|
|
1840
|
+
g.commitTimer = setTimeout(commit, COMMIT_DELAY);
|
|
1841
|
+
};
|
|
1842
|
+
const handleWheel = (e) => {
|
|
1843
|
+
if (!e.ctrlKey && !e.metaKey) return;
|
|
1844
|
+
e.preventDefault();
|
|
1845
|
+
const rect = container.getBoundingClientRect();
|
|
1846
|
+
const ax = e.clientX - rect.left;
|
|
1847
|
+
const ay = e.clientY - rect.top;
|
|
1848
|
+
if (!g.active && !begin(ax, ay)) return;
|
|
1849
|
+
g.k = clampK(g.k * Math.exp(-e.deltaY * 0.01));
|
|
1850
|
+
preview();
|
|
1851
|
+
scheduleCommit();
|
|
1852
|
+
};
|
|
1853
|
+
container.addEventListener("wheel", handleWheel, { passive: false });
|
|
1854
|
+
const pointers = /* @__PURE__ */ new Map();
|
|
1855
|
+
let startDist = 0;
|
|
1856
|
+
const spread = () => {
|
|
1857
|
+
const [a, b] = [...pointers.values()];
|
|
1858
|
+
return Math.hypot(a.clientX - b.clientX, a.clientY - b.clientY);
|
|
1859
|
+
};
|
|
1860
|
+
const onPointerDown = (e) => {
|
|
1861
|
+
if (e.pointerType === "touch") pointers.set(e.pointerId, e);
|
|
1862
|
+
};
|
|
1863
|
+
const onPointerMove = (e) => {
|
|
1864
|
+
if (!pointers.has(e.pointerId)) return;
|
|
1865
|
+
pointers.set(e.pointerId, e);
|
|
1866
|
+
if (pointers.size !== 2) return;
|
|
1867
|
+
e.preventDefault();
|
|
1868
|
+
const d = spread();
|
|
1869
|
+
const [a, b] = [...pointers.values()];
|
|
1870
|
+
const rect = container.getBoundingClientRect();
|
|
1871
|
+
const cx = (a.clientX + b.clientX) / 2 - rect.left;
|
|
1872
|
+
const cy = (a.clientY + b.clientY) / 2 - rect.top;
|
|
1873
|
+
if (!g.active) {
|
|
1874
|
+
startDist = d;
|
|
1875
|
+
if (!begin(cx, cy)) return;
|
|
1876
|
+
return;
|
|
1877
|
+
}
|
|
1878
|
+
g.k = clampK(d / startDist);
|
|
1879
|
+
preview();
|
|
1880
|
+
};
|
|
1881
|
+
const onPointerUp = (e) => {
|
|
1882
|
+
pointers.delete(e.pointerId);
|
|
1883
|
+
if (pointers.size < 2 && g.active) {
|
|
1884
|
+
startDist = 0;
|
|
1885
|
+
commit();
|
|
1886
|
+
}
|
|
1887
|
+
};
|
|
1888
|
+
container.addEventListener("pointerdown", onPointerDown);
|
|
1889
|
+
container.addEventListener("pointermove", onPointerMove, { passive: false });
|
|
1890
|
+
container.addEventListener("pointerup", onPointerUp);
|
|
1891
|
+
container.addEventListener("pointercancel", onPointerUp);
|
|
1892
|
+
return () => {
|
|
1893
|
+
container.removeEventListener("wheel", handleWheel);
|
|
1894
|
+
container.removeEventListener("pointerdown", onPointerDown);
|
|
1895
|
+
container.removeEventListener("pointermove", onPointerMove);
|
|
1896
|
+
container.removeEventListener("pointerup", onPointerUp);
|
|
1897
|
+
container.removeEventListener("pointercancel", onPointerUp);
|
|
1898
|
+
clearTimeout(g.commitTimer);
|
|
1899
|
+
if (g.raf) cancelAnimationFrame(g.raf);
|
|
1900
|
+
const el = pdfViewerEl();
|
|
1901
|
+
if (el) {
|
|
1902
|
+
el.style.transform = "";
|
|
1903
|
+
el.style.transformOrigin = "";
|
|
1904
|
+
el.style.willChange = "";
|
|
1905
|
+
}
|
|
1906
|
+
};
|
|
1907
|
+
}, [isViewerReady]);
|
|
1244
1908
|
const handleScroll = () => {
|
|
1245
1909
|
onScrollAway && onScrollAway();
|
|
1246
1910
|
scrolledToHighlightIdRef.current = null;
|
|
@@ -1358,26 +2022,46 @@ var PdfHighlighter = ({
|
|
|
1358
2022
|
setTip(null);
|
|
1359
2023
|
}
|
|
1360
2024
|
};
|
|
2025
|
+
const handleCopy = (event) => {
|
|
2026
|
+
const container = containerNodeRef.current;
|
|
2027
|
+
if (!container || !event.clipboardData) return;
|
|
2028
|
+
const target = event.target;
|
|
2029
|
+
const targetElement = target instanceof HTMLElement ? target : target instanceof Node ? target.parentElement : null;
|
|
2030
|
+
if (targetElement && (targetElement.closest("input, textarea, [contenteditable='true']") || targetElement.closest(".PdfHighlighter__tip-container"))) {
|
|
2031
|
+
return;
|
|
2032
|
+
}
|
|
2033
|
+
const selection = getWindow(container).getSelection();
|
|
2034
|
+
const range = selection?.rangeCount ? selection.getRangeAt(0) : null;
|
|
2035
|
+
if (!selection || selection.isCollapsed || !range || !container.contains(range.commonAncestorContainer)) {
|
|
2036
|
+
return;
|
|
2037
|
+
}
|
|
2038
|
+
const text = selectionRef.current?.content.text?.trim() || selection.toString().split("\n").join(" ").trim();
|
|
2039
|
+
if (!text) return;
|
|
2040
|
+
event.clipboardData.setData("text/plain", text);
|
|
2041
|
+
event.preventDefault();
|
|
2042
|
+
event.stopPropagation();
|
|
2043
|
+
};
|
|
1361
2044
|
const handleScaleValue = () => {
|
|
1362
2045
|
if (viewerRef.current) {
|
|
1363
2046
|
viewerRef.current.currentScaleValue = pdfScaleValue.toString();
|
|
1364
2047
|
}
|
|
1365
2048
|
};
|
|
1366
|
-
const renderHighlightLayer = (highlightBindings, pageNumber) => {
|
|
2049
|
+
const renderHighlightLayer = (highlightBindings, pageNumber, shouldRenderHighlight) => {
|
|
1367
2050
|
if (!viewerRef.current) return;
|
|
1368
2051
|
highlightBindings.reactRoot.render(
|
|
1369
2052
|
/* @__PURE__ */ React6.createElement(PdfHighlighterContext.Provider, { value: pdfHighlighterUtils }, /* @__PURE__ */ React6.createElement(
|
|
1370
2053
|
HighlightLayer,
|
|
1371
2054
|
{
|
|
1372
2055
|
highlightsByPage: group_highlights_by_page_default([
|
|
1373
|
-
...
|
|
2056
|
+
...highlightsRef.current,
|
|
1374
2057
|
ghostHighlightRef.current
|
|
1375
2058
|
]),
|
|
1376
2059
|
pageNumber,
|
|
1377
2060
|
scrolledToHighlightId: scrolledToHighlightIdRef.current,
|
|
1378
2061
|
viewer: viewerRef.current,
|
|
1379
2062
|
highlightBindings,
|
|
1380
|
-
|
|
2063
|
+
shouldRenderHighlight,
|
|
2064
|
+
children: childrenRef.current
|
|
1381
2065
|
}
|
|
1382
2066
|
))
|
|
1383
2067
|
);
|
|
@@ -1385,31 +2069,52 @@ var PdfHighlighter = ({
|
|
|
1385
2069
|
const renderHighlightLayers = () => {
|
|
1386
2070
|
if (!viewerRef.current) return;
|
|
1387
2071
|
for (let pageNumber = 1; pageNumber <= pdfDocument.numPages; pageNumber++) {
|
|
1388
|
-
const
|
|
1389
|
-
if (
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
2072
|
+
const { textLayer } = viewerRef.current.getPageView(pageNumber - 1) || {};
|
|
2073
|
+
if (!textLayer) continue;
|
|
2074
|
+
const textLayerDiv = textLayer.div;
|
|
2075
|
+
const highlightBindings = ensurePersistentLayer(
|
|
2076
|
+
highlightBindingsRef.current,
|
|
2077
|
+
pageNumber,
|
|
2078
|
+
textLayerDiv,
|
|
2079
|
+
"PdfHighlighter__highlight-layer"
|
|
2080
|
+
);
|
|
2081
|
+
renderHighlightLayer(
|
|
2082
|
+
highlightBindings,
|
|
2083
|
+
pageNumber,
|
|
2084
|
+
(highlight) => !isFreetextHighlight(highlight)
|
|
2085
|
+
);
|
|
2086
|
+
const pageEl = textLayerDiv.closest(".page");
|
|
2087
|
+
if (pageEl) {
|
|
2088
|
+
const noteBindings = ensurePersistentLayer(
|
|
2089
|
+
noteBindingsRef.current,
|
|
2090
|
+
pageNumber,
|
|
2091
|
+
pageEl,
|
|
2092
|
+
"PdfHighlighter__note-layer"
|
|
1396
2093
|
);
|
|
1397
|
-
|
|
1398
|
-
const reactRoot = createRoot(highlightLayer);
|
|
1399
|
-
highlightBindingsRef.current[pageNumber] = {
|
|
1400
|
-
reactRoot,
|
|
1401
|
-
container: highlightLayer,
|
|
1402
|
-
textLayer: textLayer.div
|
|
1403
|
-
// textLayer.div for version >=3.0 and textLayer.textLayerDiv otherwise.
|
|
1404
|
-
};
|
|
1405
|
-
renderHighlightLayer(
|
|
1406
|
-
highlightBindingsRef.current[pageNumber],
|
|
1407
|
-
pageNumber
|
|
1408
|
-
);
|
|
1409
|
-
}
|
|
2094
|
+
renderHighlightLayer(noteBindings, pageNumber, isFreetextHighlight);
|
|
1410
2095
|
}
|
|
1411
2096
|
}
|
|
1412
2097
|
};
|
|
2098
|
+
const scheduleRenderHighlightLayers = () => {
|
|
2099
|
+
renderHighlightLayers();
|
|
2100
|
+
renderRetryTimeoutsRef.current.forEach(clearTimeout);
|
|
2101
|
+
renderRetryTimeoutsRef.current = [50, 150, 350, 750, 1200].map(
|
|
2102
|
+
(delay) => setTimeout(renderHighlightLayers, delay)
|
|
2103
|
+
);
|
|
2104
|
+
};
|
|
2105
|
+
const resumeScrollAwayListenerAfterNavigation = () => {
|
|
2106
|
+
const container = viewerRef.current?.container;
|
|
2107
|
+
if (!container) return;
|
|
2108
|
+
if (resumeScrollAwayTimeoutRef.current) {
|
|
2109
|
+
clearTimeout(resumeScrollAwayTimeoutRef.current);
|
|
2110
|
+
}
|
|
2111
|
+
resumeScrollAwayTimeoutRef.current = setTimeout(() => {
|
|
2112
|
+
container.addEventListener("scroll", handleScroll, {
|
|
2113
|
+
once: true
|
|
2114
|
+
});
|
|
2115
|
+
resumeScrollAwayTimeoutRef.current = null;
|
|
2116
|
+
}, 1200);
|
|
2117
|
+
};
|
|
1413
2118
|
const isEditingOrHighlighting = () => {
|
|
1414
2119
|
return Boolean(selectionRef.current) || Boolean(ghostHighlightRef.current) || isAreaSelectionInProgressRef.current || isEditInProgressRef.current;
|
|
1415
2120
|
};
|
|
@@ -1441,32 +2146,72 @@ var PdfHighlighter = ({
|
|
|
1441
2146
|
const scrollToHighlight = (highlight) => {
|
|
1442
2147
|
const { boundingRect, usePdfCoordinates } = highlight.position;
|
|
1443
2148
|
const pageNumber = boundingRect.pageNumber;
|
|
1444
|
-
viewerRef.current
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
)
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
});
|
|
1463
|
-
scrolledToHighlightIdRef.current = highlight.id;
|
|
1464
|
-
renderHighlightLayers();
|
|
1465
|
-
setTimeout(() => {
|
|
1466
|
-
viewerRef.current.container.addEventListener("scroll", handleScroll, {
|
|
1467
|
-
once: true
|
|
2149
|
+
const viewer = viewerRef.current;
|
|
2150
|
+
if (!viewer) return;
|
|
2151
|
+
viewer.container.removeEventListener("scroll", handleScroll);
|
|
2152
|
+
const pageView = viewer.getPageView(pageNumber - 1);
|
|
2153
|
+
const container = viewer.container;
|
|
2154
|
+
if (pageView?.div && pageView.viewport) {
|
|
2155
|
+
const topInPage = scaledToViewport(
|
|
2156
|
+
boundingRect,
|
|
2157
|
+
pageView.viewport,
|
|
2158
|
+
usePdfCoordinates
|
|
2159
|
+
).top;
|
|
2160
|
+
const pageRect = pageView.div.getBoundingClientRect();
|
|
2161
|
+
const containerRect = container.getBoundingClientRect();
|
|
2162
|
+
const target = container.scrollTop + (pageRect.top - containerRect.top) + topInPage - SCROLL_MARGIN;
|
|
2163
|
+
const prefersReducedMotion = typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
|
2164
|
+
container.scrollTo({
|
|
2165
|
+
top: Math.max(0, target),
|
|
2166
|
+
behavior: prefersReducedMotion ? "auto" : "smooth"
|
|
1468
2167
|
});
|
|
1469
|
-
}
|
|
2168
|
+
} else {
|
|
2169
|
+
viewer.scrollPageIntoView({ pageNumber });
|
|
2170
|
+
}
|
|
2171
|
+
scrolledToHighlightIdRef.current = highlight.id;
|
|
2172
|
+
scheduleRenderHighlightLayers();
|
|
2173
|
+
resumeScrollAwayListenerAfterNavigation();
|
|
2174
|
+
};
|
|
2175
|
+
const dispatchFind = (query, findPrevious2, options = {}, type) => {
|
|
2176
|
+
eventBusRef.current.dispatch("find", {
|
|
2177
|
+
source: findControllerRef.current || viewerRef.current,
|
|
2178
|
+
type,
|
|
2179
|
+
query,
|
|
2180
|
+
phraseSearch: true,
|
|
2181
|
+
caseSensitive: options.caseSensitive ?? false,
|
|
2182
|
+
entireWord: options.entireWord ?? false,
|
|
2183
|
+
highlightAll: options.highlightAll ?? true,
|
|
2184
|
+
findPrevious: findPrevious2,
|
|
2185
|
+
matchDiacritics: options.matchDiacritics ?? false
|
|
2186
|
+
});
|
|
2187
|
+
};
|
|
2188
|
+
const currentSearchRef = useRef5({
|
|
2189
|
+
query: "",
|
|
2190
|
+
options: {}
|
|
2191
|
+
});
|
|
2192
|
+
const search = (query, options = {}) => {
|
|
2193
|
+
currentSearchRef.current = { query, options };
|
|
2194
|
+
if (!query.trim()) {
|
|
2195
|
+
clearSearch();
|
|
2196
|
+
return;
|
|
2197
|
+
}
|
|
2198
|
+
dispatchFind(query, false, options);
|
|
2199
|
+
};
|
|
2200
|
+
const findNext = () => {
|
|
2201
|
+
const { query, options } = currentSearchRef.current;
|
|
2202
|
+
if (!query.trim()) return;
|
|
2203
|
+
dispatchFind(query, false, options, "again");
|
|
2204
|
+
};
|
|
2205
|
+
const findPrevious = () => {
|
|
2206
|
+
const { query, options } = currentSearchRef.current;
|
|
2207
|
+
if (!query.trim()) return;
|
|
2208
|
+
dispatchFind(query, true, options, "again");
|
|
2209
|
+
};
|
|
2210
|
+
const clearSearch = () => {
|
|
2211
|
+
currentSearchRef.current = { query: "", options: {} };
|
|
2212
|
+
eventBusRef.current.dispatch("findbarclose", {
|
|
2213
|
+
source: findControllerRef.current || viewerRef.current
|
|
2214
|
+
});
|
|
1470
2215
|
};
|
|
1471
2216
|
const pdfHighlighterUtils = {
|
|
1472
2217
|
isEditingOrHighlighting,
|
|
@@ -1483,6 +2228,10 @@ var PdfHighlighter = ({
|
|
|
1483
2228
|
updateTipPosition: updateTipPositionRef.current,
|
|
1484
2229
|
getLinkService: () => linkServiceRef.current,
|
|
1485
2230
|
getEventBus: () => eventBusRef.current,
|
|
2231
|
+
search,
|
|
2232
|
+
findNext,
|
|
2233
|
+
findPrevious,
|
|
2234
|
+
clearSearch,
|
|
1486
2235
|
goToPage: (pageNumber) => {
|
|
1487
2236
|
console.log("[PdfHighlighter] goToPage called with page:", pageNumber);
|
|
1488
2237
|
const viewer = viewerRef.current;
|
|
@@ -1584,14 +2333,6 @@ var PdfHighlighter = ({
|
|
|
1584
2333
|
.PdfHighlighter::-webkit-scrollbar-track-piece {
|
|
1585
2334
|
background-color: ${resolvedTheme.scrollbarTrackColor};
|
|
1586
2335
|
}
|
|
1587
|
-
${resolvedTheme.mode === "dark" ? `
|
|
1588
|
-
.PdfHighlighter--dark .page {
|
|
1589
|
-
filter: invert(${resolvedTheme.darkModeInvertIntensity}) hue-rotate(180deg) brightness(1.05);
|
|
1590
|
-
}
|
|
1591
|
-
.PdfHighlighter--dark .PdfHighlighter__highlight-layer {
|
|
1592
|
-
filter: invert(${resolvedTheme.darkModeInvertIntensity}) hue-rotate(180deg) brightness(0.95);
|
|
1593
|
-
}
|
|
1594
|
-
` : ""}
|
|
1595
2336
|
`),
|
|
1596
2337
|
isViewerReady && /* @__PURE__ */ React6.createElement(
|
|
1597
2338
|
TipContainer,
|
|
@@ -1676,10 +2417,78 @@ var PdfHighlighter = ({
|
|
|
1676
2417
|
import React7, {
|
|
1677
2418
|
useState as useState6,
|
|
1678
2419
|
useRef as useRef6,
|
|
1679
|
-
useEffect as useEffect5
|
|
2420
|
+
useEffect as useEffect5,
|
|
2421
|
+
useLayoutEffect as useLayoutEffect3
|
|
1680
2422
|
} from "react";
|
|
2423
|
+
import { createPortal } from "react-dom";
|
|
2424
|
+
|
|
2425
|
+
// src/lib/copy-highlight-content.ts
|
|
2426
|
+
var intersects = (a, b) => a.left < b.left + b.width && a.left + a.width > b.left && a.top < b.top + b.height && a.top + a.height > b.top;
|
|
2427
|
+
var copyTextToClipboard = async (text) => {
|
|
2428
|
+
if (!text) return;
|
|
2429
|
+
if (navigator.clipboard?.writeText) {
|
|
2430
|
+
await navigator.clipboard.writeText(text);
|
|
2431
|
+
return;
|
|
2432
|
+
}
|
|
2433
|
+
const textarea = document.createElement("textarea");
|
|
2434
|
+
textarea.value = text;
|
|
2435
|
+
textarea.style.position = "fixed";
|
|
2436
|
+
textarea.style.opacity = "0";
|
|
2437
|
+
document.body.appendChild(textarea);
|
|
2438
|
+
textarea.select();
|
|
2439
|
+
document.execCommand("copy");
|
|
2440
|
+
textarea.remove();
|
|
2441
|
+
};
|
|
2442
|
+
var extractTextFromHighlightRect = (anchor, rect) => {
|
|
2443
|
+
const page = anchor.closest(".page");
|
|
2444
|
+
const textLayer = page?.querySelector(".textLayer");
|
|
2445
|
+
if (!page || !textLayer) return "";
|
|
2446
|
+
const pageRect = page.getBoundingClientRect();
|
|
2447
|
+
const targetRect = {
|
|
2448
|
+
left: pageRect.left + rect.left,
|
|
2449
|
+
top: pageRect.top + rect.top,
|
|
2450
|
+
width: rect.width,
|
|
2451
|
+
height: rect.height
|
|
2452
|
+
};
|
|
2453
|
+
const matches = Array.from(textLayer.querySelectorAll("span")).map((span) => {
|
|
2454
|
+
const spanRect = span.getBoundingClientRect();
|
|
2455
|
+
return {
|
|
2456
|
+
text: span.textContent || "",
|
|
2457
|
+
rect: {
|
|
2458
|
+
left: spanRect.left,
|
|
2459
|
+
top: spanRect.top,
|
|
2460
|
+
width: spanRect.width,
|
|
2461
|
+
height: spanRect.height
|
|
2462
|
+
}
|
|
2463
|
+
};
|
|
2464
|
+
}).filter(({ text, rect: rect2 }) => text.trim() && intersects(rect2, targetRect)).sort((a, b) => {
|
|
2465
|
+
const lineDelta = a.rect.top - b.rect.top;
|
|
2466
|
+
return Math.abs(lineDelta) > 4 ? lineDelta : a.rect.left - b.rect.left;
|
|
2467
|
+
});
|
|
2468
|
+
return matches.map(({ text }) => text).join(" ").replace(/\s+/g, " ").trim();
|
|
2469
|
+
};
|
|
2470
|
+
|
|
2471
|
+
// src/lib/highlight-config-layer.ts
|
|
2472
|
+
var findOrCreateHighlightConfigLayer = (anchor) => {
|
|
2473
|
+
const pageLayer = anchor.closest(".page");
|
|
2474
|
+
if (!pageLayer) return null;
|
|
2475
|
+
const doc = getDocument(pageLayer);
|
|
2476
|
+
let layer = Array.from(pageLayer.children).find(
|
|
2477
|
+
(child) => child.classList.contains("PdfHighlighter__config-layer")
|
|
2478
|
+
);
|
|
2479
|
+
if (!layer) {
|
|
2480
|
+
layer = doc.createElement("div");
|
|
2481
|
+
layer.className = "PdfHighlighter__config-layer";
|
|
2482
|
+
pageLayer.appendChild(layer);
|
|
2483
|
+
}
|
|
2484
|
+
return layer;
|
|
2485
|
+
};
|
|
2486
|
+
|
|
2487
|
+
// src/components/TextHighlight.tsx
|
|
1681
2488
|
var DefaultStyleIcon = () => /* @__PURE__ */ React7.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React7.createElement("path", { d: "M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9c.83 0 1.5-.67 1.5-1.5 0-.39-.15-.74-.39-1.01-.23-.26-.38-.61-.38-.99 0-.83.67-1.5 1.5-1.5H16c2.76 0 5-2.24 5-5 0-4.42-4.03-8-9-8zm-5.5 9c-.83 0-1.5-.67-1.5-1.5S5.67 9 6.5 9 8 9.67 8 10.5 7.33 12 6.5 12zm3-4C8.67 8 8 7.33 8 6.5S8.67 5 9.5 5s1.5.67 1.5 1.5S10.33 8 9.5 8zm5 0c-.83 0-1.5-.67-1.5-1.5S13.67 5 14.5 5s1.5.67 1.5 1.5S15.33 8 14.5 8zm3 4c-.83 0-1.5-.67-1.5-1.5S16.67 9 17.5 9s1.5.67 1.5 1.5-.67 1.5-1.5 1.5z" }));
|
|
1682
2489
|
var DefaultDeleteIcon = () => /* @__PURE__ */ React7.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React7.createElement("path", { d: "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z" }));
|
|
2490
|
+
var DefaultCopyIcon = () => /* @__PURE__ */ React7.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React7.createElement("path", { d: "M16 1H4c-1.1 0-2 .9-2 2v12h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z" }));
|
|
2491
|
+
var DefaultCopiedIcon = () => /* @__PURE__ */ React7.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React7.createElement("path", { d: "M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z" }));
|
|
1683
2492
|
var HighlightIcon = () => /* @__PURE__ */ React7.createElement("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React7.createElement("path", { d: "M6 14l3 3v5h6v-5l3-3V9H6v5zm5-12h2v3h-2V2zM3.5 5.875L4.914 4.46l2.12 2.122L5.622 8 3.5 5.875zm13.46.71l2.123-2.12 1.414 1.414L18.375 8l-1.414-1.414z" }));
|
|
1684
2493
|
var UnderlineIcon = () => /* @__PURE__ */ React7.createElement("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React7.createElement("path", { d: "M12 17c3.31 0 6-2.69 6-6V3h-2.5v8c0 1.93-1.57 3.5-3.5 3.5S8.5 12.93 8.5 11V3H6v8c0 3.31 2.69 6 6 6zm-7 2v2h14v-2H5z" }));
|
|
1685
2494
|
var StrikethroughIcon = () => /* @__PURE__ */ React7.createElement("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React7.createElement("path", { d: "M10 19h4v-3h-4v3zM5 4v3h5v3h4V7h5V4H5zM3 14h18v-2H3v2z" }));
|
|
@@ -1709,12 +2518,28 @@ var TextHighlight = ({
|
|
|
1709
2518
|
onDelete,
|
|
1710
2519
|
styleIcon,
|
|
1711
2520
|
deleteIcon,
|
|
2521
|
+
copyText,
|
|
1712
2522
|
colorPresets = DEFAULT_COLOR_PRESETS
|
|
1713
2523
|
}) => {
|
|
1714
2524
|
const [isStylePanelOpen, setIsStylePanelOpen] = useState6(false);
|
|
1715
2525
|
const [isHovered, setIsHovered] = useState6(false);
|
|
2526
|
+
const [isCopied, setIsCopied] = useState6(false);
|
|
2527
|
+
const [configLayer, setConfigLayer] = useState6(null);
|
|
1716
2528
|
const stylePanelRef = useRef6(null);
|
|
1717
2529
|
const containerRef = useRef6(null);
|
|
2530
|
+
const copyResetTimeoutRef = useRef6(null);
|
|
2531
|
+
useLayoutEffect3(() => {
|
|
2532
|
+
if (containerRef.current) {
|
|
2533
|
+
setConfigLayer(findOrCreateHighlightConfigLayer(containerRef.current));
|
|
2534
|
+
}
|
|
2535
|
+
}, []);
|
|
2536
|
+
useEffect5(() => {
|
|
2537
|
+
return () => {
|
|
2538
|
+
if (copyResetTimeoutRef.current) {
|
|
2539
|
+
window.clearTimeout(copyResetTimeoutRef.current);
|
|
2540
|
+
}
|
|
2541
|
+
};
|
|
2542
|
+
}, []);
|
|
1718
2543
|
useEffect5(() => {
|
|
1719
2544
|
if (!isStylePanelOpen) return;
|
|
1720
2545
|
const handleClickOutside = (e) => {
|
|
@@ -1746,13 +2571,26 @@ var TextHighlight = ({
|
|
|
1746
2571
|
const getPartStyle = (rect) => {
|
|
1747
2572
|
const baseStyle = { ...rect, ...style };
|
|
1748
2573
|
if (highlightStyle === "highlight") {
|
|
1749
|
-
baseStyle.backgroundColor = highlightColor
|
|
2574
|
+
baseStyle.backgroundColor = `color-mix(in srgb, ${highlightColor} var(--hl-fill-alpha, 100%), transparent)`;
|
|
1750
2575
|
} else {
|
|
1751
2576
|
baseStyle.backgroundColor = "transparent";
|
|
1752
2577
|
baseStyle.color = highlightColor;
|
|
1753
2578
|
}
|
|
1754
2579
|
return baseStyle;
|
|
1755
2580
|
};
|
|
2581
|
+
const handleCopy = async (event) => {
|
|
2582
|
+
event.stopPropagation();
|
|
2583
|
+
const text = copyText || highlight.content?.text || (containerRef.current && firstRect ? extractTextFromHighlightRect(containerRef.current, firstRect) : "");
|
|
2584
|
+
await copyTextToClipboard(text);
|
|
2585
|
+
setIsCopied(true);
|
|
2586
|
+
if (copyResetTimeoutRef.current) {
|
|
2587
|
+
window.clearTimeout(copyResetTimeoutRef.current);
|
|
2588
|
+
}
|
|
2589
|
+
copyResetTimeoutRef.current = window.setTimeout(() => {
|
|
2590
|
+
setIsCopied(false);
|
|
2591
|
+
copyResetTimeoutRef.current = null;
|
|
2592
|
+
}, 1500);
|
|
2593
|
+
};
|
|
1756
2594
|
return /* @__PURE__ */ React7.createElement(
|
|
1757
2595
|
"div",
|
|
1758
2596
|
{
|
|
@@ -1760,107 +2598,120 @@ var TextHighlight = ({
|
|
|
1760
2598
|
onContextMenu,
|
|
1761
2599
|
ref: containerRef
|
|
1762
2600
|
},
|
|
1763
|
-
(onStyleChange || onDelete) && firstRect &&
|
|
1764
|
-
"div",
|
|
1765
|
-
{
|
|
1766
|
-
className: "TextHighlight__toolbar-wrapper",
|
|
1767
|
-
style: {
|
|
1768
|
-
position: "absolute",
|
|
1769
|
-
left: firstRect.left,
|
|
1770
|
-
top: firstRect.top - 28,
|
|
1771
|
-
paddingBottom: 12
|
|
1772
|
-
},
|
|
1773
|
-
onMouseEnter: () => setIsHovered(true),
|
|
1774
|
-
onMouseLeave: () => setIsHovered(false)
|
|
1775
|
-
},
|
|
2601
|
+
configLayer && (onStyleChange || onDelete) && firstRect && createPortal(
|
|
1776
2602
|
/* @__PURE__ */ React7.createElement(
|
|
1777
2603
|
"div",
|
|
1778
2604
|
{
|
|
1779
|
-
className:
|
|
2605
|
+
className: "TextHighlight__toolbar-wrapper",
|
|
2606
|
+
style: {
|
|
2607
|
+
position: "absolute",
|
|
2608
|
+
left: firstRect.left,
|
|
2609
|
+
top: firstRect.top - 28,
|
|
2610
|
+
paddingBottom: 12
|
|
2611
|
+
},
|
|
2612
|
+
onMouseEnter: () => setIsHovered(true),
|
|
2613
|
+
onMouseLeave: () => setIsHovered(false)
|
|
1780
2614
|
},
|
|
1781
|
-
|
|
1782
|
-
"
|
|
2615
|
+
/* @__PURE__ */ React7.createElement(
|
|
2616
|
+
"div",
|
|
1783
2617
|
{
|
|
1784
|
-
className: "
|
|
1785
|
-
onClick: (e) => {
|
|
1786
|
-
e.stopPropagation();
|
|
1787
|
-
setIsStylePanelOpen(!isStylePanelOpen);
|
|
1788
|
-
},
|
|
1789
|
-
title: "Change style",
|
|
1790
|
-
type: "button"
|
|
2618
|
+
className: `TextHighlight__toolbar ${isHovered || isScrolledTo || isStylePanelOpen ? "TextHighlight__toolbar--visible" : ""}`
|
|
1791
2619
|
},
|
|
1792
|
-
|
|
2620
|
+
onStyleChange && /* @__PURE__ */ React7.createElement(
|
|
2621
|
+
"button",
|
|
2622
|
+
{
|
|
2623
|
+
className: "TextHighlight__style-button",
|
|
2624
|
+
onClick: (e) => {
|
|
2625
|
+
e.stopPropagation();
|
|
2626
|
+
setIsStylePanelOpen(!isStylePanelOpen);
|
|
2627
|
+
},
|
|
2628
|
+
title: "Change style",
|
|
2629
|
+
type: "button"
|
|
2630
|
+
},
|
|
2631
|
+
styleIcon || /* @__PURE__ */ React7.createElement(DefaultStyleIcon, null)
|
|
2632
|
+
),
|
|
2633
|
+
/* @__PURE__ */ React7.createElement(
|
|
2634
|
+
"button",
|
|
2635
|
+
{
|
|
2636
|
+
className: "TextHighlight__copy-button",
|
|
2637
|
+
onClick: handleCopy,
|
|
2638
|
+
title: isCopied ? "Copied" : "Copy text",
|
|
2639
|
+
type: "button"
|
|
2640
|
+
},
|
|
2641
|
+
isCopied ? /* @__PURE__ */ React7.createElement(DefaultCopiedIcon, null) : /* @__PURE__ */ React7.createElement(DefaultCopyIcon, null)
|
|
2642
|
+
),
|
|
2643
|
+
onDelete && /* @__PURE__ */ React7.createElement(
|
|
2644
|
+
"button",
|
|
2645
|
+
{
|
|
2646
|
+
className: "TextHighlight__delete-button",
|
|
2647
|
+
onClick: (e) => {
|
|
2648
|
+
e.stopPropagation();
|
|
2649
|
+
onDelete();
|
|
2650
|
+
},
|
|
2651
|
+
title: "Delete",
|
|
2652
|
+
type: "button"
|
|
2653
|
+
},
|
|
2654
|
+
deleteIcon || /* @__PURE__ */ React7.createElement(DefaultDeleteIcon, null)
|
|
2655
|
+
)
|
|
1793
2656
|
),
|
|
1794
|
-
|
|
1795
|
-
"
|
|
2657
|
+
isStylePanelOpen && onStyleChange && /* @__PURE__ */ React7.createElement(
|
|
2658
|
+
"div",
|
|
1796
2659
|
{
|
|
1797
|
-
className: "
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
onDelete();
|
|
1801
|
-
},
|
|
1802
|
-
title: "Delete",
|
|
1803
|
-
type: "button"
|
|
2660
|
+
className: "TextHighlight__style-panel",
|
|
2661
|
+
ref: stylePanelRef,
|
|
2662
|
+
onClick: (e) => e.stopPropagation()
|
|
1804
2663
|
},
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
/* @__PURE__ */ React7.createElement(
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
))),
|
|
1843
|
-
/* @__PURE__ */ React7.createElement("div", { className: "TextHighlight__style-row" }, /* @__PURE__ */ React7.createElement("label", null, "Color"), /* @__PURE__ */ React7.createElement("div", { className: "TextHighlight__color-options" }, /* @__PURE__ */ React7.createElement("div", { className: "TextHighlight__color-presets" }, colorPresets.map((c) => /* @__PURE__ */ React7.createElement(
|
|
1844
|
-
"button",
|
|
1845
|
-
{
|
|
1846
|
-
key: c,
|
|
1847
|
-
type: "button",
|
|
1848
|
-
className: `TextHighlight__color-preset ${highlightColor === c ? "active" : ""}`,
|
|
1849
|
-
style: { backgroundColor: c },
|
|
1850
|
-
onClick: () => onStyleChange({ highlightColor: c }),
|
|
1851
|
-
title: c
|
|
1852
|
-
}
|
|
1853
|
-
))), /* @__PURE__ */ React7.createElement(
|
|
1854
|
-
"input",
|
|
1855
|
-
{
|
|
1856
|
-
type: "color",
|
|
1857
|
-
value: highlightColor,
|
|
1858
|
-
onChange: (e) => {
|
|
1859
|
-
onStyleChange({ highlightColor: e.target.value });
|
|
2664
|
+
/* @__PURE__ */ React7.createElement("div", { className: "TextHighlight__style-row" }, /* @__PURE__ */ React7.createElement("label", null, "Style"), /* @__PURE__ */ React7.createElement("div", { className: "TextHighlight__style-buttons" }, /* @__PURE__ */ React7.createElement(
|
|
2665
|
+
"button",
|
|
2666
|
+
{
|
|
2667
|
+
type: "button",
|
|
2668
|
+
className: `TextHighlight__style-type-button ${highlightStyle === "highlight" ? "active" : ""}`,
|
|
2669
|
+
onClick: () => onStyleChange({ highlightStyle: "highlight" }),
|
|
2670
|
+
title: "Highlight"
|
|
2671
|
+
},
|
|
2672
|
+
/* @__PURE__ */ React7.createElement(HighlightIcon, null)
|
|
2673
|
+
), /* @__PURE__ */ React7.createElement(
|
|
2674
|
+
"button",
|
|
2675
|
+
{
|
|
2676
|
+
type: "button",
|
|
2677
|
+
className: `TextHighlight__style-type-button ${highlightStyle === "underline" ? "active" : ""}`,
|
|
2678
|
+
onClick: () => onStyleChange({ highlightStyle: "underline" }),
|
|
2679
|
+
title: "Underline"
|
|
2680
|
+
},
|
|
2681
|
+
/* @__PURE__ */ React7.createElement(UnderlineIcon, null)
|
|
2682
|
+
), /* @__PURE__ */ React7.createElement(
|
|
2683
|
+
"button",
|
|
2684
|
+
{
|
|
2685
|
+
type: "button",
|
|
2686
|
+
className: `TextHighlight__style-type-button ${highlightStyle === "strikethrough" ? "active" : ""}`,
|
|
2687
|
+
onClick: () => onStyleChange({ highlightStyle: "strikethrough" }),
|
|
2688
|
+
title: "Strikethrough"
|
|
2689
|
+
},
|
|
2690
|
+
/* @__PURE__ */ React7.createElement(StrikethroughIcon, null)
|
|
2691
|
+
))),
|
|
2692
|
+
/* @__PURE__ */ React7.createElement("div", { className: "TextHighlight__style-row" }, /* @__PURE__ */ React7.createElement("label", null, "Color"), /* @__PURE__ */ React7.createElement("div", { className: "TextHighlight__color-options" }, /* @__PURE__ */ React7.createElement("div", { className: "TextHighlight__color-presets" }, colorPresets.map((c) => /* @__PURE__ */ React7.createElement(
|
|
2693
|
+
"button",
|
|
2694
|
+
{
|
|
2695
|
+
key: c,
|
|
2696
|
+
type: "button",
|
|
2697
|
+
className: `TextHighlight__color-preset ${highlightColor === c ? "active" : ""}`,
|
|
2698
|
+
style: { backgroundColor: c },
|
|
2699
|
+
onClick: () => onStyleChange({ highlightColor: c }),
|
|
2700
|
+
title: c
|
|
1860
2701
|
}
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
2702
|
+
))), /* @__PURE__ */ React7.createElement(
|
|
2703
|
+
"input",
|
|
2704
|
+
{
|
|
2705
|
+
type: "color",
|
|
2706
|
+
value: highlightColor,
|
|
2707
|
+
onChange: (e) => {
|
|
2708
|
+
onStyleChange({ highlightColor: e.target.value });
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
)))
|
|
2712
|
+
)
|
|
2713
|
+
),
|
|
2714
|
+
configLayer
|
|
1864
2715
|
),
|
|
1865
2716
|
/* @__PURE__ */ React7.createElement(
|
|
1866
2717
|
"div",
|
|
@@ -1966,11 +2817,15 @@ var MonitoredHighlightContainer = ({
|
|
|
1966
2817
|
import React10, {
|
|
1967
2818
|
useState as useState7,
|
|
1968
2819
|
useRef as useRef9,
|
|
1969
|
-
useEffect as useEffect7
|
|
2820
|
+
useEffect as useEffect7,
|
|
2821
|
+
useLayoutEffect as useLayoutEffect4
|
|
1970
2822
|
} from "react";
|
|
2823
|
+
import { createPortal as createPortal2 } from "react-dom";
|
|
1971
2824
|
import { Rnd } from "react-rnd";
|
|
1972
2825
|
var DefaultStyleIcon2 = () => /* @__PURE__ */ React10.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React10.createElement("path", { d: "M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9c.83 0 1.5-.67 1.5-1.5 0-.39-.15-.74-.39-1.01-.23-.26-.38-.61-.38-.99 0-.83.67-1.5 1.5-1.5H16c2.76 0 5-2.24 5-5 0-4.42-4.03-8-9-8zm-5.5 9c-.83 0-1.5-.67-1.5-1.5S5.67 9 6.5 9 8 9.67 8 10.5 7.33 12 6.5 12zm3-4C8.67 8 8 7.33 8 6.5S8.67 5 9.5 5s1.5.67 1.5 1.5S10.33 8 9.5 8zm5 0c-.83 0-1.5-.67-1.5-1.5S13.67 5 14.5 5s1.5.67 1.5 1.5S15.33 8 14.5 8zm3 4c-.83 0-1.5-.67-1.5-1.5S16.67 9 17.5 9s1.5.67 1.5 1.5-.67 1.5-1.5 1.5z" }));
|
|
1973
2826
|
var DefaultDeleteIcon2 = () => /* @__PURE__ */ React10.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React10.createElement("path", { d: "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z" }));
|
|
2827
|
+
var DefaultCopyIcon2 = () => /* @__PURE__ */ React10.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React10.createElement("path", { d: "M16 1H4c-1.1 0-2 .9-2 2v12h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z" }));
|
|
2828
|
+
var DefaultCopiedIcon2 = () => /* @__PURE__ */ React10.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React10.createElement("path", { d: "M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z" }));
|
|
1974
2829
|
var DEFAULT_COLOR_PRESETS2 = [
|
|
1975
2830
|
"rgba(255, 226, 143, 1)",
|
|
1976
2831
|
// Yellow (default)
|
|
@@ -1996,11 +2851,28 @@ var AreaHighlight = ({
|
|
|
1996
2851
|
onDelete,
|
|
1997
2852
|
styleIcon,
|
|
1998
2853
|
deleteIcon,
|
|
2854
|
+
copyText,
|
|
1999
2855
|
colorPresets = DEFAULT_COLOR_PRESETS2
|
|
2000
2856
|
}) => {
|
|
2001
2857
|
const [isStylePanelOpen, setIsStylePanelOpen] = useState7(false);
|
|
2002
2858
|
const [isHovered, setIsHovered] = useState7(false);
|
|
2859
|
+
const [isCopied, setIsCopied] = useState7(false);
|
|
2860
|
+
const [configLayer, setConfigLayer] = useState7(null);
|
|
2003
2861
|
const stylePanelRef = useRef9(null);
|
|
2862
|
+
const containerRef = useRef9(null);
|
|
2863
|
+
const copyResetTimeoutRef = useRef9(null);
|
|
2864
|
+
useLayoutEffect4(() => {
|
|
2865
|
+
if (containerRef.current) {
|
|
2866
|
+
setConfigLayer(findOrCreateHighlightConfigLayer(containerRef.current));
|
|
2867
|
+
}
|
|
2868
|
+
}, []);
|
|
2869
|
+
useEffect7(() => {
|
|
2870
|
+
return () => {
|
|
2871
|
+
if (copyResetTimeoutRef.current) {
|
|
2872
|
+
window.clearTimeout(copyResetTimeoutRef.current);
|
|
2873
|
+
}
|
|
2874
|
+
};
|
|
2875
|
+
}, []);
|
|
2004
2876
|
useEffect7(() => {
|
|
2005
2877
|
if (!isStylePanelOpen) return;
|
|
2006
2878
|
const handleClickOutside = (e) => {
|
|
@@ -2020,87 +2892,117 @@ var AreaHighlight = ({
|
|
|
2020
2892
|
const key = `${highlight.position.boundingRect.width}${highlight.position.boundingRect.height}${highlight.position.boundingRect.left}${highlight.position.boundingRect.top}`;
|
|
2021
2893
|
const mergedStyle = {
|
|
2022
2894
|
...style,
|
|
2023
|
-
backgroundColor: highlightColor
|
|
2895
|
+
backgroundColor: `color-mix(in srgb, ${highlightColor} var(--hl-fill-alpha, 100%), transparent)`
|
|
2896
|
+
};
|
|
2897
|
+
const handleCopy = async (event) => {
|
|
2898
|
+
event.stopPropagation();
|
|
2899
|
+
const text = copyText || highlight.content?.text || (containerRef.current ? extractTextFromHighlightRect(
|
|
2900
|
+
containerRef.current,
|
|
2901
|
+
highlight.position.boundingRect
|
|
2902
|
+
) : "");
|
|
2903
|
+
await copyTextToClipboard(text);
|
|
2904
|
+
setIsCopied(true);
|
|
2905
|
+
if (copyResetTimeoutRef.current) {
|
|
2906
|
+
window.clearTimeout(copyResetTimeoutRef.current);
|
|
2907
|
+
}
|
|
2908
|
+
copyResetTimeoutRef.current = window.setTimeout(() => {
|
|
2909
|
+
setIsCopied(false);
|
|
2910
|
+
copyResetTimeoutRef.current = null;
|
|
2911
|
+
}, 1500);
|
|
2024
2912
|
};
|
|
2025
2913
|
return /* @__PURE__ */ React10.createElement(
|
|
2026
2914
|
"div",
|
|
2027
2915
|
{
|
|
2028
2916
|
className: `AreaHighlight ${highlightClass}`,
|
|
2029
|
-
onContextMenu
|
|
2917
|
+
onContextMenu,
|
|
2918
|
+
ref: containerRef
|
|
2030
2919
|
},
|
|
2031
|
-
(onStyleChange || onDelete) &&
|
|
2032
|
-
"div",
|
|
2033
|
-
{
|
|
2034
|
-
className: "AreaHighlight__toolbar-wrapper",
|
|
2035
|
-
style: {
|
|
2036
|
-
position: "absolute",
|
|
2037
|
-
left: highlight.position.boundingRect.left,
|
|
2038
|
-
top: highlight.position.boundingRect.top - 28,
|
|
2039
|
-
paddingBottom: 12
|
|
2040
|
-
},
|
|
2041
|
-
onMouseEnter: () => setIsHovered(true),
|
|
2042
|
-
onMouseLeave: () => setIsHovered(false)
|
|
2043
|
-
},
|
|
2920
|
+
configLayer && (onStyleChange || onDelete) && createPortal2(
|
|
2044
2921
|
/* @__PURE__ */ React10.createElement(
|
|
2045
2922
|
"div",
|
|
2046
2923
|
{
|
|
2047
|
-
className:
|
|
2924
|
+
className: "AreaHighlight__toolbar-wrapper",
|
|
2925
|
+
style: {
|
|
2926
|
+
position: "absolute",
|
|
2927
|
+
left: highlight.position.boundingRect.left,
|
|
2928
|
+
top: highlight.position.boundingRect.top - 28,
|
|
2929
|
+
paddingBottom: 12
|
|
2930
|
+
},
|
|
2931
|
+
onMouseEnter: () => setIsHovered(true),
|
|
2932
|
+
onMouseLeave: () => setIsHovered(false)
|
|
2048
2933
|
},
|
|
2049
|
-
|
|
2050
|
-
"
|
|
2934
|
+
/* @__PURE__ */ React10.createElement(
|
|
2935
|
+
"div",
|
|
2051
2936
|
{
|
|
2052
|
-
className: "
|
|
2053
|
-
onClick: (e) => {
|
|
2054
|
-
e.stopPropagation();
|
|
2055
|
-
setIsStylePanelOpen(!isStylePanelOpen);
|
|
2056
|
-
},
|
|
2057
|
-
title: "Change color",
|
|
2058
|
-
type: "button"
|
|
2937
|
+
className: `AreaHighlight__toolbar ${isHovered || isScrolledTo || isStylePanelOpen ? "AreaHighlight__toolbar--visible" : ""}`
|
|
2059
2938
|
},
|
|
2060
|
-
|
|
2939
|
+
onStyleChange && /* @__PURE__ */ React10.createElement(
|
|
2940
|
+
"button",
|
|
2941
|
+
{
|
|
2942
|
+
className: "AreaHighlight__style-button",
|
|
2943
|
+
onClick: (e) => {
|
|
2944
|
+
e.stopPropagation();
|
|
2945
|
+
setIsStylePanelOpen(!isStylePanelOpen);
|
|
2946
|
+
},
|
|
2947
|
+
title: "Change color",
|
|
2948
|
+
type: "button"
|
|
2949
|
+
},
|
|
2950
|
+
styleIcon || /* @__PURE__ */ React10.createElement(DefaultStyleIcon2, null)
|
|
2951
|
+
),
|
|
2952
|
+
/* @__PURE__ */ React10.createElement(
|
|
2953
|
+
"button",
|
|
2954
|
+
{
|
|
2955
|
+
className: "AreaHighlight__copy-button",
|
|
2956
|
+
onClick: handleCopy,
|
|
2957
|
+
title: isCopied ? "Copied" : "Copy text",
|
|
2958
|
+
type: "button"
|
|
2959
|
+
},
|
|
2960
|
+
isCopied ? /* @__PURE__ */ React10.createElement(DefaultCopiedIcon2, null) : /* @__PURE__ */ React10.createElement(DefaultCopyIcon2, null)
|
|
2961
|
+
),
|
|
2962
|
+
onDelete && /* @__PURE__ */ React10.createElement(
|
|
2963
|
+
"button",
|
|
2964
|
+
{
|
|
2965
|
+
className: "AreaHighlight__delete-button",
|
|
2966
|
+
onClick: (e) => {
|
|
2967
|
+
e.stopPropagation();
|
|
2968
|
+
onDelete();
|
|
2969
|
+
},
|
|
2970
|
+
title: "Delete",
|
|
2971
|
+
type: "button"
|
|
2972
|
+
},
|
|
2973
|
+
deleteIcon || /* @__PURE__ */ React10.createElement(DefaultDeleteIcon2, null)
|
|
2974
|
+
)
|
|
2061
2975
|
),
|
|
2062
|
-
|
|
2063
|
-
"
|
|
2976
|
+
isStylePanelOpen && onStyleChange && /* @__PURE__ */ React10.createElement(
|
|
2977
|
+
"div",
|
|
2064
2978
|
{
|
|
2065
|
-
className: "
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
onDelete();
|
|
2069
|
-
},
|
|
2070
|
-
title: "Delete",
|
|
2071
|
-
type: "button"
|
|
2979
|
+
className: "AreaHighlight__style-panel",
|
|
2980
|
+
ref: stylePanelRef,
|
|
2981
|
+
onClick: (e) => e.stopPropagation()
|
|
2072
2982
|
},
|
|
2073
|
-
|
|
2983
|
+
/* @__PURE__ */ React10.createElement("div", { className: "AreaHighlight__style-row" }, /* @__PURE__ */ React10.createElement("label", null, "Color"), /* @__PURE__ */ React10.createElement("div", { className: "AreaHighlight__color-options" }, /* @__PURE__ */ React10.createElement("div", { className: "AreaHighlight__color-presets" }, colorPresets.map((c) => /* @__PURE__ */ React10.createElement(
|
|
2984
|
+
"button",
|
|
2985
|
+
{
|
|
2986
|
+
key: c,
|
|
2987
|
+
type: "button",
|
|
2988
|
+
className: `AreaHighlight__color-preset ${highlightColor === c ? "active" : ""}`,
|
|
2989
|
+
style: { backgroundColor: c },
|
|
2990
|
+
onClick: () => onStyleChange({ highlightColor: c }),
|
|
2991
|
+
title: c
|
|
2992
|
+
}
|
|
2993
|
+
))), /* @__PURE__ */ React10.createElement(
|
|
2994
|
+
"input",
|
|
2995
|
+
{
|
|
2996
|
+
type: "color",
|
|
2997
|
+
value: highlightColor,
|
|
2998
|
+
onChange: (e) => {
|
|
2999
|
+
onStyleChange({ highlightColor: e.target.value });
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
)))
|
|
2074
3003
|
)
|
|
2075
3004
|
),
|
|
2076
|
-
|
|
2077
|
-
"div",
|
|
2078
|
-
{
|
|
2079
|
-
className: "AreaHighlight__style-panel",
|
|
2080
|
-
ref: stylePanelRef,
|
|
2081
|
-
onClick: (e) => e.stopPropagation()
|
|
2082
|
-
},
|
|
2083
|
-
/* @__PURE__ */ React10.createElement("div", { className: "AreaHighlight__style-row" }, /* @__PURE__ */ React10.createElement("label", null, "Color"), /* @__PURE__ */ React10.createElement("div", { className: "AreaHighlight__color-options" }, /* @__PURE__ */ React10.createElement("div", { className: "AreaHighlight__color-presets" }, colorPresets.map((c) => /* @__PURE__ */ React10.createElement(
|
|
2084
|
-
"button",
|
|
2085
|
-
{
|
|
2086
|
-
key: c,
|
|
2087
|
-
type: "button",
|
|
2088
|
-
className: `AreaHighlight__color-preset ${highlightColor === c ? "active" : ""}`,
|
|
2089
|
-
style: { backgroundColor: c },
|
|
2090
|
-
onClick: () => onStyleChange({ highlightColor: c }),
|
|
2091
|
-
title: c
|
|
2092
|
-
}
|
|
2093
|
-
))), /* @__PURE__ */ React10.createElement(
|
|
2094
|
-
"input",
|
|
2095
|
-
{
|
|
2096
|
-
type: "color",
|
|
2097
|
-
value: highlightColor,
|
|
2098
|
-
onChange: (e) => {
|
|
2099
|
-
onStyleChange({ highlightColor: e.target.value });
|
|
2100
|
-
}
|
|
2101
|
-
}
|
|
2102
|
-
)))
|
|
2103
|
-
)
|
|
3005
|
+
configLayer
|
|
2104
3006
|
),
|
|
2105
3007
|
/* @__PURE__ */ React10.createElement(
|
|
2106
3008
|
Rnd,
|
|
@@ -2157,6 +3059,8 @@ var DefaultDragIcon = () => /* @__PURE__ */ React11.createElement("svg", { width
|
|
|
2157
3059
|
var DefaultEditIcon = () => /* @__PURE__ */ React11.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React11.createElement("path", { d: "M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z" }));
|
|
2158
3060
|
var DefaultStyleIcon3 = () => /* @__PURE__ */ React11.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React11.createElement("path", { d: "M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9c.83 0 1.5-.67 1.5-1.5 0-.39-.15-.74-.39-1.01-.23-.26-.38-.61-.38-.99 0-.83.67-1.5 1.5-1.5H16c2.76 0 5-2.24 5-5 0-4.42-4.03-8-9-8zm-5.5 9c-.83 0-1.5-.67-1.5-1.5S5.67 9 6.5 9 8 9.67 8 10.5 7.33 12 6.5 12zm3-4C8.67 8 8 7.33 8 6.5S8.67 5 9.5 5s1.5.67 1.5 1.5S10.33 8 9.5 8zm5 0c-.83 0-1.5-.67-1.5-1.5S13.67 5 14.5 5s1.5.67 1.5 1.5S15.33 8 14.5 8zm3 4c-.83 0-1.5-.67-1.5-1.5S16.67 9 17.5 9s1.5.67 1.5 1.5-.67 1.5-1.5 1.5z" }));
|
|
2159
3061
|
var DefaultDeleteIcon3 = () => /* @__PURE__ */ React11.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React11.createElement("path", { d: "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z" }));
|
|
3062
|
+
var DefaultCompactIcon = () => /* @__PURE__ */ React11.createElement("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React11.createElement("path", { d: "M6 3h9l5 5v13H6V3zm8 1.5V9h4.5L14 4.5zM8 12h8v1.5H8V12zm0 3h8v1.5H8V15zm0 3h5v1.5H8V18z" }));
|
|
3063
|
+
var DefaultCollapseIcon = () => /* @__PURE__ */ React11.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React11.createElement("path", { d: "M7 10v2h10v-2H7zm-2-7h14c1.1 0 2 .9 2 2v14c0 1.1-.9 2-2 2H5c-1.1 0-2-.9-2-2V5c0-1.1.9-2 2-2zm0 2v14h14V5H5z" }));
|
|
2160
3064
|
var DEFAULT_BACKGROUND_PRESETS = ["transparent", "#ffffc8", "#ffcdd2", "#c8e6c9", "#bbdefb", "#e1bee7"];
|
|
2161
3065
|
var DEFAULT_TEXT_PRESETS = ["#333333", "#d32f2f", "#1976d2", "#388e3c", "#7b1fa2"];
|
|
2162
3066
|
var FreetextHighlight = ({
|
|
@@ -2180,9 +3084,13 @@ var FreetextHighlight = ({
|
|
|
2180
3084
|
backgroundColorPresets = DEFAULT_BACKGROUND_PRESETS,
|
|
2181
3085
|
textColorPresets = DEFAULT_TEXT_PRESETS,
|
|
2182
3086
|
onDelete,
|
|
2183
|
-
deleteIcon
|
|
3087
|
+
deleteIcon,
|
|
3088
|
+
compact = false,
|
|
3089
|
+
compactSize = 32,
|
|
3090
|
+
compactIcon
|
|
2184
3091
|
}) => {
|
|
2185
3092
|
const [isEditing, setIsEditing] = useState8(false);
|
|
3093
|
+
const [isExpanded, setIsExpanded] = useState8(!compact);
|
|
2186
3094
|
const [isStylePanelOpen, setIsStylePanelOpen] = useState8(false);
|
|
2187
3095
|
const [text, setText] = useState8(highlight.content?.text || "");
|
|
2188
3096
|
const textareaRef = useRef10(null);
|
|
@@ -2190,6 +3098,13 @@ var FreetextHighlight = ({
|
|
|
2190
3098
|
useEffect8(() => {
|
|
2191
3099
|
setText(highlight.content?.text || "");
|
|
2192
3100
|
}, [highlight.content?.text]);
|
|
3101
|
+
useEffect8(() => {
|
|
3102
|
+
setIsExpanded(!compact);
|
|
3103
|
+
setIsStylePanelOpen(false);
|
|
3104
|
+
if (!compact) {
|
|
3105
|
+
setIsEditing(false);
|
|
3106
|
+
}
|
|
3107
|
+
}, [compact]);
|
|
2193
3108
|
useEffect8(() => {
|
|
2194
3109
|
if (isEditing && textareaRef.current) {
|
|
2195
3110
|
textareaRef.current.focus();
|
|
@@ -2213,10 +3128,14 @@ var FreetextHighlight = ({
|
|
|
2213
3128
|
}, [isStylePanelOpen]);
|
|
2214
3129
|
const highlightClass = isScrolledTo ? "FreetextHighlight--scrolledTo" : "";
|
|
2215
3130
|
const editingClass = isEditing ? "FreetextHighlight--editing" : "";
|
|
2216
|
-
const
|
|
3131
|
+
const compactClass = compact ? "FreetextHighlight--compact" : "";
|
|
3132
|
+
const isCompactCollapsed = compact && !isExpanded && !isEditing && !isStylePanelOpen;
|
|
3133
|
+
const collapsedClass = isCompactCollapsed ? "FreetextHighlight--collapsed" : "";
|
|
3134
|
+
const key = `${highlight.position.boundingRect.width}${highlight.position.boundingRect.height}${highlight.position.boundingRect.left}${highlight.position.boundingRect.top}${isCompactCollapsed ? "collapsed" : "expanded"}`;
|
|
2217
3135
|
const handleTextClick = (e) => {
|
|
2218
3136
|
e.stopPropagation();
|
|
2219
3137
|
if (!isEditing) {
|
|
3138
|
+
setIsExpanded(true);
|
|
2220
3139
|
setIsEditing(true);
|
|
2221
3140
|
onEditStart?.();
|
|
2222
3141
|
}
|
|
@@ -2251,7 +3170,7 @@ var FreetextHighlight = ({
|
|
|
2251
3170
|
return /* @__PURE__ */ React11.createElement(
|
|
2252
3171
|
"div",
|
|
2253
3172
|
{
|
|
2254
|
-
className: `FreetextHighlight ${highlightClass} ${editingClass}`,
|
|
3173
|
+
className: `FreetextHighlight ${highlightClass} ${editingClass} ${compactClass} ${collapsedClass}`,
|
|
2255
3174
|
onContextMenu
|
|
2256
3175
|
},
|
|
2257
3176
|
/* @__PURE__ */ React11.createElement(
|
|
@@ -2274,14 +3193,14 @@ var FreetextHighlight = ({
|
|
|
2274
3193
|
default: {
|
|
2275
3194
|
x: highlight.position.boundingRect.left,
|
|
2276
3195
|
y: highlight.position.boundingRect.top,
|
|
2277
|
-
width: highlight.position.boundingRect.width || 150,
|
|
2278
|
-
height: highlight.position.boundingRect.height || 80
|
|
3196
|
+
width: isCompactCollapsed ? compactSize : highlight.position.boundingRect.width || 150,
|
|
3197
|
+
height: isCompactCollapsed ? compactSize : highlight.position.boundingRect.height || 80
|
|
2279
3198
|
},
|
|
2280
|
-
minWidth: 100,
|
|
2281
|
-
minHeight: 50,
|
|
3199
|
+
minWidth: isCompactCollapsed ? compactSize : 100,
|
|
3200
|
+
minHeight: isCompactCollapsed ? compactSize : 50,
|
|
2282
3201
|
key,
|
|
2283
3202
|
bounds,
|
|
2284
|
-
enableResizing: {
|
|
3203
|
+
enableResizing: isCompactCollapsed ? false : {
|
|
2285
3204
|
top: false,
|
|
2286
3205
|
right: true,
|
|
2287
3206
|
bottom: true,
|
|
@@ -2306,9 +3225,21 @@ var FreetextHighlight = ({
|
|
|
2306
3225
|
onEditStart?.();
|
|
2307
3226
|
}
|
|
2308
3227
|
},
|
|
2309
|
-
cancel: ".FreetextHighlight__text, .FreetextHighlight__input, .FreetextHighlight__edit-button, .FreetextHighlight__style-button, .FreetextHighlight__style-panel, .FreetextHighlight__delete-button"
|
|
3228
|
+
cancel: ".FreetextHighlight__text, .FreetextHighlight__input, .FreetextHighlight__edit-button, .FreetextHighlight__style-button, .FreetextHighlight__style-panel, .FreetextHighlight__delete-button, .FreetextHighlight__collapse-button, .FreetextHighlight__compact-button"
|
|
2310
3229
|
},
|
|
2311
|
-
/* @__PURE__ */ React11.createElement("div", { className: "FreetextHighlight__container", style: containerStyle },
|
|
3230
|
+
/* @__PURE__ */ React11.createElement("div", { className: "FreetextHighlight__container", style: containerStyle }, isCompactCollapsed ? /* @__PURE__ */ React11.createElement(
|
|
3231
|
+
"button",
|
|
3232
|
+
{
|
|
3233
|
+
className: "FreetextHighlight__compact-button",
|
|
3234
|
+
type: "button",
|
|
3235
|
+
title: text || "Open note",
|
|
3236
|
+
onClick: (event) => {
|
|
3237
|
+
event.stopPropagation();
|
|
3238
|
+
setIsExpanded(true);
|
|
3239
|
+
}
|
|
3240
|
+
},
|
|
3241
|
+
compactIcon || /* @__PURE__ */ React11.createElement(DefaultCompactIcon, null)
|
|
3242
|
+
) : /* @__PURE__ */ React11.createElement(React11.Fragment, null, /* @__PURE__ */ React11.createElement("div", { className: "FreetextHighlight__toolbar" }, /* @__PURE__ */ React11.createElement("div", { className: "FreetextHighlight__drag-handle", title: "Drag to move" }, dragIcon || /* @__PURE__ */ React11.createElement(DefaultDragIcon, null)), /* @__PURE__ */ React11.createElement(
|
|
2312
3243
|
"button",
|
|
2313
3244
|
{
|
|
2314
3245
|
className: "FreetextHighlight__edit-button",
|
|
@@ -2329,6 +3260,19 @@ var FreetextHighlight = ({
|
|
|
2329
3260
|
type: "button"
|
|
2330
3261
|
},
|
|
2331
3262
|
styleIcon || /* @__PURE__ */ React11.createElement(DefaultStyleIcon3, null)
|
|
3263
|
+
), compact && /* @__PURE__ */ React11.createElement(
|
|
3264
|
+
"button",
|
|
3265
|
+
{
|
|
3266
|
+
className: "FreetextHighlight__collapse-button",
|
|
3267
|
+
onClick: (e) => {
|
|
3268
|
+
e.stopPropagation();
|
|
3269
|
+
setIsExpanded(false);
|
|
3270
|
+
setIsStylePanelOpen(false);
|
|
3271
|
+
},
|
|
3272
|
+
title: "Collapse note",
|
|
3273
|
+
type: "button"
|
|
3274
|
+
},
|
|
3275
|
+
/* @__PURE__ */ React11.createElement(DefaultCollapseIcon, null)
|
|
2332
3276
|
), onDelete && /* @__PURE__ */ React11.createElement(
|
|
2333
3277
|
"button",
|
|
2334
3278
|
{
|
|
@@ -2433,7 +3377,7 @@ var FreetextHighlight = ({
|
|
|
2433
3377
|
onKeyDown: handleKeyDown,
|
|
2434
3378
|
onClick: (e) => e.stopPropagation()
|
|
2435
3379
|
}
|
|
2436
|
-
) : /* @__PURE__ */ React11.createElement("div", { className: "FreetextHighlight__text" }, text || "New note")))
|
|
3380
|
+
) : /* @__PURE__ */ React11.createElement("div", { className: "FreetextHighlight__text" }, text || "New note"))))
|
|
2437
3381
|
)
|
|
2438
3382
|
);
|
|
2439
3383
|
};
|
|
@@ -2687,7 +3631,8 @@ var SignaturePad = ({
|
|
|
2687
3631
|
};
|
|
2688
3632
|
|
|
2689
3633
|
// src/components/DrawingHighlight.tsx
|
|
2690
|
-
import React14, { useState as useState9, useCallback as useCallback4, useEffect as useEffect10, useRef as useRef12 } from "react";
|
|
3634
|
+
import React14, { useState as useState9, useCallback as useCallback4, useEffect as useEffect10, useLayoutEffect as useLayoutEffect5, useRef as useRef12 } from "react";
|
|
3635
|
+
import { createPortal as createPortal3 } from "react-dom";
|
|
2691
3636
|
import { Rnd as Rnd4 } from "react-rnd";
|
|
2692
3637
|
var DRAWING_COLORS = ["#000000", "#FF0000", "#0000FF", "#00FF00", "#FFFF00"];
|
|
2693
3638
|
var STROKE_WIDTHS = [
|
|
@@ -2734,7 +3679,15 @@ var DrawingHighlight = ({
|
|
|
2734
3679
|
}) => {
|
|
2735
3680
|
const highlightClass = isScrolledTo ? "DrawingHighlight--scrolledTo" : "";
|
|
2736
3681
|
const [showStyleControls, setShowStyleControls] = useState9(false);
|
|
3682
|
+
const [isHovered, setIsHovered] = useState9(false);
|
|
3683
|
+
const [configLayer, setConfigLayer] = useState9(null);
|
|
2737
3684
|
const styleControlsRef = useRef12(null);
|
|
3685
|
+
const containerRef = useRef12(null);
|
|
3686
|
+
useLayoutEffect5(() => {
|
|
3687
|
+
if (containerRef.current) {
|
|
3688
|
+
setConfigLayer(findOrCreateHighlightConfigLayer(containerRef.current));
|
|
3689
|
+
}
|
|
3690
|
+
}, []);
|
|
2738
3691
|
useEffect10(() => {
|
|
2739
3692
|
if (!showStyleControls) return;
|
|
2740
3693
|
const handleClickOutside = (e) => {
|
|
@@ -2787,8 +3740,78 @@ var DrawingHighlight = ({
|
|
|
2787
3740
|
"div",
|
|
2788
3741
|
{
|
|
2789
3742
|
className: `DrawingHighlight ${highlightClass}`,
|
|
2790
|
-
onContextMenu
|
|
3743
|
+
onContextMenu,
|
|
3744
|
+
ref: containerRef
|
|
2791
3745
|
},
|
|
3746
|
+
configLayer && createPortal3(
|
|
3747
|
+
/* @__PURE__ */ React14.createElement(
|
|
3748
|
+
"div",
|
|
3749
|
+
{
|
|
3750
|
+
className: `DrawingHighlight__toolbar DrawingHighlight__toolbar--floating ${isHovered || isScrolledTo || showStyleControls ? "DrawingHighlight__toolbar--visible" : ""}`,
|
|
3751
|
+
style: {
|
|
3752
|
+
left: highlight.position.boundingRect.left + 4,
|
|
3753
|
+
top: highlight.position.boundingRect.top + 4
|
|
3754
|
+
},
|
|
3755
|
+
onMouseEnter: () => setIsHovered(true),
|
|
3756
|
+
onMouseLeave: () => setIsHovered(false)
|
|
3757
|
+
},
|
|
3758
|
+
/* @__PURE__ */ React14.createElement("div", { className: "DrawingHighlight__drag-handle", title: "Drag to move" }, dragIcon || /* @__PURE__ */ React14.createElement(DefaultDragIcon3, null)),
|
|
3759
|
+
strokes && strokes.length > 0 && onStyleChange && /* @__PURE__ */ React14.createElement(
|
|
3760
|
+
"button",
|
|
3761
|
+
{
|
|
3762
|
+
type: "button",
|
|
3763
|
+
className: "DrawingHighlight__style-button",
|
|
3764
|
+
title: "Edit style",
|
|
3765
|
+
onClick: (e) => {
|
|
3766
|
+
e.stopPropagation();
|
|
3767
|
+
setShowStyleControls(!showStyleControls);
|
|
3768
|
+
}
|
|
3769
|
+
},
|
|
3770
|
+
/* @__PURE__ */ React14.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React14.createElement("path", { d: "M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z" }))
|
|
3771
|
+
),
|
|
3772
|
+
onDelete && /* @__PURE__ */ React14.createElement(
|
|
3773
|
+
"button",
|
|
3774
|
+
{
|
|
3775
|
+
className: "DrawingHighlight__delete-button",
|
|
3776
|
+
onClick: (e) => {
|
|
3777
|
+
e.stopPropagation();
|
|
3778
|
+
onDelete();
|
|
3779
|
+
},
|
|
3780
|
+
title: "Delete",
|
|
3781
|
+
type: "button"
|
|
3782
|
+
},
|
|
3783
|
+
deleteIcon || /* @__PURE__ */ React14.createElement(DefaultDeleteIcon5, null)
|
|
3784
|
+
),
|
|
3785
|
+
showStyleControls && strokes && strokes.length > 0 && onStyleChange && /* @__PURE__ */ React14.createElement("div", { className: "DrawingHighlight__style-controls", ref: styleControlsRef }, /* @__PURE__ */ React14.createElement("div", { className: "DrawingHighlight__color-picker" }, DRAWING_COLORS.map((color) => /* @__PURE__ */ React14.createElement(
|
|
3786
|
+
"button",
|
|
3787
|
+
{
|
|
3788
|
+
key: color,
|
|
3789
|
+
type: "button",
|
|
3790
|
+
className: `DrawingHighlight__color-button ${currentColor === color ? "active" : ""}`,
|
|
3791
|
+
style: { backgroundColor: color },
|
|
3792
|
+
onClick: (e) => {
|
|
3793
|
+
e.stopPropagation();
|
|
3794
|
+
handleColorChange(color);
|
|
3795
|
+
},
|
|
3796
|
+
title: `Color: ${color}`
|
|
3797
|
+
}
|
|
3798
|
+
))), /* @__PURE__ */ React14.createElement("div", { className: "DrawingHighlight__width-picker" }, STROKE_WIDTHS.map((w) => /* @__PURE__ */ React14.createElement(
|
|
3799
|
+
"button",
|
|
3800
|
+
{
|
|
3801
|
+
key: w.value,
|
|
3802
|
+
type: "button",
|
|
3803
|
+
className: `DrawingHighlight__width-button ${currentWidth === w.value ? "active" : ""}`,
|
|
3804
|
+
onClick: (e) => {
|
|
3805
|
+
e.stopPropagation();
|
|
3806
|
+
handleWidthChange(w.value);
|
|
3807
|
+
},
|
|
3808
|
+
title: w.label
|
|
3809
|
+
},
|
|
3810
|
+
w.label
|
|
3811
|
+
))))
|
|
3812
|
+
),
|
|
3813
|
+
configLayer
|
|
3814
|
+
),
|
|
2792
3815
|
/* @__PURE__ */ React14.createElement(
|
|
2793
3816
|
Rnd4,
|
|
2794
3817
|
{
|
|
@@ -2826,72 +3849,29 @@ var DrawingHighlight = ({
|
|
|
2826
3849
|
key,
|
|
2827
3850
|
bounds,
|
|
2828
3851
|
lockAspectRatio: false,
|
|
2829
|
-
dragHandleClassName: "DrawingHighlight__drag-handle",
|
|
2830
3852
|
onClick: (event) => {
|
|
2831
3853
|
event.stopPropagation();
|
|
2832
3854
|
event.preventDefault();
|
|
2833
3855
|
},
|
|
2834
3856
|
style
|
|
2835
3857
|
},
|
|
2836
|
-
/* @__PURE__ */ React14.createElement(
|
|
2837
|
-
"
|
|
2838
|
-
{
|
|
2839
|
-
type: "button",
|
|
2840
|
-
className: "DrawingHighlight__style-button",
|
|
2841
|
-
title: "Edit style",
|
|
2842
|
-
onClick: (e) => {
|
|
2843
|
-
e.stopPropagation();
|
|
2844
|
-
setShowStyleControls(!showStyleControls);
|
|
2845
|
-
}
|
|
2846
|
-
},
|
|
2847
|
-
/* @__PURE__ */ React14.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React14.createElement("path", { d: "M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z" }))
|
|
2848
|
-
), onDelete && /* @__PURE__ */ React14.createElement(
|
|
2849
|
-
"button",
|
|
2850
|
-
{
|
|
2851
|
-
className: "DrawingHighlight__delete-button",
|
|
2852
|
-
onClick: (e) => {
|
|
2853
|
-
e.stopPropagation();
|
|
2854
|
-
onDelete();
|
|
2855
|
-
},
|
|
2856
|
-
title: "Delete",
|
|
2857
|
-
type: "button"
|
|
2858
|
-
},
|
|
2859
|
-
deleteIcon || /* @__PURE__ */ React14.createElement(DefaultDeleteIcon5, null)
|
|
2860
|
-
)), showStyleControls && strokes && strokes.length > 0 && onStyleChange && /* @__PURE__ */ React14.createElement("div", { className: "DrawingHighlight__style-controls", ref: styleControlsRef }, /* @__PURE__ */ React14.createElement("div", { className: "DrawingHighlight__color-picker" }, DRAWING_COLORS.map((color) => /* @__PURE__ */ React14.createElement(
|
|
2861
|
-
"button",
|
|
2862
|
-
{
|
|
2863
|
-
key: color,
|
|
2864
|
-
type: "button",
|
|
2865
|
-
className: `DrawingHighlight__color-button ${currentColor === color ? "active" : ""}`,
|
|
2866
|
-
style: { backgroundColor: color },
|
|
2867
|
-
onClick: (e) => {
|
|
2868
|
-
e.stopPropagation();
|
|
2869
|
-
handleColorChange(color);
|
|
2870
|
-
},
|
|
2871
|
-
title: `Color: ${color}`
|
|
2872
|
-
}
|
|
2873
|
-
))), /* @__PURE__ */ React14.createElement("div", { className: "DrawingHighlight__width-picker" }, STROKE_WIDTHS.map((w) => /* @__PURE__ */ React14.createElement(
|
|
2874
|
-
"button",
|
|
3858
|
+
/* @__PURE__ */ React14.createElement(
|
|
3859
|
+
"div",
|
|
2875
3860
|
{
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
onClick: (e) => {
|
|
2880
|
-
e.stopPropagation();
|
|
2881
|
-
handleWidthChange(w.value);
|
|
2882
|
-
},
|
|
2883
|
-
title: w.label
|
|
3861
|
+
className: "DrawingHighlight__container",
|
|
3862
|
+
onMouseEnter: () => setIsHovered(true),
|
|
3863
|
+
onMouseLeave: () => setIsHovered(false)
|
|
2884
3864
|
},
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
}
|
|
2894
|
-
)
|
|
3865
|
+
/* @__PURE__ */ React14.createElement("div", { className: "DrawingHighlight__content" }, imageUrl ? /* @__PURE__ */ React14.createElement(
|
|
3866
|
+
"img",
|
|
3867
|
+
{
|
|
3868
|
+
src: imageUrl,
|
|
3869
|
+
alt: "Drawing",
|
|
3870
|
+
className: "DrawingHighlight__image",
|
|
3871
|
+
draggable: false
|
|
3872
|
+
}
|
|
3873
|
+
) : /* @__PURE__ */ React14.createElement("div", { className: "DrawingHighlight__placeholder" }, "No drawing"))
|
|
3874
|
+
)
|
|
2895
3875
|
)
|
|
2896
3876
|
);
|
|
2897
3877
|
};
|
|
@@ -2900,8 +3880,10 @@ var DrawingHighlight = ({
|
|
|
2900
3880
|
import React15, {
|
|
2901
3881
|
useState as useState10,
|
|
2902
3882
|
useRef as useRef13,
|
|
2903
|
-
useEffect as useEffect11
|
|
3883
|
+
useEffect as useEffect11,
|
|
3884
|
+
useLayoutEffect as useLayoutEffect6
|
|
2904
3885
|
} from "react";
|
|
3886
|
+
import { createPortal as createPortal4 } from "react-dom";
|
|
2905
3887
|
import { Rnd as Rnd5 } from "react-rnd";
|
|
2906
3888
|
var DefaultStyleIcon4 = () => /* @__PURE__ */ React15.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React15.createElement("path", { d: "M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9c.83 0 1.5-.67 1.5-1.5 0-.39-.15-.74-.39-1.01-.23-.26-.38-.61-.38-.99 0-.83.67-1.5 1.5-1.5H16c2.76 0 5-2.24 5-5 0-4.42-4.03-8-9-8zm-5.5 9c-.83 0-1.5-.67-1.5-1.5S5.67 9 6.5 9 8 9.67 8 10.5 7.33 12 6.5 12zm3-4C8.67 8 8 7.33 8 6.5S8.67 5 9.5 5s1.5.67 1.5 1.5S10.33 8 9.5 8zm5 0c-.83 0-1.5-.67-1.5-1.5S13.67 5 14.5 5s1.5.67 1.5 1.5S15.33 8 14.5 8zm3 4c-.83 0-1.5-.67-1.5-1.5S16.67 9 17.5 9s1.5.67 1.5 1.5-.67 1.5-1.5 1.5z" }));
|
|
2907
3889
|
var DefaultDeleteIcon6 = () => /* @__PURE__ */ React15.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React15.createElement("path", { d: "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z" }));
|
|
@@ -2944,7 +3926,14 @@ var ShapeHighlight = ({
|
|
|
2944
3926
|
}) => {
|
|
2945
3927
|
const [isStylePanelOpen, setIsStylePanelOpen] = useState10(false);
|
|
2946
3928
|
const [isHovered, setIsHovered] = useState10(false);
|
|
3929
|
+
const [configLayer, setConfigLayer] = useState10(null);
|
|
2947
3930
|
const stylePanelRef = useRef13(null);
|
|
3931
|
+
const containerRef = useRef13(null);
|
|
3932
|
+
useLayoutEffect6(() => {
|
|
3933
|
+
if (containerRef.current) {
|
|
3934
|
+
setConfigLayer(findOrCreateHighlightConfigLayer(containerRef.current));
|
|
3935
|
+
}
|
|
3936
|
+
}, []);
|
|
2948
3937
|
useEffect11(() => {
|
|
2949
3938
|
if (!isStylePanelOpen) return;
|
|
2950
3939
|
const handleClickOutside = (e) => {
|
|
@@ -3056,92 +4045,96 @@ var ShapeHighlight = ({
|
|
|
3056
4045
|
"div",
|
|
3057
4046
|
{
|
|
3058
4047
|
className: `ShapeHighlight ${highlightClass}`,
|
|
3059
|
-
onContextMenu
|
|
4048
|
+
onContextMenu,
|
|
4049
|
+
ref: containerRef
|
|
3060
4050
|
},
|
|
3061
|
-
(onStyleChange || onDelete) &&
|
|
3062
|
-
"div",
|
|
3063
|
-
{
|
|
3064
|
-
className: "ShapeHighlight__toolbar-wrapper",
|
|
3065
|
-
style: {
|
|
3066
|
-
position: "absolute",
|
|
3067
|
-
left: highlight.position.boundingRect.left,
|
|
3068
|
-
top: highlight.position.boundingRect.top - 28,
|
|
3069
|
-
paddingBottom: 12
|
|
3070
|
-
},
|
|
3071
|
-
onMouseEnter: () => setIsHovered(true),
|
|
3072
|
-
onMouseLeave: () => setIsHovered(false)
|
|
3073
|
-
},
|
|
4051
|
+
configLayer && (onStyleChange || onDelete) && createPortal4(
|
|
3074
4052
|
/* @__PURE__ */ React15.createElement(
|
|
3075
4053
|
"div",
|
|
3076
4054
|
{
|
|
3077
|
-
className:
|
|
4055
|
+
className: "ShapeHighlight__toolbar-wrapper",
|
|
4056
|
+
style: {
|
|
4057
|
+
position: "absolute",
|
|
4058
|
+
left: highlight.position.boundingRect.left,
|
|
4059
|
+
top: highlight.position.boundingRect.top - 28,
|
|
4060
|
+
paddingBottom: 12
|
|
4061
|
+
},
|
|
4062
|
+
onMouseEnter: () => setIsHovered(true),
|
|
4063
|
+
onMouseLeave: () => setIsHovered(false)
|
|
3078
4064
|
},
|
|
3079
|
-
|
|
3080
|
-
"
|
|
4065
|
+
/* @__PURE__ */ React15.createElement(
|
|
4066
|
+
"div",
|
|
3081
4067
|
{
|
|
3082
|
-
className: "
|
|
3083
|
-
onClick: (e) => {
|
|
3084
|
-
e.stopPropagation();
|
|
3085
|
-
setIsStylePanelOpen(!isStylePanelOpen);
|
|
3086
|
-
},
|
|
3087
|
-
title: "Change style",
|
|
3088
|
-
type: "button"
|
|
4068
|
+
className: `ShapeHighlight__toolbar ${isHovered || isScrolledTo || isStylePanelOpen ? "ShapeHighlight__toolbar--visible" : ""}`
|
|
3089
4069
|
},
|
|
3090
|
-
|
|
4070
|
+
onStyleChange && /* @__PURE__ */ React15.createElement(
|
|
4071
|
+
"button",
|
|
4072
|
+
{
|
|
4073
|
+
className: "ShapeHighlight__style-button",
|
|
4074
|
+
onClick: (e) => {
|
|
4075
|
+
e.stopPropagation();
|
|
4076
|
+
setIsStylePanelOpen(!isStylePanelOpen);
|
|
4077
|
+
},
|
|
4078
|
+
title: "Change style",
|
|
4079
|
+
type: "button"
|
|
4080
|
+
},
|
|
4081
|
+
styleIcon || /* @__PURE__ */ React15.createElement(DefaultStyleIcon4, null)
|
|
4082
|
+
),
|
|
4083
|
+
onDelete && /* @__PURE__ */ React15.createElement(
|
|
4084
|
+
"button",
|
|
4085
|
+
{
|
|
4086
|
+
className: "ShapeHighlight__delete-button",
|
|
4087
|
+
onClick: (e) => {
|
|
4088
|
+
e.stopPropagation();
|
|
4089
|
+
onDelete();
|
|
4090
|
+
},
|
|
4091
|
+
title: "Delete",
|
|
4092
|
+
type: "button"
|
|
4093
|
+
},
|
|
4094
|
+
deleteIcon || /* @__PURE__ */ React15.createElement(DefaultDeleteIcon6, null)
|
|
4095
|
+
)
|
|
3091
4096
|
),
|
|
3092
|
-
|
|
3093
|
-
"
|
|
4097
|
+
isStylePanelOpen && onStyleChange && /* @__PURE__ */ React15.createElement(
|
|
4098
|
+
"div",
|
|
3094
4099
|
{
|
|
3095
|
-
className: "
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
onDelete();
|
|
3099
|
-
},
|
|
3100
|
-
title: "Delete",
|
|
3101
|
-
type: "button"
|
|
4100
|
+
className: "ShapeHighlight__style-panel",
|
|
4101
|
+
ref: stylePanelRef,
|
|
4102
|
+
onClick: (e) => e.stopPropagation()
|
|
3102
4103
|
},
|
|
3103
|
-
|
|
4104
|
+
/* @__PURE__ */ React15.createElement("div", { className: "ShapeHighlight__style-row" }, /* @__PURE__ */ React15.createElement("label", null, "Color"), /* @__PURE__ */ React15.createElement("div", { className: "ShapeHighlight__color-options" }, /* @__PURE__ */ React15.createElement("div", { className: "ShapeHighlight__color-presets" }, colorPresets.map((c) => /* @__PURE__ */ React15.createElement(
|
|
4105
|
+
"button",
|
|
4106
|
+
{
|
|
4107
|
+
key: c,
|
|
4108
|
+
type: "button",
|
|
4109
|
+
className: `ShapeHighlight__color-preset ${strokeColor === c ? "active" : ""}`,
|
|
4110
|
+
style: { backgroundColor: c },
|
|
4111
|
+
onClick: () => onStyleChange({ strokeColor: c }),
|
|
4112
|
+
title: c
|
|
4113
|
+
}
|
|
4114
|
+
))), /* @__PURE__ */ React15.createElement(
|
|
4115
|
+
"input",
|
|
4116
|
+
{
|
|
4117
|
+
type: "color",
|
|
4118
|
+
value: strokeColor,
|
|
4119
|
+
onChange: (e) => {
|
|
4120
|
+
onStyleChange({ strokeColor: e.target.value });
|
|
4121
|
+
}
|
|
4122
|
+
}
|
|
4123
|
+
))),
|
|
4124
|
+
/* @__PURE__ */ React15.createElement("div", { className: "ShapeHighlight__style-row" }, /* @__PURE__ */ React15.createElement("label", null, "Width"), /* @__PURE__ */ React15.createElement("div", { className: "ShapeHighlight__width-options" }, STROKE_WIDTHS2.map((w) => /* @__PURE__ */ React15.createElement(
|
|
4125
|
+
"button",
|
|
4126
|
+
{
|
|
4127
|
+
key: w.value,
|
|
4128
|
+
type: "button",
|
|
4129
|
+
className: `ShapeHighlight__width-button ${strokeWidth === w.value ? "active" : ""}`,
|
|
4130
|
+
onClick: () => onStyleChange({ strokeWidth: w.value }),
|
|
4131
|
+
title: w.label
|
|
4132
|
+
},
|
|
4133
|
+
w.label
|
|
4134
|
+
))))
|
|
3104
4135
|
)
|
|
3105
4136
|
),
|
|
3106
|
-
|
|
3107
|
-
"div",
|
|
3108
|
-
{
|
|
3109
|
-
className: "ShapeHighlight__style-panel",
|
|
3110
|
-
ref: stylePanelRef,
|
|
3111
|
-
onClick: (e) => e.stopPropagation()
|
|
3112
|
-
},
|
|
3113
|
-
/* @__PURE__ */ React15.createElement("div", { className: "ShapeHighlight__style-row" }, /* @__PURE__ */ React15.createElement("label", null, "Color"), /* @__PURE__ */ React15.createElement("div", { className: "ShapeHighlight__color-options" }, /* @__PURE__ */ React15.createElement("div", { className: "ShapeHighlight__color-presets" }, colorPresets.map((c) => /* @__PURE__ */ React15.createElement(
|
|
3114
|
-
"button",
|
|
3115
|
-
{
|
|
3116
|
-
key: c,
|
|
3117
|
-
type: "button",
|
|
3118
|
-
className: `ShapeHighlight__color-preset ${strokeColor === c ? "active" : ""}`,
|
|
3119
|
-
style: { backgroundColor: c },
|
|
3120
|
-
onClick: () => onStyleChange({ strokeColor: c }),
|
|
3121
|
-
title: c
|
|
3122
|
-
}
|
|
3123
|
-
))), /* @__PURE__ */ React15.createElement(
|
|
3124
|
-
"input",
|
|
3125
|
-
{
|
|
3126
|
-
type: "color",
|
|
3127
|
-
value: strokeColor,
|
|
3128
|
-
onChange: (e) => {
|
|
3129
|
-
onStyleChange({ strokeColor: e.target.value });
|
|
3130
|
-
}
|
|
3131
|
-
}
|
|
3132
|
-
))),
|
|
3133
|
-
/* @__PURE__ */ React15.createElement("div", { className: "ShapeHighlight__style-row" }, /* @__PURE__ */ React15.createElement("label", null, "Width"), /* @__PURE__ */ React15.createElement("div", { className: "ShapeHighlight__width-options" }, STROKE_WIDTHS2.map((w) => /* @__PURE__ */ React15.createElement(
|
|
3134
|
-
"button",
|
|
3135
|
-
{
|
|
3136
|
-
key: w.value,
|
|
3137
|
-
type: "button",
|
|
3138
|
-
className: `ShapeHighlight__width-button ${strokeWidth === w.value ? "active" : ""}`,
|
|
3139
|
-
onClick: () => onStyleChange({ strokeWidth: w.value }),
|
|
3140
|
-
title: w.label
|
|
3141
|
-
},
|
|
3142
|
-
w.label
|
|
3143
|
-
))))
|
|
3144
|
-
)
|
|
4137
|
+
configLayer
|
|
3145
4138
|
),
|
|
3146
4139
|
/* @__PURE__ */ React15.createElement(
|
|
3147
4140
|
Rnd5,
|
|
@@ -3195,455 +4188,1033 @@ var ShapeHighlight = ({
|
|
|
3195
4188
|
)
|
|
3196
4189
|
);
|
|
3197
4190
|
};
|
|
3198
|
-
|
|
3199
|
-
// src/components/PdfLoader.tsx
|
|
3200
|
-
import React16, { useEffect as useEffect12, useRef as useRef14, useState as useState11 } from "react";
|
|
3201
|
-
import { GlobalWorkerOptions, getDocument as getDocument2 } from "pdfjs-dist";
|
|
3202
|
-
var DEFAULT_BEFORE_LOAD = (progress) =>
|
|
4191
|
+
|
|
4192
|
+
// src/components/PdfLoader.tsx
|
|
4193
|
+
import React16, { useEffect as useEffect12, useRef as useRef14, useState as useState11 } from "react";
|
|
4194
|
+
import { GlobalWorkerOptions, getDocument as getDocument2 } from "pdfjs-dist";
|
|
4195
|
+
var DEFAULT_BEFORE_LOAD = (progress) => {
|
|
4196
|
+
const pct = progress && progress.total ? Math.min(100, Math.floor(progress.loaded / progress.total * 100)) : null;
|
|
4197
|
+
return /* @__PURE__ */ React16.createElement(
|
|
4198
|
+
"div",
|
|
4199
|
+
{
|
|
4200
|
+
style: {
|
|
4201
|
+
display: "flex",
|
|
4202
|
+
flexDirection: "column",
|
|
4203
|
+
alignItems: "center",
|
|
4204
|
+
justifyContent: "center",
|
|
4205
|
+
gap: 12,
|
|
4206
|
+
height: "100%",
|
|
4207
|
+
color: "currentColor",
|
|
4208
|
+
opacity: 0.7,
|
|
4209
|
+
fontSize: 13
|
|
4210
|
+
}
|
|
4211
|
+
},
|
|
4212
|
+
/* @__PURE__ */ React16.createElement(
|
|
4213
|
+
"div",
|
|
4214
|
+
{
|
|
4215
|
+
style: {
|
|
4216
|
+
width: 28,
|
|
4217
|
+
height: 28,
|
|
4218
|
+
border: "3px solid currentColor",
|
|
4219
|
+
borderTopColor: "transparent",
|
|
4220
|
+
borderRadius: "50%",
|
|
4221
|
+
animation: "pdfloader-spin 0.8s linear infinite"
|
|
4222
|
+
}
|
|
4223
|
+
}
|
|
4224
|
+
),
|
|
4225
|
+
/* @__PURE__ */ React16.createElement("div", null, pct !== null ? `Loading ${pct}%` : "Loading\u2026"),
|
|
4226
|
+
/* @__PURE__ */ React16.createElement("style", null, "@keyframes pdfloader-spin{to{transform:rotate(360deg)}}")
|
|
4227
|
+
);
|
|
4228
|
+
};
|
|
3203
4229
|
var DEFAULT_ERROR_MESSAGE = (error) => /* @__PURE__ */ React16.createElement("div", { style: { color: "black" } }, error.message);
|
|
3204
4230
|
var DEFAULT_ON_ERROR = (error) => {
|
|
3205
4231
|
throw new Error(`Error loading PDF document: ${error.message}!`);
|
|
3206
4232
|
};
|
|
3207
|
-
var DEFAULT_WORKER_SRC =
|
|
4233
|
+
var DEFAULT_WORKER_SRC = new URL(
|
|
4234
|
+
"pdfjs-dist/build/pdf.worker.min.mjs",
|
|
4235
|
+
import.meta.url
|
|
4236
|
+
).toString();
|
|
4237
|
+
var CACHE_MAX = 3;
|
|
4238
|
+
var lruTick = 0;
|
|
4239
|
+
var docCache = /* @__PURE__ */ new Map();
|
|
4240
|
+
var evictIfNeeded = () => {
|
|
4241
|
+
if (docCache.size <= CACHE_MAX) return;
|
|
4242
|
+
let victimKey;
|
|
4243
|
+
let oldest = Infinity;
|
|
4244
|
+
for (const [key, entry] of docCache) {
|
|
4245
|
+
if (entry.refs === 0 && entry.used < oldest) {
|
|
4246
|
+
oldest = entry.used;
|
|
4247
|
+
victimKey = key;
|
|
4248
|
+
}
|
|
4249
|
+
}
|
|
4250
|
+
if (victimKey !== void 0) {
|
|
4251
|
+
const victim = docCache.get(victimKey);
|
|
4252
|
+
docCache.delete(victimKey);
|
|
4253
|
+
victim.task.destroy();
|
|
4254
|
+
}
|
|
4255
|
+
};
|
|
4256
|
+
var cacheKeyOf = (document2) => {
|
|
4257
|
+
if (typeof document2 === "string") return document2;
|
|
4258
|
+
if (document2 instanceof URL) return document2.href;
|
|
4259
|
+
if (document2 && typeof document2 === "object" && !ArrayBuffer.isView(document2) && typeof document2.url === "string") {
|
|
4260
|
+
return document2.url;
|
|
4261
|
+
}
|
|
4262
|
+
return null;
|
|
4263
|
+
};
|
|
4264
|
+
var stripUndefined = (obj) => {
|
|
4265
|
+
const out = {};
|
|
4266
|
+
for (const [k, v] of Object.entries(obj))
|
|
4267
|
+
if (v !== void 0) out[k] = v;
|
|
4268
|
+
return out;
|
|
4269
|
+
};
|
|
4270
|
+
var buildSource = (document2, perf) => {
|
|
4271
|
+
const cleaned = stripUndefined(perf);
|
|
4272
|
+
if (typeof document2 === "string" || document2 instanceof URL) {
|
|
4273
|
+
return { url: document2, ...cleaned };
|
|
4274
|
+
}
|
|
4275
|
+
if (ArrayBuffer.isView(document2)) {
|
|
4276
|
+
return document2;
|
|
4277
|
+
}
|
|
4278
|
+
return { ...cleaned, ...document2 };
|
|
4279
|
+
};
|
|
3208
4280
|
var PdfLoader = ({
|
|
3209
4281
|
document: document2,
|
|
3210
4282
|
beforeLoad = DEFAULT_BEFORE_LOAD,
|
|
3211
4283
|
errorMessage = DEFAULT_ERROR_MESSAGE,
|
|
3212
4284
|
children,
|
|
3213
4285
|
onError = DEFAULT_ON_ERROR,
|
|
3214
|
-
workerSrc = DEFAULT_WORKER_SRC
|
|
4286
|
+
workerSrc = DEFAULT_WORKER_SRC,
|
|
4287
|
+
disableAutoFetch = true,
|
|
4288
|
+
disableStream,
|
|
4289
|
+
rangeChunkSize,
|
|
4290
|
+
withCredentials,
|
|
4291
|
+
httpHeaders,
|
|
4292
|
+
enableCache = true
|
|
3215
4293
|
}) => {
|
|
3216
|
-
const
|
|
3217
|
-
const pdfDocumentRef = useRef14(null);
|
|
4294
|
+
const [pdfDocument, setPdfDocument] = useState11(null);
|
|
3218
4295
|
const [error, setError] = useState11(null);
|
|
3219
4296
|
const [loadingProgress, setLoadingProgress] = useState11(null);
|
|
4297
|
+
const onErrorRef = useRef14(onError);
|
|
4298
|
+
onErrorRef.current = onError;
|
|
4299
|
+
const httpHeadersKey = httpHeaders ? JSON.stringify(httpHeaders) : "";
|
|
3220
4300
|
useEffect12(() => {
|
|
3221
4301
|
GlobalWorkerOptions.workerSrc = workerSrc;
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
4302
|
+
let cancelled = false;
|
|
4303
|
+
setPdfDocument(null);
|
|
4304
|
+
setError(null);
|
|
4305
|
+
setLoadingProgress(null);
|
|
4306
|
+
const key = enableCache ? cacheKeyOf(document2) : null;
|
|
4307
|
+
let entry;
|
|
4308
|
+
const cached = key ? docCache.get(key) : void 0;
|
|
4309
|
+
if (cached) {
|
|
4310
|
+
console.log("[PdfLoader] cache hit", key);
|
|
4311
|
+
entry = cached;
|
|
4312
|
+
entry.refs += 1;
|
|
4313
|
+
entry.used = ++lruTick;
|
|
4314
|
+
} else {
|
|
4315
|
+
console.log("[PdfLoader] loading", key ?? "(bytes)", {
|
|
4316
|
+
disableAutoFetch
|
|
4317
|
+
});
|
|
4318
|
+
const task = getDocument2(
|
|
4319
|
+
buildSource(document2, {
|
|
4320
|
+
disableAutoFetch,
|
|
4321
|
+
disableStream,
|
|
4322
|
+
rangeChunkSize,
|
|
4323
|
+
withCredentials,
|
|
4324
|
+
httpHeaders
|
|
4325
|
+
})
|
|
4326
|
+
);
|
|
4327
|
+
task.onProgress = (progress) => {
|
|
4328
|
+
if (!cancelled)
|
|
4329
|
+
setLoadingProgress(progress.loaded > progress.total ? null : progress);
|
|
4330
|
+
};
|
|
4331
|
+
entry = { task, promise: task.promise, refs: 1, used: ++lruTick };
|
|
4332
|
+
if (key) {
|
|
4333
|
+
docCache.set(key, entry);
|
|
4334
|
+
evictIfNeeded();
|
|
3232
4335
|
}
|
|
4336
|
+
}
|
|
4337
|
+
entry.promise.then((proxy) => {
|
|
4338
|
+
if (!cancelled) setPdfDocument(proxy);
|
|
4339
|
+
}).catch((err) => {
|
|
4340
|
+
if (cancelled || err.message === "Worker was destroyed") return;
|
|
4341
|
+
setError(err);
|
|
4342
|
+
onErrorRef.current(err);
|
|
3233
4343
|
}).finally(() => {
|
|
3234
|
-
setLoadingProgress(null);
|
|
4344
|
+
if (!cancelled) setLoadingProgress(null);
|
|
3235
4345
|
});
|
|
3236
4346
|
return () => {
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
4347
|
+
cancelled = true;
|
|
4348
|
+
entry.refs -= 1;
|
|
4349
|
+
if (key && docCache.has(key)) {
|
|
4350
|
+
entry.used = ++lruTick;
|
|
4351
|
+
evictIfNeeded();
|
|
4352
|
+
} else {
|
|
4353
|
+
entry.task.destroy();
|
|
3242
4354
|
}
|
|
3243
4355
|
};
|
|
3244
|
-
}, [
|
|
3245
|
-
|
|
4356
|
+
}, [
|
|
4357
|
+
document2,
|
|
4358
|
+
workerSrc,
|
|
4359
|
+
disableAutoFetch,
|
|
4360
|
+
disableStream,
|
|
4361
|
+
rangeChunkSize,
|
|
4362
|
+
withCredentials,
|
|
4363
|
+
httpHeadersKey,
|
|
4364
|
+
enableCache
|
|
4365
|
+
]);
|
|
4366
|
+
if (error) return errorMessage(error);
|
|
4367
|
+
if (pdfDocument) return children(pdfDocument);
|
|
4368
|
+
return beforeLoad(loadingProgress);
|
|
3246
4369
|
};
|
|
3247
4370
|
|
|
3248
|
-
// src/lib/
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
4371
|
+
// src/lib/extract-sentences.ts
|
|
4372
|
+
var DEFAULT_OPTIONS = {
|
|
4373
|
+
pages: "all",
|
|
4374
|
+
includePositions: false,
|
|
4375
|
+
includeSources: false,
|
|
4376
|
+
normalize: true,
|
|
4377
|
+
locale: "en",
|
|
4378
|
+
idPrefix: "",
|
|
4379
|
+
includeTextUnitTypes: ["paragraph"],
|
|
4380
|
+
readingOrder: "auto",
|
|
4381
|
+
columnDetection: "auto"
|
|
4382
|
+
};
|
|
4383
|
+
var isTextItem = (item) => {
|
|
4384
|
+
return typeof item === "object" && item !== null && "str" in item && typeof item.str === "string" && "transform" in item && Array.isArray(item.transform);
|
|
4385
|
+
};
|
|
4386
|
+
var shouldInsertSpace = (previous, next) => {
|
|
4387
|
+
if (!previous || !next) return false;
|
|
4388
|
+
if (/\s$/.test(previous) || /^\s/.test(next)) return false;
|
|
4389
|
+
if (/^[,.;:!?)]/.test(next)) return false;
|
|
4390
|
+
if (/[([{]$/.test(previous)) return false;
|
|
4391
|
+
return true;
|
|
4392
|
+
};
|
|
4393
|
+
var getLastNonWhitespace = (text) => {
|
|
4394
|
+
const match = text.match(/\S(?=\s*$)/);
|
|
4395
|
+
return match?.[0] ?? "";
|
|
4396
|
+
};
|
|
4397
|
+
var getTrimmedChars = (text) => Array.from(text.trim());
|
|
4398
|
+
var getItemTextKind = (text) => {
|
|
4399
|
+
if (!text.trim()) return "space";
|
|
4400
|
+
if (/^[,.;:!?)\]}]+$/.test(text.trim())) return "punctuation";
|
|
4401
|
+
const chars = getTrimmedChars(text);
|
|
4402
|
+
if (chars.length === 1 && /[\p{L}\p{N}]/u.test(chars[0])) {
|
|
4403
|
+
return "singleGlyph";
|
|
3261
4404
|
}
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
4405
|
+
if (/[\p{L}\p{N}]/u.test(text)) return "wordLike";
|
|
4406
|
+
return "other";
|
|
4407
|
+
};
|
|
4408
|
+
var getMedianGlyphWidth = (textItems) => {
|
|
4409
|
+
const glyphWidths = textItems.map((item) => {
|
|
4410
|
+
const textLength = Math.max(getTrimmedChars(item.text).length, 1);
|
|
4411
|
+
return item.rect.width / textLength;
|
|
4412
|
+
}).filter((width) => Number.isFinite(width) && width > 0).sort((a, b) => a - b);
|
|
4413
|
+
if (glyphWidths.length === 0) return 0;
|
|
4414
|
+
return glyphWidths[Math.floor(glyphWidths.length / 2)];
|
|
4415
|
+
};
|
|
4416
|
+
var getHorizontalGap = (previousItem, nextItem, medianGlyphWidth) => {
|
|
4417
|
+
const previousRight = previousItem.rect.left + previousItem.rect.width;
|
|
4418
|
+
const gap = nextItem.rect.left - previousRight;
|
|
4419
|
+
const zeroTolerance = Math.max(medianGlyphWidth * 0.08, 0.35);
|
|
4420
|
+
return Math.abs(gap) <= zeroTolerance ? 0 : gap;
|
|
4421
|
+
};
|
|
4422
|
+
var shouldInsertSpaceBetweenItems = (previousItem, nextItem, currentText, averageHeight, medianGlyphWidth) => {
|
|
4423
|
+
if (!previousItem || !currentText || !nextItem.text) return false;
|
|
4424
|
+
if (/\s$/.test(currentText) || /^\s/.test(nextItem.text)) return false;
|
|
4425
|
+
const previousChar = getLastNonWhitespace(currentText);
|
|
4426
|
+
const nextChar = Array.from(nextItem.text.trim())[0] ?? "";
|
|
4427
|
+
const previousKind = getItemTextKind(previousItem.text);
|
|
4428
|
+
const nextKind = getItemTextKind(nextItem.text);
|
|
4429
|
+
if (!nextChar) return false;
|
|
4430
|
+
if (nextKind === "punctuation" || /^[,.;:!?)]/.test(nextChar)) return false;
|
|
4431
|
+
if (/[([{]$/.test(previousChar)) return false;
|
|
4432
|
+
const gap = getHorizontalGap(previousItem, nextItem, medianGlyphWidth);
|
|
4433
|
+
if (gap < 0) return false;
|
|
4434
|
+
const glyphWidth = Math.max(
|
|
4435
|
+
medianGlyphWidth,
|
|
4436
|
+
averageHeight * 0.18,
|
|
4437
|
+
1
|
|
4438
|
+
);
|
|
4439
|
+
const singleGlyphPair = previousKind === "singleGlyph" && nextKind === "singleGlyph";
|
|
4440
|
+
if (singleGlyphPair) {
|
|
4441
|
+
return gap > Math.max(glyphWidth * 0.35, 1);
|
|
3270
4442
|
}
|
|
3271
|
-
if (
|
|
3272
|
-
return
|
|
3273
|
-
r: parseInt(hex.slice(0, 2), 16) / 255,
|
|
3274
|
-
g: parseInt(hex.slice(2, 4), 16) / 255,
|
|
3275
|
-
b: parseInt(hex.slice(4, 6), 16) / 255,
|
|
3276
|
-
a: 1
|
|
3277
|
-
};
|
|
4443
|
+
if ((previousKind === "wordLike" || previousKind === "singleGlyph") && (nextKind === "wordLike" || nextKind === "singleGlyph")) {
|
|
4444
|
+
return gap > Math.max(glyphWidth * 0.08, 0.35);
|
|
3278
4445
|
}
|
|
3279
|
-
return
|
|
3280
|
-
}
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
4446
|
+
return shouldInsertSpace(currentText, nextItem.text);
|
|
4447
|
+
};
|
|
4448
|
+
var appendTextItem = (currentText, item, previousItem, averageHeight, medianGlyphWidth) => {
|
|
4449
|
+
return `${currentText}${shouldInsertSpaceBetweenItems(
|
|
4450
|
+
previousItem,
|
|
4451
|
+
item,
|
|
4452
|
+
currentText,
|
|
4453
|
+
averageHeight,
|
|
4454
|
+
medianGlyphWidth
|
|
4455
|
+
) ? " " : ""}${item.text}`;
|
|
4456
|
+
};
|
|
4457
|
+
var getAverageItemHeight = (textItems) => {
|
|
4458
|
+
const heights = textItems.map((item) => item.rect.height).filter((height) => height > 0).sort((a, b) => a - b);
|
|
4459
|
+
if (heights.length === 0) return 0;
|
|
4460
|
+
return heights[Math.floor(heights.length / 2)];
|
|
4461
|
+
};
|
|
4462
|
+
var areTextItemsOnSameLine = (previous, next, averageHeight) => {
|
|
4463
|
+
const previousMiddle = previous.rect.top + previous.rect.height / 2;
|
|
4464
|
+
const nextMiddle = next.rect.top + next.rect.height / 2;
|
|
4465
|
+
const tolerance = Math.max(averageHeight * 0.45, 2);
|
|
4466
|
+
return Math.abs(previousMiddle - nextMiddle) <= tolerance;
|
|
4467
|
+
};
|
|
4468
|
+
var getTextItemRect = (item, viewport, pageNumber) => {
|
|
4469
|
+
const [, , , , x, y] = item.transform;
|
|
4470
|
+
const width = Math.abs(item.width ?? 0);
|
|
4471
|
+
const height = Math.abs(item.height ?? 0);
|
|
4472
|
+
const [x1, y1, x2, y2] = viewport.convertToViewportRectangle([
|
|
4473
|
+
x,
|
|
4474
|
+
y,
|
|
4475
|
+
x + width,
|
|
4476
|
+
y + height
|
|
4477
|
+
]);
|
|
4478
|
+
return {
|
|
4479
|
+
left: Math.min(x1, x2),
|
|
4480
|
+
top: Math.min(y1, y2),
|
|
4481
|
+
width: Math.abs(x2 - x1),
|
|
4482
|
+
height: Math.abs(y2 - y1),
|
|
4483
|
+
pageNumber
|
|
4484
|
+
};
|
|
4485
|
+
};
|
|
4486
|
+
var resolvePageNumbers = (pdfDocument, pages) => {
|
|
4487
|
+
if (pages === "all" || pages === void 0) {
|
|
4488
|
+
return Array.from({ length: pdfDocument.numPages }, (_, index) => index + 1);
|
|
3298
4489
|
}
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
const
|
|
3305
|
-
const
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
4490
|
+
return [...new Set(pages)].filter((pageNumber) => pageNumber >= 1 && pageNumber <= pdfDocument.numPages).sort((a, b) => a - b);
|
|
4491
|
+
};
|
|
4492
|
+
var buildPageTextStream = (textItems) => {
|
|
4493
|
+
let text = "";
|
|
4494
|
+
const charItemIndexes = [];
|
|
4495
|
+
const averageHeight = getAverageItemHeight(textItems);
|
|
4496
|
+
const medianGlyphWidth = getMedianGlyphWidth(textItems);
|
|
4497
|
+
let previousItem;
|
|
4498
|
+
textItems.forEach((item) => {
|
|
4499
|
+
if (!item.text) return;
|
|
4500
|
+
if (shouldInsertSpaceBetweenItems(
|
|
4501
|
+
previousItem,
|
|
4502
|
+
item,
|
|
4503
|
+
text,
|
|
4504
|
+
averageHeight,
|
|
4505
|
+
medianGlyphWidth
|
|
4506
|
+
)) {
|
|
4507
|
+
text += " ";
|
|
4508
|
+
charItemIndexes.push(null);
|
|
3310
4509
|
}
|
|
3311
|
-
const
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
const testLine = currentLine ? `${currentLine} ${word}` : word;
|
|
3315
|
-
const testWidth = font.widthOfTextAtSize(testLine, fontSize);
|
|
3316
|
-
if (testWidth <= maxWidth) {
|
|
3317
|
-
currentLine = testLine;
|
|
3318
|
-
} else {
|
|
3319
|
-
if (currentLine) {
|
|
3320
|
-
lines.push(currentLine);
|
|
3321
|
-
currentLine = "";
|
|
3322
|
-
}
|
|
3323
|
-
if (font.widthOfTextAtSize(word, fontSize) > maxWidth) {
|
|
3324
|
-
let remaining = word;
|
|
3325
|
-
while (remaining.length > 0) {
|
|
3326
|
-
let charCount = 1;
|
|
3327
|
-
while (charCount < remaining.length && font.widthOfTextAtSize(remaining.substring(0, charCount + 1), fontSize) <= maxWidth) {
|
|
3328
|
-
charCount++;
|
|
3329
|
-
}
|
|
3330
|
-
const chunk = remaining.substring(0, charCount);
|
|
3331
|
-
remaining = remaining.substring(charCount);
|
|
3332
|
-
if (remaining.length > 0) {
|
|
3333
|
-
lines.push(chunk);
|
|
3334
|
-
} else {
|
|
3335
|
-
currentLine = chunk;
|
|
3336
|
-
}
|
|
3337
|
-
}
|
|
3338
|
-
} else {
|
|
3339
|
-
currentLine = word;
|
|
3340
|
-
}
|
|
3341
|
-
}
|
|
4510
|
+
for (const char of item.text) {
|
|
4511
|
+
text += char;
|
|
4512
|
+
charItemIndexes.push(item.index);
|
|
3342
4513
|
}
|
|
3343
|
-
|
|
3344
|
-
}
|
|
3345
|
-
return
|
|
3346
|
-
}
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
4514
|
+
previousItem = item;
|
|
4515
|
+
});
|
|
4516
|
+
return { text, charItemIndexes };
|
|
4517
|
+
};
|
|
4518
|
+
var normalizePdfLigatures = (text) => {
|
|
4519
|
+
return text.replace(/ffi/g, "ffi").replace(/ffl/g, "ffl").replace(/fi/g, "fi").replace(/fl/g, "fl").replace(/ff/g, "ff");
|
|
4520
|
+
};
|
|
4521
|
+
var cleanupPdfHyphenSpacing = (text) => {
|
|
4522
|
+
return text.replace(
|
|
4523
|
+
/\b([\p{L}\p{N}]{2,})\s+-\s+([\p{L}\p{N}]{2,})\b/gu,
|
|
4524
|
+
"$1-$2"
|
|
4525
|
+
);
|
|
4526
|
+
};
|
|
4527
|
+
var normalizePdfSentenceText = (text) => {
|
|
4528
|
+
return cleanupPdfHyphenSpacing(normalizePdfLigatures(text)).replace(/(\p{L}+)-\s+(\p{L}+)/gu, "$1-$2").replace(/\[\s*([^\]]*?)\s*\]/g, (_, citation) => {
|
|
4529
|
+
const normalizedCitation = citation.replace(/\s*,\s*/g, ", ").replace(/\s+/g, " ").trim();
|
|
4530
|
+
return `[${normalizedCitation}]`;
|
|
4531
|
+
}).replace(/\s+/g, " ").trim();
|
|
4532
|
+
};
|
|
4533
|
+
var splitPdfSentences = (text, locale = DEFAULT_OPTIONS.locale) => {
|
|
4534
|
+
const segmenterConstructor = Intl.Segmenter;
|
|
4535
|
+
if (segmenterConstructor) {
|
|
4536
|
+
const segmenter = new segmenterConstructor(locale, {
|
|
4537
|
+
granularity: "sentence"
|
|
4538
|
+
});
|
|
4539
|
+
return Array.from(segmenter.segment(text)).map((segment) => ({
|
|
4540
|
+
text: segment.segment.trim(),
|
|
4541
|
+
start: segment.index + segment.segment.search(/\S/),
|
|
4542
|
+
end: segment.index + segment.segment.trimEnd().length
|
|
4543
|
+
})).filter((sentence) => sentence.text.length > 0 && sentence.start >= 0);
|
|
3353
4544
|
}
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
const
|
|
3363
|
-
if (
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
const lineThickness = Math.max(1, height * 0.1);
|
|
3374
|
-
page.drawRectangle({
|
|
3375
|
-
x,
|
|
3376
|
-
y,
|
|
3377
|
-
width,
|
|
3378
|
-
height: lineThickness,
|
|
3379
|
-
color: rgb(color.r, color.g, color.b),
|
|
3380
|
-
opacity: color.a
|
|
3381
|
-
});
|
|
3382
|
-
} else if (highlightStyle === "strikethrough") {
|
|
3383
|
-
const lineThickness = Math.max(1, height * 0.1);
|
|
3384
|
-
const lineY = y + height / 2 - lineThickness / 2;
|
|
3385
|
-
page.drawRectangle({
|
|
3386
|
-
x,
|
|
3387
|
-
y: lineY,
|
|
3388
|
-
width,
|
|
3389
|
-
height: lineThickness,
|
|
3390
|
-
color: rgb(color.r, color.g, color.b),
|
|
3391
|
-
opacity: color.a
|
|
4545
|
+
const ranges = [];
|
|
4546
|
+
let start = 0;
|
|
4547
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
4548
|
+
const char = text[index];
|
|
4549
|
+
if (!/[.!?]/.test(char)) continue;
|
|
4550
|
+
const nextText = text.slice(index + 1);
|
|
4551
|
+
const nextNonSpace = nextText.match(/\S/);
|
|
4552
|
+
const nextChar = nextNonSpace?.[0] ?? "";
|
|
4553
|
+
const shouldSplit = nextChar === "" || /[A-Z([{"']/.test(nextChar);
|
|
4554
|
+
if (!shouldSplit) continue;
|
|
4555
|
+
const rawSentence = text.slice(start, index + 1);
|
|
4556
|
+
const leadingWhitespace2 = rawSentence.search(/\S/);
|
|
4557
|
+
const sentenceStart = start + Math.max(leadingWhitespace2, 0);
|
|
4558
|
+
const sentenceText = rawSentence.trim();
|
|
4559
|
+
if (sentenceText) {
|
|
4560
|
+
ranges.push({
|
|
4561
|
+
text: sentenceText,
|
|
4562
|
+
start: sentenceStart,
|
|
4563
|
+
end: start + rawSentence.trimEnd().length
|
|
3392
4564
|
});
|
|
3393
4565
|
}
|
|
4566
|
+
start = index + 1;
|
|
3394
4567
|
}
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
const
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
4568
|
+
const finalRawSentence = text.slice(start);
|
|
4569
|
+
const leadingWhitespace = finalRawSentence.search(/\S/);
|
|
4570
|
+
const finalText = finalRawSentence.trim();
|
|
4571
|
+
if (finalText) {
|
|
4572
|
+
ranges.push({
|
|
4573
|
+
text: finalText,
|
|
4574
|
+
start: start + Math.max(leadingWhitespace, 0),
|
|
4575
|
+
end: start + finalRawSentence.trimEnd().length
|
|
4576
|
+
});
|
|
4577
|
+
}
|
|
4578
|
+
return ranges;
|
|
4579
|
+
};
|
|
4580
|
+
var getSentenceTextItemIndexes = (charItemIndexes, start, end) => {
|
|
4581
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
4582
|
+
for (let index = start; index < end; index += 1) {
|
|
4583
|
+
const itemIndex = charItemIndexes[index];
|
|
4584
|
+
if (itemIndex !== null && itemIndex !== void 0) {
|
|
4585
|
+
indexes.add(itemIndex);
|
|
4586
|
+
}
|
|
4587
|
+
}
|
|
4588
|
+
return Array.from(indexes).sort((a, b) => a - b);
|
|
4589
|
+
};
|
|
4590
|
+
var getScaledPosition = (textItems, textItemIndexes, page) => {
|
|
4591
|
+
const indexSet = new Set(textItemIndexes);
|
|
4592
|
+
const rects = textItems.filter((item) => indexSet.has(item.index)).map((item) => item.rect).filter((rect) => rect.width > 0 && rect.height > 0);
|
|
4593
|
+
if (rects.length === 0) return void 0;
|
|
4594
|
+
const boundingRect = get_bounding_rect_default(rects);
|
|
4595
|
+
return {
|
|
4596
|
+
boundingRect: viewportToScaled(boundingRect, page),
|
|
4597
|
+
rects: rects.map((rect) => viewportToScaled(rect, page))
|
|
4598
|
+
};
|
|
4599
|
+
};
|
|
4600
|
+
var getTextItemsPosition = (textItems, textItemIndexes, page, includePositions) => {
|
|
4601
|
+
return includePositions ? getScaledPosition(textItems, textItemIndexes, page) : void 0;
|
|
4602
|
+
};
|
|
4603
|
+
var getTextItemsBoundingRect = (textItems) => {
|
|
4604
|
+
const rects = textItems.map((item) => item.rect).filter((rect) => rect.width > 0 && rect.height > 0);
|
|
4605
|
+
return rects.length > 0 ? get_bounding_rect_default(rects) : void 0;
|
|
4606
|
+
};
|
|
4607
|
+
var getOrderedTextItems = (textItems, readingOrder) => {
|
|
4608
|
+
if (readingOrder === "document") return [...textItems];
|
|
4609
|
+
const averageHeight = getAverageItemHeight(textItems);
|
|
4610
|
+
return [...textItems].sort((a, b) => {
|
|
4611
|
+
const topDelta = a.rect.top - b.rect.top;
|
|
4612
|
+
if (Math.abs(topDelta) > Math.max(averageHeight * 0.45, 2)) {
|
|
4613
|
+
return topDelta;
|
|
4614
|
+
}
|
|
4615
|
+
return a.rect.left - b.rect.left;
|
|
3410
4616
|
});
|
|
3411
|
-
}
|
|
3412
|
-
|
|
3413
|
-
const
|
|
3414
|
-
const
|
|
3415
|
-
|
|
3416
|
-
)
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
console.log("Freetext export:", {
|
|
3426
|
-
storedFontSize,
|
|
3427
|
-
yRatio,
|
|
3428
|
-
fontSize,
|
|
3429
|
-
boxDimensions: { x, y, width, height },
|
|
3430
|
-
text: text.substring(0, 50)
|
|
4617
|
+
};
|
|
4618
|
+
var groupTextItemsIntoLines = (textItems) => {
|
|
4619
|
+
const averageHeight = getAverageItemHeight(textItems);
|
|
4620
|
+
const sortedItems = getOrderedTextItems(textItems, "position");
|
|
4621
|
+
const lines = [];
|
|
4622
|
+
sortedItems.forEach((item) => {
|
|
4623
|
+
const currentLine = lines[lines.length - 1];
|
|
4624
|
+
const previousItem = currentLine?.[currentLine.length - 1];
|
|
4625
|
+
if (currentLine && previousItem && areTextItemsOnSameLine(previousItem, item, averageHeight)) {
|
|
4626
|
+
currentLine.push(item);
|
|
4627
|
+
currentLine.sort((a, b) => a.rect.left - b.rect.left);
|
|
4628
|
+
return;
|
|
4629
|
+
}
|
|
4630
|
+
lines.push([item]);
|
|
3431
4631
|
});
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
height,
|
|
3440
|
-
color: rgb(bgColor.r, bgColor.g, bgColor.b),
|
|
3441
|
-
opacity: bgColor.a
|
|
3442
|
-
});
|
|
4632
|
+
return lines;
|
|
4633
|
+
};
|
|
4634
|
+
var isLikelyBodyTextItem = (item, page) => {
|
|
4635
|
+
const text = item.text.trim();
|
|
4636
|
+
if (!text) return false;
|
|
4637
|
+
if (item.rect.top < page.height * 0.12 && page.pageNumber === 1 || item.rect.top > page.height * 0.88) {
|
|
4638
|
+
return false;
|
|
3443
4639
|
}
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
4640
|
+
if (isLikelyFootnoteOrReference(text)) return false;
|
|
4641
|
+
return true;
|
|
4642
|
+
};
|
|
4643
|
+
var getColumnRanges = (textItems, page) => {
|
|
4644
|
+
if (textItems.length < 24) return void 0;
|
|
4645
|
+
const minLeft = Math.min(...textItems.map((item) => item.rect.left));
|
|
4646
|
+
const maxRight = Math.max(
|
|
4647
|
+
...textItems.map((item) => item.rect.left + item.rect.width)
|
|
4648
|
+
);
|
|
4649
|
+
const pageSpan = maxRight - minLeft;
|
|
4650
|
+
const binCount = 80;
|
|
4651
|
+
const bins = Array.from({ length: binCount }, () => 0);
|
|
4652
|
+
textItems.forEach((item) => {
|
|
4653
|
+
const left = item.rect.left;
|
|
4654
|
+
const right = item.rect.left + item.rect.width;
|
|
4655
|
+
const startBin = Math.max(
|
|
4656
|
+
0,
|
|
4657
|
+
Math.floor((left - minLeft) / pageSpan * binCount)
|
|
4658
|
+
);
|
|
4659
|
+
const endBin = Math.min(
|
|
4660
|
+
binCount - 1,
|
|
4661
|
+
Math.ceil((right - minLeft) / pageSpan * binCount)
|
|
4662
|
+
);
|
|
4663
|
+
for (let index = startBin; index <= endBin; index += 1) {
|
|
4664
|
+
bins[index] += 1;
|
|
4665
|
+
}
|
|
4666
|
+
});
|
|
4667
|
+
const centerStart = Math.floor(binCount * 0.35);
|
|
4668
|
+
const centerEnd = Math.ceil(binCount * 0.65);
|
|
4669
|
+
let bestGapStart = -1;
|
|
4670
|
+
let bestGapEnd = -1;
|
|
4671
|
+
let currentGapStart = -1;
|
|
4672
|
+
for (let index = centerStart; index <= centerEnd; index += 1) {
|
|
4673
|
+
if (bins[index] === 0) {
|
|
4674
|
+
if (currentGapStart === -1) currentGapStart = index;
|
|
4675
|
+
continue;
|
|
4676
|
+
}
|
|
4677
|
+
if (currentGapStart !== -1 && index - currentGapStart > bestGapEnd - bestGapStart) {
|
|
4678
|
+
bestGapStart = currentGapStart;
|
|
4679
|
+
bestGapEnd = index;
|
|
3462
4680
|
}
|
|
4681
|
+
currentGapStart = -1;
|
|
3463
4682
|
}
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
const
|
|
3469
|
-
|
|
4683
|
+
if (currentGapStart !== -1 && centerEnd + 1 - currentGapStart > bestGapEnd - bestGapStart) {
|
|
4684
|
+
bestGapStart = currentGapStart;
|
|
4685
|
+
bestGapEnd = centerEnd + 1;
|
|
4686
|
+
}
|
|
4687
|
+
const gapBins = bestGapEnd - bestGapStart;
|
|
4688
|
+
const gapWidth = gapBins / binCount * pageSpan;
|
|
4689
|
+
const minGapWidth = Math.max(page.width * 0.035, 14);
|
|
4690
|
+
if (bestGapStart < 0 || gapWidth < minGapWidth) return void 0;
|
|
4691
|
+
const gapLeft = minLeft + bestGapStart / binCount * pageSpan;
|
|
4692
|
+
const gapRight = minLeft + bestGapEnd / binCount * pageSpan;
|
|
4693
|
+
const splitX = (gapLeft + gapRight) / 2;
|
|
4694
|
+
const leftItems = textItems.filter(
|
|
4695
|
+
(item) => item.rect.left + item.rect.width / 2 < splitX
|
|
4696
|
+
);
|
|
4697
|
+
const rightItems = textItems.filter(
|
|
4698
|
+
(item) => item.rect.left + item.rect.width / 2 >= splitX
|
|
4699
|
+
);
|
|
4700
|
+
if (leftItems.length < 10 || rightItems.length < 10) return void 0;
|
|
4701
|
+
const leftRange = {
|
|
4702
|
+
left: Math.min(...leftItems.map((item) => item.rect.left)),
|
|
4703
|
+
right: Math.max(...leftItems.map((item) => item.rect.left + item.rect.width))
|
|
4704
|
+
};
|
|
4705
|
+
const rightRange = {
|
|
4706
|
+
left: Math.min(...rightItems.map((item) => item.rect.left)),
|
|
4707
|
+
right: Math.max(...rightItems.map((item) => item.rect.left + item.rect.width))
|
|
4708
|
+
};
|
|
4709
|
+
const leftHeight = getAverageItemHeight(leftItems);
|
|
4710
|
+
const rightHeight = getAverageItemHeight(rightItems);
|
|
4711
|
+
if (leftHeight > 0 && rightHeight > 0 && Math.max(leftHeight, rightHeight) / Math.min(leftHeight, rightHeight) > 1.45) {
|
|
4712
|
+
return void 0;
|
|
4713
|
+
}
|
|
4714
|
+
return [leftRange, rightRange];
|
|
4715
|
+
};
|
|
4716
|
+
var assignColumnsToPage = (page, columnDetection) => {
|
|
4717
|
+
if (columnDetection === "none") {
|
|
3470
4718
|
return {
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
4719
|
+
...page,
|
|
4720
|
+
textItems: page.textItems.map((item) => ({ ...item, columnIndex: 0 })),
|
|
4721
|
+
columns: [
|
|
4722
|
+
{
|
|
4723
|
+
index: 0,
|
|
4724
|
+
left: 0,
|
|
4725
|
+
right: page.width,
|
|
4726
|
+
width: page.width,
|
|
4727
|
+
textItemIndexes: page.textItems.map((item) => item.index)
|
|
4728
|
+
}
|
|
4729
|
+
]
|
|
3475
4730
|
};
|
|
3476
|
-
}
|
|
4731
|
+
}
|
|
4732
|
+
const bodyItems = page.textItems.filter(
|
|
4733
|
+
(item) => isLikelyBodyTextItem(item, page)
|
|
4734
|
+
);
|
|
4735
|
+
const ranges = getColumnRanges(bodyItems, page);
|
|
4736
|
+
if (!ranges) {
|
|
3477
4737
|
return {
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
4738
|
+
...page,
|
|
4739
|
+
textItems: page.textItems.map((item) => ({ ...item, columnIndex: 0 })),
|
|
4740
|
+
columns: [
|
|
4741
|
+
{
|
|
4742
|
+
index: 0,
|
|
4743
|
+
left: 0,
|
|
4744
|
+
right: page.width,
|
|
4745
|
+
width: page.width,
|
|
4746
|
+
textItemIndexes: page.textItems.map((item) => item.index)
|
|
4747
|
+
}
|
|
4748
|
+
]
|
|
3482
4749
|
};
|
|
3483
|
-
}
|
|
4750
|
+
}
|
|
4751
|
+
const splitX = (ranges[0].right + ranges[1].left) / 2;
|
|
4752
|
+
const textItems = page.textItems.map((item) => {
|
|
4753
|
+
const centerX = item.rect.left + item.rect.width / 2;
|
|
3484
4754
|
return {
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
width: height,
|
|
3488
|
-
height: width
|
|
4755
|
+
...item,
|
|
4756
|
+
columnIndex: centerX < splitX ? 0 : 1
|
|
3489
4757
|
};
|
|
3490
|
-
}
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
4758
|
+
});
|
|
4759
|
+
const columns = ranges.map((range, index) => ({
|
|
4760
|
+
index,
|
|
4761
|
+
left: range.left,
|
|
4762
|
+
right: range.right,
|
|
4763
|
+
width: range.right - range.left,
|
|
4764
|
+
textItemIndexes: textItems.filter((item) => item.columnIndex === index).map((item) => item.index)
|
|
4765
|
+
}));
|
|
4766
|
+
return { ...page, textItems, columns };
|
|
4767
|
+
};
|
|
4768
|
+
var isFullWidthBlock = (block, page, detectedColumnCount) => {
|
|
4769
|
+
if (detectedColumnCount < 2) return false;
|
|
4770
|
+
const textItems = block.flat();
|
|
4771
|
+
const boundingRect = getTextItemsBoundingRect(textItems);
|
|
4772
|
+
if (!boundingRect) return false;
|
|
4773
|
+
return boundingRect.width > page.width * 0.72;
|
|
4774
|
+
};
|
|
4775
|
+
var buildBlocksForItems = (textItems, page) => {
|
|
4776
|
+
const lines = groupTextItemsIntoLines(textItems);
|
|
4777
|
+
const averageHeight = getAverageItemHeight(textItems);
|
|
4778
|
+
const blocks = [];
|
|
4779
|
+
lines.forEach((line) => {
|
|
4780
|
+
const currentBlock = blocks[blocks.length - 1];
|
|
4781
|
+
const previousLine = currentBlock?.[currentBlock.length - 1];
|
|
4782
|
+
if (currentBlock && previousLine && !shouldStartNewBlock(previousLine, line, page, averageHeight)) {
|
|
4783
|
+
currentBlock.push(line);
|
|
4784
|
+
return;
|
|
4785
|
+
}
|
|
4786
|
+
blocks.push([line]);
|
|
4787
|
+
});
|
|
4788
|
+
return blocks;
|
|
4789
|
+
};
|
|
4790
|
+
var compareBlocksByPosition = (left, right) => {
|
|
4791
|
+
const leftRect = getTextItemsBoundingRect(left.block.flat());
|
|
4792
|
+
const rightRect = getTextItemsBoundingRect(right.block.flat());
|
|
4793
|
+
return (leftRect?.top ?? 0) - (rightRect?.top ?? 0);
|
|
4794
|
+
};
|
|
4795
|
+
var compareBlocksByColumnReadingOrder = (left, right) => {
|
|
4796
|
+
const columnDelta = (left.columnIndex ?? 0) - (right.columnIndex ?? 0);
|
|
4797
|
+
if (columnDelta !== 0) return columnDelta;
|
|
4798
|
+
return compareBlocksByPosition(left, right);
|
|
4799
|
+
};
|
|
4800
|
+
var buildTextUnit = ({
|
|
4801
|
+
block,
|
|
4802
|
+
columnIndex,
|
|
4803
|
+
indexInPage,
|
|
4804
|
+
includePositions,
|
|
4805
|
+
idPrefix,
|
|
4806
|
+
page,
|
|
4807
|
+
shouldNormalize
|
|
4808
|
+
}) => {
|
|
4809
|
+
const textItems = block.flat();
|
|
4810
|
+
const rawText = block.map(getLineText).join(" ");
|
|
4811
|
+
const textItemIndexes = textItems.map((item) => item.index);
|
|
4812
|
+
const type = classifyTextUnit(rawText, textItemIndexes, page);
|
|
4813
|
+
const position = getTextItemsPosition(
|
|
4814
|
+
page.textItems,
|
|
4815
|
+
textItemIndexes,
|
|
4816
|
+
page,
|
|
4817
|
+
includePositions
|
|
4818
|
+
);
|
|
4819
|
+
return {
|
|
4820
|
+
id: `${idPrefix}p${page.pageNumber}-u${indexInPage}`,
|
|
4821
|
+
type,
|
|
4822
|
+
text: shouldNormalize ? normalizePdfSentenceText(rawText) : rawText.trim(),
|
|
4823
|
+
rawText: rawText.trim(),
|
|
4824
|
+
pageNumber: page.pageNumber,
|
|
4825
|
+
indexInPage,
|
|
4826
|
+
...columnIndex !== void 0 ? { columnIndex } : {},
|
|
4827
|
+
...position ? { position } : {},
|
|
4828
|
+
source: {
|
|
4829
|
+
textItemIndexes
|
|
4830
|
+
}
|
|
4831
|
+
};
|
|
4832
|
+
};
|
|
4833
|
+
var getLineText = (line) => {
|
|
4834
|
+
const averageHeight = getAverageItemHeight(line);
|
|
4835
|
+
const medianGlyphWidth = getMedianGlyphWidth(line);
|
|
4836
|
+
return line.reduce((text, item, index) => {
|
|
4837
|
+
return appendTextItem(
|
|
4838
|
+
text,
|
|
4839
|
+
item,
|
|
4840
|
+
line[index - 1],
|
|
4841
|
+
averageHeight,
|
|
4842
|
+
medianGlyphWidth
|
|
3509
4843
|
);
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
4844
|
+
}, "");
|
|
4845
|
+
};
|
|
4846
|
+
var getLineRect = (line) => get_bounding_rect_default(line.map((item) => item.rect));
|
|
4847
|
+
var shouldStartNewBlock = (previousLine, nextLine, page, averageHeight) => {
|
|
4848
|
+
const previousRect = getLineRect(previousLine);
|
|
4849
|
+
const nextRect = getLineRect(nextLine);
|
|
4850
|
+
const verticalGap = nextRect.top - (previousRect.top + previousRect.height);
|
|
4851
|
+
const leftDelta = Math.abs(nextRect.left - previousRect.left);
|
|
4852
|
+
const previousText = getLineText(previousLine).trim();
|
|
4853
|
+
const nextText = getLineText(nextLine).trim();
|
|
4854
|
+
if (verticalGap > Math.max(averageHeight * 1.15, 7)) return true;
|
|
4855
|
+
if (leftDelta > page.width * 0.18) return true;
|
|
4856
|
+
if (isLikelyStandaloneBlock(previousText) || isLikelyStandaloneBlock(nextText)) {
|
|
4857
|
+
return true;
|
|
3523
4858
|
}
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
const
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
4859
|
+
return false;
|
|
4860
|
+
};
|
|
4861
|
+
var isLikelyStandaloneBlock = (text) => {
|
|
4862
|
+
const normalizedText = normalizePdfSentenceText(text);
|
|
4863
|
+
if (!normalizedText) return false;
|
|
4864
|
+
if (isLikelySectionHeading(normalizedText)) return true;
|
|
4865
|
+
if (isLikelyFootnoteOrReference(normalizedText)) return true;
|
|
4866
|
+
return false;
|
|
4867
|
+
};
|
|
4868
|
+
var isLikelySectionHeading = (text) => {
|
|
4869
|
+
if (text.length > 80) return false;
|
|
4870
|
+
if (/^\d+(\.\d+)*\.?\s+[A-Z]/.test(text)) return true;
|
|
4871
|
+
const letters = text.replace(/[^A-Za-z]/g, "");
|
|
4872
|
+
if (letters.length < 3) return false;
|
|
4873
|
+
const uppercaseLetters = text.replace(/[^A-Z]/g, "");
|
|
4874
|
+
return uppercaseLetters.length / letters.length > 0.75;
|
|
4875
|
+
};
|
|
4876
|
+
var isLikelyFootnoteOrReference = (text) => {
|
|
4877
|
+
return /^\d+\s*https?:\/\//i.test(text) || /^\[\d+\]/.test(text) || /https?:\/\/|doi\.org|arxiv\.org/i.test(text);
|
|
4878
|
+
};
|
|
4879
|
+
var isLikelyAuthor = (text) => {
|
|
4880
|
+
return text.length <= 80 && !/[.!?]$/.test(text) && /^[A-Z][A-Za-z'.-]+(?:\s+[A-Z][A-Za-z'.-]+){1,4}$/.test(text);
|
|
4881
|
+
};
|
|
4882
|
+
var isLikelyAffiliation = (text) => {
|
|
4883
|
+
return text.length <= 160 && /university|institute|department|school|college|faculty|laboratory|germany|usa|china|france|italy|spain|canada|japan|korea/i.test(
|
|
4884
|
+
text
|
|
3533
4885
|
);
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
4886
|
+
};
|
|
4887
|
+
var classifyTextUnit = (rawText, textItemIndexes, page) => {
|
|
4888
|
+
const text = normalizePdfSentenceText(rawText);
|
|
4889
|
+
const textItems = page.textItems.filter(
|
|
4890
|
+
(item) => textItemIndexes.includes(item.index)
|
|
4891
|
+
);
|
|
4892
|
+
const rects = textItems.map((item) => item.rect);
|
|
4893
|
+
const boundingRect = rects.length > 0 ? get_bounding_rect_default(rects) : void 0;
|
|
4894
|
+
const averagePageHeight = getAverageItemHeight(page.textItems);
|
|
4895
|
+
const averageUnitHeight = getAverageItemHeight(textItems);
|
|
4896
|
+
const isTopOfFirstPage = page.pageNumber === 1 && (boundingRect?.top ?? page.height) < page.height * 0.2;
|
|
4897
|
+
const isBottomOfPage = (boundingRect?.top ?? 0) > page.height * 0.78;
|
|
4898
|
+
const isLargeText = averagePageHeight > 0 && averageUnitHeight > averagePageHeight * 1.25;
|
|
4899
|
+
const isCentered = boundingRect !== void 0 && Math.abs(boundingRect.left + boundingRect.width / 2 - page.width / 2) < page.width * 0.16;
|
|
4900
|
+
if (isLikelyFootnoteOrReference(text) || isBottomOfPage && /^\d+\s/.test(text)) {
|
|
4901
|
+
return text.includes("://") || /^\d+\s*https?:\/\//i.test(text) ? "footnote" : "reference";
|
|
4902
|
+
}
|
|
4903
|
+
if (isLikelySectionHeading(text)) return "heading";
|
|
4904
|
+
if (isTopOfFirstPage && isLargeText && isCentered) return "title";
|
|
4905
|
+
if (isTopOfFirstPage && isLikelyAuthor(text)) return "author";
|
|
4906
|
+
if (isTopOfFirstPage && isLikelyAffiliation(text)) return "affiliation";
|
|
4907
|
+
return "paragraph";
|
|
4908
|
+
};
|
|
4909
|
+
var extractTextUnitsFromPages = (pages, options = {}) => {
|
|
4910
|
+
const includePositions = options.includePositions ?? DEFAULT_OPTIONS.includePositions;
|
|
4911
|
+
const includeSources = options.includeSources ?? DEFAULT_OPTIONS.includeSources;
|
|
4912
|
+
const shouldNormalize = options.normalize ?? DEFAULT_OPTIONS.normalize;
|
|
4913
|
+
const idPrefix = options.idPrefix ?? DEFAULT_OPTIONS.idPrefix;
|
|
4914
|
+
const readingOrder = options.readingOrder ?? DEFAULT_OPTIONS.readingOrder;
|
|
4915
|
+
const columnDetection = options.columnDetection ?? DEFAULT_OPTIONS.columnDetection;
|
|
4916
|
+
const units = [];
|
|
4917
|
+
pages.forEach((page) => {
|
|
4918
|
+
const pageWithColumns = assignColumnsToPage(page, columnDetection);
|
|
4919
|
+
const detectedColumnCount = pageWithColumns.columns?.length ?? 1;
|
|
4920
|
+
const columnIndexes = detectedColumnCount > 1 && readingOrder !== "document" ? pageWithColumns.columns?.map((column) => column.index) ?? [0] : [0];
|
|
4921
|
+
const blocks = [];
|
|
4922
|
+
if (readingOrder === "document") {
|
|
4923
|
+
blocks.push(
|
|
4924
|
+
...buildBlocksForItems(pageWithColumns.textItems, pageWithColumns).map(
|
|
4925
|
+
(block) => ({ block })
|
|
4926
|
+
)
|
|
4927
|
+
);
|
|
4928
|
+
} else {
|
|
4929
|
+
const allBlocks = columnIndexes.flatMap(
|
|
4930
|
+
(columnIndex) => buildBlocksForItems(
|
|
4931
|
+
pageWithColumns.textItems.filter(
|
|
4932
|
+
(item) => item.columnIndex === columnIndex
|
|
4933
|
+
),
|
|
4934
|
+
pageWithColumns
|
|
4935
|
+
).map((block) => ({ block, columnIndex }))
|
|
4936
|
+
);
|
|
4937
|
+
const fullWidthBlocks = allBlocks.filter(
|
|
4938
|
+
({ block }) => isFullWidthBlock(block, pageWithColumns, detectedColumnCount)
|
|
4939
|
+
);
|
|
4940
|
+
const columnBlocks = allBlocks.filter(
|
|
4941
|
+
({ block }) => !isFullWidthBlock(block, pageWithColumns, detectedColumnCount)
|
|
4942
|
+
);
|
|
4943
|
+
const topFullWidthBlocks = fullWidthBlocks.filter(({ block }) => {
|
|
4944
|
+
const rect = getTextItemsBoundingRect(block.flat());
|
|
4945
|
+
return (rect?.top ?? 0) < pageWithColumns.height * 0.3;
|
|
3555
4946
|
});
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
const endPt = highlight.content?.shape?.endPoint;
|
|
3560
|
-
const startX = startPt ? x + startPt.x * width : x;
|
|
3561
|
-
const startY = startPt ? y + (1 - startPt.y) * height : y + height / 2;
|
|
3562
|
-
const endX = endPt ? x + endPt.x * width : x + width;
|
|
3563
|
-
const endY = endPt ? y + (1 - endPt.y) * height : y + height / 2;
|
|
3564
|
-
page.drawLine({
|
|
3565
|
-
start: { x: startX, y: startY },
|
|
3566
|
-
end: { x: endX, y: endY },
|
|
3567
|
-
color: rgb(color.r, color.g, color.b),
|
|
3568
|
-
thickness: strokeWidth,
|
|
3569
|
-
opacity: color.a
|
|
4947
|
+
const bottomFullWidthBlocks = fullWidthBlocks.filter(({ block }) => {
|
|
4948
|
+
const rect = getTextItemsBoundingRect(block.flat());
|
|
4949
|
+
return (rect?.top ?? 0) >= pageWithColumns.height * 0.3;
|
|
3570
4950
|
});
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
4951
|
+
blocks.push(
|
|
4952
|
+
...topFullWidthBlocks.sort(compareBlocksByPosition),
|
|
4953
|
+
...columnBlocks.sort(compareBlocksByColumnReadingOrder),
|
|
4954
|
+
...bottomFullWidthBlocks.sort(compareBlocksByPosition)
|
|
4955
|
+
);
|
|
4956
|
+
}
|
|
4957
|
+
blocks.forEach(({ block, columnIndex }, indexInPage) => {
|
|
4958
|
+
units.push(
|
|
4959
|
+
buildTextUnit({
|
|
4960
|
+
block,
|
|
4961
|
+
columnIndex,
|
|
4962
|
+
indexInPage,
|
|
4963
|
+
includePositions,
|
|
4964
|
+
idPrefix,
|
|
4965
|
+
page: pageWithColumns,
|
|
4966
|
+
shouldNormalize
|
|
4967
|
+
})
|
|
4968
|
+
);
|
|
4969
|
+
});
|
|
4970
|
+
});
|
|
4971
|
+
if (!includeSources) {
|
|
4972
|
+
return units.map(({ source: _source, ...rest }) => rest);
|
|
4973
|
+
}
|
|
4974
|
+
return units;
|
|
4975
|
+
};
|
|
4976
|
+
var extractTextUnits = async (pdfDocument, options = {}) => {
|
|
4977
|
+
const resolvedOptions = { ...DEFAULT_OPTIONS, ...options };
|
|
4978
|
+
const pages = await extractPageTextItems(pdfDocument, {
|
|
4979
|
+
pages: resolvedOptions.pages,
|
|
4980
|
+
columnDetection: resolvedOptions.columnDetection
|
|
4981
|
+
});
|
|
4982
|
+
return extractTextUnitsFromPages(pages, resolvedOptions);
|
|
4983
|
+
};
|
|
4984
|
+
var extractPageTextItems = async (pdfDocument, options = {}) => {
|
|
4985
|
+
const pageNumbers = resolvePageNumbers(pdfDocument, options.pages);
|
|
4986
|
+
const columnDetection = options.columnDetection ?? DEFAULT_OPTIONS.columnDetection;
|
|
4987
|
+
const extractedPages = [];
|
|
4988
|
+
for (const pageNumber of pageNumbers) {
|
|
4989
|
+
const page = await pdfDocument.getPage(pageNumber);
|
|
4990
|
+
const viewport = page.getViewport({ scale: 1 });
|
|
4991
|
+
const textContent = await page.getTextContent();
|
|
4992
|
+
const textItems = [];
|
|
4993
|
+
textContent.items.forEach((item, index) => {
|
|
4994
|
+
if (!isTextItem(item) || item.str.length === 0) return;
|
|
4995
|
+
textItems.push({
|
|
4996
|
+
text: item.str,
|
|
4997
|
+
index,
|
|
4998
|
+
pageNumber,
|
|
4999
|
+
rect: getTextItemRect(item, viewport, pageNumber)
|
|
3583
5000
|
});
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
5001
|
+
});
|
|
5002
|
+
extractedPages.push(assignColumnsToPage({
|
|
5003
|
+
pageNumber,
|
|
5004
|
+
width: viewport.width,
|
|
5005
|
+
height: viewport.height,
|
|
5006
|
+
textItems
|
|
5007
|
+
}, columnDetection));
|
|
5008
|
+
}
|
|
5009
|
+
return extractedPages;
|
|
5010
|
+
};
|
|
5011
|
+
var extractSentences = async (pdfDocument, options = {}) => {
|
|
5012
|
+
const resolvedOptions = { ...DEFAULT_OPTIONS, ...options };
|
|
5013
|
+
const pages = await extractPageTextItems(pdfDocument, {
|
|
5014
|
+
pages: resolvedOptions.pages,
|
|
5015
|
+
columnDetection: resolvedOptions.columnDetection
|
|
5016
|
+
});
|
|
5017
|
+
const textUnits = extractTextUnitsFromPages(pages, { ...resolvedOptions, includeSources: true });
|
|
5018
|
+
const sentences = [];
|
|
5019
|
+
let globalIndex = 0;
|
|
5020
|
+
textUnits.filter((unit) => resolvedOptions.includeTextUnitTypes.includes(unit.type)).forEach((unit) => {
|
|
5021
|
+
const page = pages.find((candidatePage) => candidatePage.pageNumber === unit.pageNumber);
|
|
5022
|
+
if (!page) return;
|
|
5023
|
+
const pageTextItemsByIndex = new Map(
|
|
5024
|
+
page.textItems.map((item) => [item.index, item])
|
|
5025
|
+
);
|
|
5026
|
+
const stream = buildPageTextStream(
|
|
5027
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
5028
|
+
unit.source.textItemIndexes.map((textItemIndex) => pageTextItemsByIndex.get(textItemIndex)).filter((item) => item !== void 0)
|
|
5029
|
+
);
|
|
5030
|
+
if (!stream.text.trim()) return;
|
|
5031
|
+
const sentenceRanges = splitPdfSentences(
|
|
5032
|
+
stream.text,
|
|
5033
|
+
resolvedOptions.locale
|
|
5034
|
+
);
|
|
5035
|
+
sentenceRanges.forEach((sentenceRange, indexInPage) => {
|
|
5036
|
+
const textItemIndexes = getSentenceTextItemIndexes(
|
|
5037
|
+
stream.charItemIndexes,
|
|
5038
|
+
sentenceRange.start,
|
|
5039
|
+
sentenceRange.end
|
|
5040
|
+
);
|
|
5041
|
+
const position = resolvedOptions.includePositions ? getScaledPosition(page.textItems, textItemIndexes, page) : void 0;
|
|
5042
|
+
const id = `${resolvedOptions.idPrefix}p${page.pageNumber}-u${unit.indexInPage}-s${indexInPage}`;
|
|
5043
|
+
const rawText = sentenceRange.text;
|
|
5044
|
+
sentences.push({
|
|
5045
|
+
id,
|
|
5046
|
+
text: resolvedOptions.normalize ? normalizePdfSentenceText(rawText) : rawText,
|
|
5047
|
+
rawText,
|
|
5048
|
+
pageNumber: page.pageNumber,
|
|
5049
|
+
indexInPage,
|
|
5050
|
+
globalIndex,
|
|
5051
|
+
...unit.columnIndex !== void 0 ? { columnIndex: unit.columnIndex } : {},
|
|
5052
|
+
...position ? { position } : {},
|
|
5053
|
+
...resolvedOptions.includeSources ? { source: { startOffset: sentenceRange.start, endOffset: sentenceRange.end, textItemIndexes } } : {}
|
|
3593
5054
|
});
|
|
3594
|
-
|
|
3595
|
-
}
|
|
5055
|
+
globalIndex += 1;
|
|
5056
|
+
});
|
|
5057
|
+
});
|
|
5058
|
+
return sentences;
|
|
5059
|
+
};
|
|
5060
|
+
var sentenceToHighlight = (sentence, options = {}) => {
|
|
5061
|
+
if (!sentence.position) {
|
|
5062
|
+
throw new Error(
|
|
5063
|
+
"Cannot convert sentence to highlight because it has no position. Call extractSentences with includePositions: true."
|
|
5064
|
+
);
|
|
3596
5065
|
}
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
5066
|
+
return {
|
|
5067
|
+
id: options.id ?? sentence.id,
|
|
5068
|
+
type: "text",
|
|
5069
|
+
content: { text: sentence.text },
|
|
5070
|
+
position: sentence.position
|
|
5071
|
+
};
|
|
5072
|
+
};
|
|
5073
|
+
var stripWhitespaceWithMap = (raw) => {
|
|
5074
|
+
let stripped = "";
|
|
5075
|
+
const map = [];
|
|
5076
|
+
for (let i = 0; i < raw.length; i += 1) {
|
|
5077
|
+
const c = raw[i];
|
|
5078
|
+
if (/\s/.test(c)) continue;
|
|
5079
|
+
let ch = c.toLowerCase();
|
|
5080
|
+
if (ch === "\u2018" || ch === "\u2019") ch = "'";
|
|
5081
|
+
else if (ch === "\u201C" || ch === "\u201D") ch = '"';
|
|
5082
|
+
stripped += ch;
|
|
5083
|
+
map.push(i);
|
|
5084
|
+
}
|
|
5085
|
+
return { stripped, map };
|
|
5086
|
+
};
|
|
5087
|
+
var boundedLevenshtein = (a, b, max) => {
|
|
5088
|
+
const al = a.length;
|
|
5089
|
+
const bl = b.length;
|
|
5090
|
+
if (Math.abs(al - bl) > max) return max + 1;
|
|
5091
|
+
let prev = new Array(bl + 1);
|
|
5092
|
+
let curr = new Array(bl + 1);
|
|
5093
|
+
for (let j = 0; j <= bl; j++) prev[j] = j;
|
|
5094
|
+
for (let i = 1; i <= al; i++) {
|
|
5095
|
+
curr[0] = i;
|
|
5096
|
+
let rowMin = curr[0];
|
|
5097
|
+
for (let j = 1; j <= bl; j++) {
|
|
5098
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
5099
|
+
curr[j] = Math.min(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost);
|
|
5100
|
+
if (curr[j] < rowMin) rowMin = curr[j];
|
|
5101
|
+
}
|
|
5102
|
+
if (rowMin > max) return max + 1;
|
|
5103
|
+
[prev, curr] = [curr, prev];
|
|
3608
5104
|
}
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
const
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
await renderImageHighlight(pdfDoc, page, highlight);
|
|
3631
|
-
break;
|
|
3632
|
-
case "drawing":
|
|
3633
|
-
await renderImageHighlight(pdfDoc, page, highlight);
|
|
3634
|
-
break;
|
|
3635
|
-
case "shape":
|
|
3636
|
-
await renderShapeHighlight(page, highlight);
|
|
3637
|
-
break;
|
|
3638
|
-
default:
|
|
3639
|
-
await renderAreaHighlight(page, highlight, options);
|
|
5105
|
+
return prev[bl];
|
|
5106
|
+
};
|
|
5107
|
+
var fuzzyFind = (haystack, needle, maxDistance) => {
|
|
5108
|
+
const len = needle.length;
|
|
5109
|
+
if (len === 0) return -1;
|
|
5110
|
+
const anchor = needle.slice(0, Math.min(8, len));
|
|
5111
|
+
let best = -1;
|
|
5112
|
+
let bestDist = maxDistance + 1;
|
|
5113
|
+
let from = 0;
|
|
5114
|
+
while (true) {
|
|
5115
|
+
const at = haystack.indexOf(anchor, from);
|
|
5116
|
+
if (at === -1) break;
|
|
5117
|
+
for (let offset = -2; offset <= 2; offset++) {
|
|
5118
|
+
const start = at + offset;
|
|
5119
|
+
if (start < 0 || start + len > haystack.length) continue;
|
|
5120
|
+
const window2 = haystack.slice(start, start + len);
|
|
5121
|
+
const dist = boundedLevenshtein(needle, window2, bestDist - 1);
|
|
5122
|
+
if (dist < bestDist) {
|
|
5123
|
+
bestDist = dist;
|
|
5124
|
+
best = start;
|
|
5125
|
+
if (dist === 0) return best;
|
|
3640
5126
|
}
|
|
3641
5127
|
}
|
|
3642
|
-
|
|
3643
|
-
options.onProgress?.(currentPage, totalPages);
|
|
5128
|
+
from = at + 1;
|
|
3644
5129
|
}
|
|
3645
|
-
return
|
|
3646
|
-
}
|
|
5130
|
+
return bestDist <= maxDistance ? best : -1;
|
|
5131
|
+
};
|
|
5132
|
+
var rectsForRange = (items, itemStarts, start, end, pageNumber) => {
|
|
5133
|
+
const rects = [];
|
|
5134
|
+
for (let k = 0; k < items.length; k++) {
|
|
5135
|
+
const itemStart = itemStarts[k];
|
|
5136
|
+
const item = items[k];
|
|
5137
|
+
const len = item.text.length;
|
|
5138
|
+
if (len === 0) continue;
|
|
5139
|
+
const itemEnd = itemStart + len;
|
|
5140
|
+
const a = Math.max(start, itemStart);
|
|
5141
|
+
const b = Math.min(end, itemEnd);
|
|
5142
|
+
if (b <= a) continue;
|
|
5143
|
+
const { left, top, width, height } = item.rect;
|
|
5144
|
+
if (!(width > 0) || !(height > 0)) continue;
|
|
5145
|
+
const s = (a - itemStart) / len;
|
|
5146
|
+
const e = (b - itemStart) / len;
|
|
5147
|
+
rects.push({
|
|
5148
|
+
left: left + s * width,
|
|
5149
|
+
top,
|
|
5150
|
+
width: (e - s) * width,
|
|
5151
|
+
height,
|
|
5152
|
+
pageNumber
|
|
5153
|
+
});
|
|
5154
|
+
}
|
|
5155
|
+
rects.sort((p, q) => p.top - q.top || p.left - q.left);
|
|
5156
|
+
const merged = [];
|
|
5157
|
+
for (const r of rects) {
|
|
5158
|
+
const last = merged[merged.length - 1];
|
|
5159
|
+
if (last && Math.abs(last.top - r.top) < Math.max(2, r.height * 0.4) && r.left - (last.left + last.width) < Math.max(4, r.height * 0.6)) {
|
|
5160
|
+
const right = Math.max(last.left + last.width, r.left + r.width);
|
|
5161
|
+
last.left = Math.min(last.left, r.left);
|
|
5162
|
+
last.width = right - last.left;
|
|
5163
|
+
last.height = Math.max(last.height, r.height);
|
|
5164
|
+
} else {
|
|
5165
|
+
merged.push({ ...r });
|
|
5166
|
+
}
|
|
5167
|
+
}
|
|
5168
|
+
return merged;
|
|
5169
|
+
};
|
|
5170
|
+
var getTextPosition = async (pdfDocument, query, options = {}) => {
|
|
5171
|
+
const { stripped: needle } = stripWhitespaceWithMap(query);
|
|
5172
|
+
if (!needle) return null;
|
|
5173
|
+
const allowFuzzy = options.fuzzy ?? true;
|
|
5174
|
+
const maxDistance = Math.max(2, Math.floor(needle.length * 0.15));
|
|
5175
|
+
const pages = await extractPageTextItems(pdfDocument, {
|
|
5176
|
+
pages: options.pages ?? "all"
|
|
5177
|
+
});
|
|
5178
|
+
for (const page of pages) {
|
|
5179
|
+
const items = page.textItems.filter((it) => it.text).map((it) => ({ text: it.text, rect: it.rect }));
|
|
5180
|
+
if (items.length === 0) continue;
|
|
5181
|
+
const itemStarts = [];
|
|
5182
|
+
let raw = "";
|
|
5183
|
+
for (const it of items) {
|
|
5184
|
+
itemStarts.push(raw.length);
|
|
5185
|
+
raw += it.text;
|
|
5186
|
+
}
|
|
5187
|
+
const { stripped, map } = stripWhitespaceWithMap(raw);
|
|
5188
|
+
let at = stripped.indexOf(needle);
|
|
5189
|
+
let confidence = "exact";
|
|
5190
|
+
if (at === -1 && allowFuzzy) {
|
|
5191
|
+
at = fuzzyFind(stripped, needle, maxDistance);
|
|
5192
|
+
confidence = "fuzzy";
|
|
5193
|
+
}
|
|
5194
|
+
if (at === -1) continue;
|
|
5195
|
+
const rawStart = map[at];
|
|
5196
|
+
const rawEnd = map[Math.min(at + needle.length - 1, map.length - 1)] + 1;
|
|
5197
|
+
const rects = rectsForRange(
|
|
5198
|
+
items,
|
|
5199
|
+
itemStarts,
|
|
5200
|
+
rawStart,
|
|
5201
|
+
rawEnd,
|
|
5202
|
+
page.pageNumber
|
|
5203
|
+
);
|
|
5204
|
+
if (rects.length === 0) continue;
|
|
5205
|
+
const boundingRect = get_bounding_rect_default(rects);
|
|
5206
|
+
return {
|
|
5207
|
+
position: {
|
|
5208
|
+
boundingRect: viewportToScaled(boundingRect, page),
|
|
5209
|
+
rects: rects.map((r) => viewportToScaled(r, page))
|
|
5210
|
+
},
|
|
5211
|
+
pageNumber: page.pageNumber,
|
|
5212
|
+
matchedText: raw.slice(rawStart, rawEnd),
|
|
5213
|
+
confidence
|
|
5214
|
+
};
|
|
5215
|
+
}
|
|
5216
|
+
return null;
|
|
5217
|
+
};
|
|
3647
5218
|
|
|
3648
5219
|
// src/components/leftpanel/LeftPanel.tsx
|
|
3649
5220
|
import React21, { useState as useState17, useMemo as useMemo3, useCallback as useCallback8, useRef as useRef19, useEffect as useEffect18 } from "react";
|
|
@@ -4078,13 +5649,13 @@ var OutlineItem = ({
|
|
|
4078
5649
|
const defaultExpandIconStyle = {
|
|
4079
5650
|
width: 12,
|
|
4080
5651
|
height: 12,
|
|
4081
|
-
color: "#64748b"
|
|
5652
|
+
color: "var(--lp-muted, #64748b)"
|
|
4082
5653
|
};
|
|
4083
5654
|
const defaultActiveIndicatorStyle = {
|
|
4084
5655
|
width: 6,
|
|
4085
5656
|
height: 6,
|
|
4086
5657
|
borderRadius: "50%",
|
|
4087
|
-
backgroundColor: "#3b82f6",
|
|
5658
|
+
backgroundColor: "var(--lp-accent, #3b82f6)",
|
|
4088
5659
|
flexShrink: 0
|
|
4089
5660
|
};
|
|
4090
5661
|
const defaultTitleStyle = {
|
|
@@ -4095,23 +5666,23 @@ var OutlineItem = ({
|
|
|
4095
5666
|
fontSize: "13px",
|
|
4096
5667
|
fontWeight: 400,
|
|
4097
5668
|
fontStyle: item.italic ? "italic" : "normal",
|
|
4098
|
-
color: "#475569",
|
|
5669
|
+
color: "var(--lp-text, #475569)",
|
|
4099
5670
|
lineHeight: 1.4
|
|
4100
5671
|
};
|
|
4101
5672
|
const defaultTitleActiveStyle = {
|
|
4102
5673
|
fontWeight: 500,
|
|
4103
|
-
color: "#1e40af"
|
|
5674
|
+
color: "var(--lp-accent, #1e40af)"
|
|
4104
5675
|
};
|
|
4105
5676
|
const defaultPageNumberStyle = {
|
|
4106
5677
|
fontSize: "11px",
|
|
4107
|
-
color: "#94a3b8",
|
|
5678
|
+
color: "var(--lp-muted, #94a3b8)",
|
|
4108
5679
|
flexShrink: 0,
|
|
4109
5680
|
fontVariantNumeric: "tabular-nums",
|
|
4110
5681
|
minWidth: "20px",
|
|
4111
5682
|
textAlign: "right"
|
|
4112
5683
|
};
|
|
4113
5684
|
const defaultPageNumberActiveStyle = {
|
|
4114
|
-
color: "#3b82f6"
|
|
5685
|
+
color: "var(--lp-accent, #3b82f6)"
|
|
4115
5686
|
};
|
|
4116
5687
|
const defaultChildrenContainerStyle = {
|
|
4117
5688
|
position: "relative"
|
|
@@ -4119,7 +5690,7 @@ var OutlineItem = ({
|
|
|
4119
5690
|
const containerStyle = {
|
|
4120
5691
|
...defaultContainerStyle,
|
|
4121
5692
|
...styles?.container,
|
|
4122
|
-
...isHovered && !isActive ? { backgroundColor: "#f8fafc", ...styles?.containerHover } : {},
|
|
5693
|
+
...isHovered && !isActive ? { backgroundColor: "var(--lp-hover, #f8fafc)", ...styles?.containerHover } : {},
|
|
4123
5694
|
...isActive ? styles?.containerActive : {}
|
|
4124
5695
|
};
|
|
4125
5696
|
const expandButtonStyle = {
|
|
@@ -4188,7 +5759,7 @@ var OutlineItem = ({
|
|
|
4188
5759
|
},
|
|
4189
5760
|
style: expandButtonStyle,
|
|
4190
5761
|
onMouseEnter: (e) => {
|
|
4191
|
-
e.currentTarget.style.backgroundColor = "#e5e7eb";
|
|
5762
|
+
e.currentTarget.style.backgroundColor = "var(--lp-hover, #e5e7eb)";
|
|
4192
5763
|
},
|
|
4193
5764
|
onMouseLeave: (e) => {
|
|
4194
5765
|
e.currentTarget.style.backgroundColor = "transparent";
|
|
@@ -4245,13 +5816,13 @@ var DocumentOutline = ({
|
|
|
4245
5816
|
const defaultLoadingSpinnerStyle = {
|
|
4246
5817
|
width: 28,
|
|
4247
5818
|
height: 28,
|
|
4248
|
-
color: "#94a3b8",
|
|
5819
|
+
color: "var(--lp-muted, #94a3b8)",
|
|
4249
5820
|
marginBottom: 12,
|
|
4250
5821
|
animation: "spin 1s linear infinite"
|
|
4251
5822
|
};
|
|
4252
5823
|
const defaultLoadingTextStyle = {
|
|
4253
5824
|
fontSize: 13,
|
|
4254
|
-
color: "#64748b"
|
|
5825
|
+
color: "var(--lp-muted, #64748b)"
|
|
4255
5826
|
};
|
|
4256
5827
|
const defaultEmptyContainerStyle = {
|
|
4257
5828
|
display: "flex",
|
|
@@ -4264,7 +5835,7 @@ var DocumentOutline = ({
|
|
|
4264
5835
|
width: 56,
|
|
4265
5836
|
height: 56,
|
|
4266
5837
|
borderRadius: "50%",
|
|
4267
|
-
backgroundColor: "#f1f5f9",
|
|
5838
|
+
backgroundColor: "var(--lp-hover, #f1f5f9)",
|
|
4268
5839
|
display: "flex",
|
|
4269
5840
|
alignItems: "center",
|
|
4270
5841
|
justifyContent: "center",
|
|
@@ -4273,17 +5844,17 @@ var DocumentOutline = ({
|
|
|
4273
5844
|
const defaultEmptyIconStyle = {
|
|
4274
5845
|
width: 28,
|
|
4275
5846
|
height: 28,
|
|
4276
|
-
color: "#94a3b8"
|
|
5847
|
+
color: "var(--lp-muted, #94a3b8)"
|
|
4277
5848
|
};
|
|
4278
5849
|
const defaultEmptyTitleStyle = {
|
|
4279
5850
|
fontSize: 14,
|
|
4280
5851
|
fontWeight: 500,
|
|
4281
|
-
color: "#475569",
|
|
5852
|
+
color: "var(--lp-text, #475569)",
|
|
4282
5853
|
marginBottom: 4
|
|
4283
5854
|
};
|
|
4284
5855
|
const defaultEmptyDescriptionStyle = {
|
|
4285
5856
|
fontSize: 13,
|
|
4286
|
-
color: "#94a3b8",
|
|
5857
|
+
color: "var(--lp-muted, #94a3b8)",
|
|
4287
5858
|
textAlign: "center"
|
|
4288
5859
|
};
|
|
4289
5860
|
const defaultContainerStyle = {
|
|
@@ -4395,7 +5966,7 @@ var ThumbnailItem = React19.memo(({
|
|
|
4395
5966
|
width: "85%",
|
|
4396
5967
|
aspectRatio: "8.5 / 11",
|
|
4397
5968
|
// Standard page aspect ratio
|
|
4398
|
-
backgroundColor: "#ffffff",
|
|
5969
|
+
backgroundColor: "var(--lp-bg, #ffffff)",
|
|
4399
5970
|
borderRadius: "4px",
|
|
4400
5971
|
overflow: "hidden",
|
|
4401
5972
|
boxShadow: isActive ? "0 4px 12px rgba(59, 130, 246, 0.25)" : isHovered ? "0 4px 12px rgba(0, 0, 0, 0.12)" : "0 1px 3px rgba(0, 0, 0, 0.08)",
|
|
@@ -4405,7 +5976,7 @@ var ThumbnailItem = React19.memo(({
|
|
|
4405
5976
|
marginTop: "6px",
|
|
4406
5977
|
fontSize: "11px",
|
|
4407
5978
|
fontWeight: 500,
|
|
4408
|
-
color: isActive ? "#3b82f6" : "#6b7280",
|
|
5979
|
+
color: isActive ? "var(--lp-accent, #3b82f6)" : "var(--lp-muted, #6b7280)",
|
|
4409
5980
|
transition: "color 0.15s ease"
|
|
4410
5981
|
};
|
|
4411
5982
|
return /* @__PURE__ */ React19.createElement(
|
|
@@ -4437,7 +6008,7 @@ var ThumbnailItem = React19.memo(({
|
|
|
4437
6008
|
display: "flex",
|
|
4438
6009
|
alignItems: "center",
|
|
4439
6010
|
justifyContent: "center",
|
|
4440
|
-
backgroundColor: "#f9fafb"
|
|
6011
|
+
backgroundColor: "var(--lp-hover, #f9fafb)"
|
|
4441
6012
|
}
|
|
4442
6013
|
},
|
|
4443
6014
|
/* @__PURE__ */ React19.createElement(
|
|
@@ -4446,7 +6017,7 @@ var ThumbnailItem = React19.memo(({
|
|
|
4446
6017
|
style: {
|
|
4447
6018
|
width: 24,
|
|
4448
6019
|
height: 24,
|
|
4449
|
-
color: "#9ca3af",
|
|
6020
|
+
color: "var(--lp-muted, #9ca3af)",
|
|
4450
6021
|
animation: "spin 1s linear infinite"
|
|
4451
6022
|
}
|
|
4452
6023
|
}
|
|
@@ -4487,7 +6058,7 @@ var ThumbnailItem = React19.memo(({
|
|
|
4487
6058
|
display: "flex",
|
|
4488
6059
|
alignItems: "center",
|
|
4489
6060
|
justifyContent: "center",
|
|
4490
|
-
backgroundColor: "#f9fafb"
|
|
6061
|
+
backgroundColor: "var(--lp-hover, #f9fafb)"
|
|
4491
6062
|
}
|
|
4492
6063
|
},
|
|
4493
6064
|
/* @__PURE__ */ React19.createElement(
|
|
@@ -4496,7 +6067,7 @@ var ThumbnailItem = React19.memo(({
|
|
|
4496
6067
|
style: {
|
|
4497
6068
|
fontSize: 24,
|
|
4498
6069
|
fontWeight: 300,
|
|
4499
|
-
color: "#d1d5db"
|
|
6070
|
+
color: "var(--lp-muted, #d1d5db)"
|
|
4500
6071
|
}
|
|
4501
6072
|
},
|
|
4502
6073
|
pageNumber
|
|
@@ -4553,7 +6124,7 @@ var ThumbnailPanel = ({
|
|
|
4553
6124
|
padding: "48px 16px"
|
|
4554
6125
|
}
|
|
4555
6126
|
},
|
|
4556
|
-
/* @__PURE__ */ React20.createElement("p", { style: { fontSize: 13, color: "#9ca3af" } }, "No pages to display")
|
|
6127
|
+
/* @__PURE__ */ React20.createElement("p", { style: { fontSize: 13, color: "var(--lp-muted, #9ca3af)" } }, "No pages to display")
|
|
4557
6128
|
);
|
|
4558
6129
|
}
|
|
4559
6130
|
return /* @__PURE__ */ React20.createElement(
|
|
@@ -4627,6 +6198,14 @@ var defaultTheme = {
|
|
|
4627
6198
|
mutedTextColor: "#6b7280",
|
|
4628
6199
|
hoverBackgroundColor: "#f9fafb"
|
|
4629
6200
|
};
|
|
6201
|
+
var defaultDarkTheme2 = {
|
|
6202
|
+
backgroundColor: "#1f1d1b",
|
|
6203
|
+
borderColor: "#3a3733",
|
|
6204
|
+
accentColor: "#7aa2f7",
|
|
6205
|
+
textColor: "#eae6e0",
|
|
6206
|
+
mutedTextColor: "#a8a29a",
|
|
6207
|
+
hoverBackgroundColor: "#2a2724"
|
|
6208
|
+
};
|
|
4630
6209
|
var LeftPanel = ({
|
|
4631
6210
|
pdfDocument,
|
|
4632
6211
|
viewer = null,
|
|
@@ -4645,6 +6224,7 @@ var LeftPanel = ({
|
|
|
4645
6224
|
onPageSelect,
|
|
4646
6225
|
thumbnailWidth = 180,
|
|
4647
6226
|
children,
|
|
6227
|
+
mode = "light",
|
|
4648
6228
|
theme: userTheme,
|
|
4649
6229
|
showFooter = true,
|
|
4650
6230
|
showToggleButton = true,
|
|
@@ -4659,7 +6239,13 @@ var LeftPanel = ({
|
|
|
4659
6239
|
outlineItemStyles,
|
|
4660
6240
|
outlineItemClassNames
|
|
4661
6241
|
}) => {
|
|
4662
|
-
const theme = useMemo3(
|
|
6242
|
+
const theme = useMemo3(
|
|
6243
|
+
() => ({
|
|
6244
|
+
...mode === "dark" ? defaultDarkTheme2 : defaultTheme,
|
|
6245
|
+
...userTheme
|
|
6246
|
+
}),
|
|
6247
|
+
[mode, userTheme]
|
|
6248
|
+
);
|
|
4663
6249
|
const [internalIsOpen, setInternalIsOpen] = useState17(true);
|
|
4664
6250
|
const [activeTab, setActiveTab] = useState17(defaultTab);
|
|
4665
6251
|
const isOpen = controlledIsOpen !== void 0 ? controlledIsOpen : internalIsOpen;
|
|
@@ -4971,6 +6557,12 @@ var LeftPanel = ({
|
|
|
4971
6557
|
)
|
|
4972
6558
|
));
|
|
4973
6559
|
};
|
|
6560
|
+
|
|
6561
|
+
// src/index.ts
|
|
6562
|
+
var exportPdf = async (...args) => {
|
|
6563
|
+
const { exportPdf: exportPdf2 } = await import("./export-pdf-W2QGWADM.js");
|
|
6564
|
+
return exportPdf2(...args);
|
|
6565
|
+
};
|
|
4974
6566
|
export {
|
|
4975
6567
|
AreaHighlight,
|
|
4976
6568
|
DocumentOutline,
|
|
@@ -4990,7 +6582,12 @@ export {
|
|
|
4990
6582
|
ThumbnailItem,
|
|
4991
6583
|
ThumbnailPanel,
|
|
4992
6584
|
exportPdf,
|
|
6585
|
+
extractPageTextItems,
|
|
6586
|
+
extractSentences,
|
|
6587
|
+
extractTextUnits,
|
|
6588
|
+
getTextPosition,
|
|
4993
6589
|
scaledPositionToViewport,
|
|
6590
|
+
sentenceToHighlight,
|
|
4994
6591
|
useDocumentOutline,
|
|
4995
6592
|
useHighlightContainerContext,
|
|
4996
6593
|
useLeftPanelContext,
|