react-pdf-highlighter-plus 1.1.4 → 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 +152 -20
- package/dist/esm/index.d.ts +104 -9
- package/dist/esm/index.js +1031 -137
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/style/PdfHighlighter.css +53 -17
- package/package.json +1 -1
package/dist/esm/index.js
CHANGED
|
@@ -87,6 +87,323 @@ var scaledPositionToViewport = ({ boundingRect, rects, usePdfCoordinates }, view
|
|
|
87
87
|
};
|
|
88
88
|
};
|
|
89
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
|
+
|
|
90
407
|
// src/lib/get-bounding-rect.ts
|
|
91
408
|
var getBoundingRect = (clientRects) => {
|
|
92
409
|
const rects = Array.from(clientRects).map((rect) => {
|
|
@@ -292,16 +609,6 @@ var getPagesFromRange = (range) => {
|
|
|
292
609
|
}
|
|
293
610
|
return pages;
|
|
294
611
|
};
|
|
295
|
-
var findOrCreateContainerLayer = (container, className) => {
|
|
296
|
-
const doc = getDocument(container);
|
|
297
|
-
let layer = container.querySelector(`.${className}`);
|
|
298
|
-
if (!layer && container.children.length) {
|
|
299
|
-
layer = doc.createElement("div");
|
|
300
|
-
layer.className = className;
|
|
301
|
-
container.appendChild(layer);
|
|
302
|
-
}
|
|
303
|
-
return layer;
|
|
304
|
-
};
|
|
305
612
|
|
|
306
613
|
// src/components/DrawingCanvas.tsx
|
|
307
614
|
import React, {
|
|
@@ -1126,41 +1433,79 @@ var PDFViewer;
|
|
|
1126
1433
|
var SCROLL_MARGIN = 10;
|
|
1127
1434
|
var DEFAULT_SCALE_VALUE = "auto";
|
|
1128
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
|
+
};
|
|
1129
1474
|
var defaultLightTheme = {
|
|
1130
1475
|
mode: "light",
|
|
1131
1476
|
containerBackgroundColor: "#e5e5e5",
|
|
1132
1477
|
scrollbarThumbColor: "#9f9f9f",
|
|
1133
1478
|
scrollbarTrackColor: "#d1d1d1",
|
|
1134
|
-
darkModeInvertIntensity: 0.9
|
|
1479
|
+
darkModeInvertIntensity: 0.9,
|
|
1480
|
+
darkModeColors: DEFAULT_DARK_MODE_COLORS2
|
|
1135
1481
|
};
|
|
1136
1482
|
var defaultDarkTheme = {
|
|
1137
1483
|
mode: "dark",
|
|
1138
1484
|
containerBackgroundColor: "#3a3a3a",
|
|
1139
|
-
// Lighter than PDF page
|
|
1485
|
+
// Lighter than PDF page for contrast
|
|
1140
1486
|
scrollbarThumbColor: "#6b6b6b",
|
|
1141
1487
|
scrollbarTrackColor: "#2c2c2c",
|
|
1142
|
-
darkModeInvertIntensity: 0.9
|
|
1488
|
+
darkModeInvertIntensity: 0.9,
|
|
1489
|
+
darkModeColors: DEFAULT_DARK_MODE_COLORS2
|
|
1143
1490
|
};
|
|
1144
|
-
var
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
)
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
layer.className = "PdfHighlighter__note-layer";
|
|
1161
|
-
container.appendChild(layer);
|
|
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;
|
|
1162
1507
|
}
|
|
1163
|
-
return
|
|
1508
|
+
return binding;
|
|
1164
1509
|
};
|
|
1165
1510
|
var isFreetextHighlight = (highlight) => "type" in highlight && highlight.type === "freetext";
|
|
1166
1511
|
var disableTextSelection = (viewer, flag) => {
|
|
@@ -1170,6 +1515,9 @@ var PdfHighlighter = ({
|
|
|
1170
1515
|
highlights,
|
|
1171
1516
|
onScrollAway,
|
|
1172
1517
|
pdfScaleValue = DEFAULT_SCALE_VALUE,
|
|
1518
|
+
onZoomChange,
|
|
1519
|
+
initialPage,
|
|
1520
|
+
onPageChange,
|
|
1173
1521
|
onSelection: onSelectionFinished,
|
|
1174
1522
|
onCreateGhostHighlight,
|
|
1175
1523
|
onRemoveGhostHighlight,
|
|
@@ -1189,12 +1537,12 @@ var PdfHighlighter = ({
|
|
|
1189
1537
|
enableDrawingMode,
|
|
1190
1538
|
onDrawingComplete,
|
|
1191
1539
|
onDrawingCancel,
|
|
1192
|
-
drawingStrokeColor
|
|
1540
|
+
drawingStrokeColor: drawingStrokeColorProp,
|
|
1193
1541
|
drawingStrokeWidth = 3,
|
|
1194
1542
|
enableShapeMode,
|
|
1195
1543
|
onShapeComplete,
|
|
1196
1544
|
onShapeCancel,
|
|
1197
|
-
shapeStrokeColor
|
|
1545
|
+
shapeStrokeColor: shapeStrokeColorProp,
|
|
1198
1546
|
shapeStrokeWidth = 2,
|
|
1199
1547
|
theme: userTheme
|
|
1200
1548
|
}) => {
|
|
@@ -1203,6 +1551,18 @@ var PdfHighlighter = ({
|
|
|
1203
1551
|
const defaults = mode === "light" ? defaultLightTheme : defaultDarkTheme;
|
|
1204
1552
|
return { ...defaults, ...userTheme, mode };
|
|
1205
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;
|
|
1206
1566
|
const [tip, setTip] = useState5(null);
|
|
1207
1567
|
const [isViewerReady, setIsViewerReady] = useState5(false);
|
|
1208
1568
|
const containerNodeRef = useRef5(null);
|
|
@@ -1235,10 +1595,17 @@ var PdfHighlighter = ({
|
|
|
1235
1595
|
);
|
|
1236
1596
|
const findControllerRef = useRef5(null);
|
|
1237
1597
|
const viewerRef = useRef5(null);
|
|
1598
|
+
const prevDocRef = useRef5(null);
|
|
1599
|
+
const lastRenderKeyRef = useRef5(null);
|
|
1238
1600
|
highlightsRef.current = highlights;
|
|
1239
1601
|
childrenRef.current = children;
|
|
1240
1602
|
useLayoutEffect2(() => {
|
|
1241
1603
|
if (!containerNodeRef.current) return;
|
|
1604
|
+
const renderKey = `${pdfDocument.fingerprints ?? pdfDocument.numPages}::${recolorKey}`;
|
|
1605
|
+
if (lastRenderKeyRef.current === renderKey) {
|
|
1606
|
+
console.log("[PdfHighlighter] skip duplicate setDocument", renderKey);
|
|
1607
|
+
return;
|
|
1608
|
+
}
|
|
1242
1609
|
findControllerRef.current = findControllerRef.current || new PDFFindController({
|
|
1243
1610
|
eventBus: eventBusRef.current,
|
|
1244
1611
|
linkService: linkServiceRef.current
|
|
@@ -1251,11 +1618,51 @@ var PdfHighlighter = ({
|
|
|
1251
1618
|
removePageBorders: true,
|
|
1252
1619
|
linkService: linkServiceRef.current
|
|
1253
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;
|
|
1254
1661
|
viewerRef.current.setDocument(pdfDocument);
|
|
1255
1662
|
linkServiceRef.current.setDocument(pdfDocument);
|
|
1256
1663
|
linkServiceRef.current.setViewer(viewerRef.current);
|
|
1257
1664
|
setIsViewerReady(true);
|
|
1258
|
-
}, [pdfDocument]);
|
|
1665
|
+
}, [pdfDocument, recolorKey]);
|
|
1259
1666
|
useLayoutEffect2(() => {
|
|
1260
1667
|
if (!containerNodeRef.current) return;
|
|
1261
1668
|
resizeObserverRef.current = new ResizeObserver(handleScaleValue);
|
|
@@ -1282,6 +1689,222 @@ var PdfHighlighter = ({
|
|
|
1282
1689
|
}
|
|
1283
1690
|
};
|
|
1284
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]);
|
|
1285
1908
|
const handleScroll = () => {
|
|
1286
1909
|
onScrollAway && onScrollAway();
|
|
1287
1910
|
scrolledToHighlightIdRef.current = null;
|
|
@@ -1449,34 +2072,25 @@ var PdfHighlighter = ({
|
|
|
1449
2072
|
const { textLayer } = viewerRef.current.getPageView(pageNumber - 1) || {};
|
|
1450
2073
|
if (!textLayer) continue;
|
|
1451
2074
|
const textLayerDiv = textLayer.div;
|
|
1452
|
-
const
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
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,
|
|
1466
2090
|
pageNumber,
|
|
1467
|
-
|
|
2091
|
+
pageEl,
|
|
2092
|
+
"PdfHighlighter__note-layer"
|
|
1468
2093
|
);
|
|
1469
|
-
}
|
|
1470
|
-
if (noteLayer) {
|
|
1471
|
-
let noteBindings = noteBindingsRef.current[pageNumber];
|
|
1472
|
-
if (!noteBindings?.container?.isConnected) {
|
|
1473
|
-
noteBindings = {
|
|
1474
|
-
reactRoot: createRoot(noteLayer),
|
|
1475
|
-
container: noteLayer,
|
|
1476
|
-
textLayer: textLayerDiv
|
|
1477
|
-
};
|
|
1478
|
-
noteBindingsRef.current[pageNumber] = noteBindings;
|
|
1479
|
-
}
|
|
1480
2094
|
renderHighlightLayer(noteBindings, pageNumber, isFreetextHighlight);
|
|
1481
2095
|
}
|
|
1482
2096
|
}
|
|
@@ -1532,25 +2146,28 @@ var PdfHighlighter = ({
|
|
|
1532
2146
|
const scrollToHighlight = (highlight) => {
|
|
1533
2147
|
const { boundingRect, usePdfCoordinates } = highlight.position;
|
|
1534
2148
|
const pageNumber = boundingRect.pageNumber;
|
|
1535
|
-
viewerRef.current
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
)
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
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"
|
|
2167
|
+
});
|
|
2168
|
+
} else {
|
|
2169
|
+
viewer.scrollPageIntoView({ pageNumber });
|
|
2170
|
+
}
|
|
1554
2171
|
scrolledToHighlightIdRef.current = highlight.id;
|
|
1555
2172
|
scheduleRenderHighlightLayers();
|
|
1556
2173
|
resumeScrollAwayListenerAfterNavigation();
|
|
@@ -1716,17 +2333,6 @@ var PdfHighlighter = ({
|
|
|
1716
2333
|
.PdfHighlighter::-webkit-scrollbar-track-piece {
|
|
1717
2334
|
background-color: ${resolvedTheme.scrollbarTrackColor};
|
|
1718
2335
|
}
|
|
1719
|
-
${resolvedTheme.mode === "dark" ? `
|
|
1720
|
-
.PdfHighlighter--dark .page {
|
|
1721
|
-
filter: invert(${resolvedTheme.darkModeInvertIntensity}) hue-rotate(180deg) brightness(1.05);
|
|
1722
|
-
}
|
|
1723
|
-
.PdfHighlighter--dark .PdfHighlighter__highlight-layer {
|
|
1724
|
-
filter: invert(${resolvedTheme.darkModeInvertIntensity}) hue-rotate(180deg) brightness(0.95);
|
|
1725
|
-
}
|
|
1726
|
-
.PdfHighlighter--dark .PdfHighlighter__note-layer {
|
|
1727
|
-
filter: invert(${resolvedTheme.darkModeInvertIntensity}) hue-rotate(180deg) brightness(0.95);
|
|
1728
|
-
}
|
|
1729
|
-
` : ""}
|
|
1730
2336
|
`),
|
|
1731
2337
|
isViewerReady && /* @__PURE__ */ React6.createElement(
|
|
1732
2338
|
TipContainer,
|
|
@@ -1965,7 +2571,7 @@ var TextHighlight = ({
|
|
|
1965
2571
|
const getPartStyle = (rect) => {
|
|
1966
2572
|
const baseStyle = { ...rect, ...style };
|
|
1967
2573
|
if (highlightStyle === "highlight") {
|
|
1968
|
-
baseStyle.backgroundColor = highlightColor
|
|
2574
|
+
baseStyle.backgroundColor = `color-mix(in srgb, ${highlightColor} var(--hl-fill-alpha, 100%), transparent)`;
|
|
1969
2575
|
} else {
|
|
1970
2576
|
baseStyle.backgroundColor = "transparent";
|
|
1971
2577
|
baseStyle.color = highlightColor;
|
|
@@ -2286,7 +2892,7 @@ var AreaHighlight = ({
|
|
|
2286
2892
|
const key = `${highlight.position.boundingRect.width}${highlight.position.boundingRect.height}${highlight.position.boundingRect.left}${highlight.position.boundingRect.top}`;
|
|
2287
2893
|
const mergedStyle = {
|
|
2288
2894
|
...style,
|
|
2289
|
-
backgroundColor: highlightColor
|
|
2895
|
+
backgroundColor: `color-mix(in srgb, ${highlightColor} var(--hl-fill-alpha, 100%), transparent)`
|
|
2290
2896
|
};
|
|
2291
2897
|
const handleCopy = async (event) => {
|
|
2292
2898
|
event.stopPropagation();
|
|
@@ -3586,7 +4192,40 @@ var ShapeHighlight = ({
|
|
|
3586
4192
|
// src/components/PdfLoader.tsx
|
|
3587
4193
|
import React16, { useEffect as useEffect12, useRef as useRef14, useState as useState11 } from "react";
|
|
3588
4194
|
import { GlobalWorkerOptions, getDocument as getDocument2 } from "pdfjs-dist";
|
|
3589
|
-
var DEFAULT_BEFORE_LOAD = (progress) =>
|
|
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
|
+
};
|
|
3590
4229
|
var DEFAULT_ERROR_MESSAGE = (error) => /* @__PURE__ */ React16.createElement("div", { style: { color: "black" } }, error.message);
|
|
3591
4230
|
var DEFAULT_ON_ERROR = (error) => {
|
|
3592
4231
|
throw new Error(`Error loading PDF document: ${error.message}!`);
|
|
@@ -3595,44 +4234,138 @@ var DEFAULT_WORKER_SRC = new URL(
|
|
|
3595
4234
|
"pdfjs-dist/build/pdf.worker.min.mjs",
|
|
3596
4235
|
import.meta.url
|
|
3597
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
|
+
};
|
|
3598
4280
|
var PdfLoader = ({
|
|
3599
4281
|
document: document2,
|
|
3600
4282
|
beforeLoad = DEFAULT_BEFORE_LOAD,
|
|
3601
4283
|
errorMessage = DEFAULT_ERROR_MESSAGE,
|
|
3602
4284
|
children,
|
|
3603
4285
|
onError = DEFAULT_ON_ERROR,
|
|
3604
|
-
workerSrc = DEFAULT_WORKER_SRC
|
|
4286
|
+
workerSrc = DEFAULT_WORKER_SRC,
|
|
4287
|
+
disableAutoFetch = true,
|
|
4288
|
+
disableStream,
|
|
4289
|
+
rangeChunkSize,
|
|
4290
|
+
withCredentials,
|
|
4291
|
+
httpHeaders,
|
|
4292
|
+
enableCache = true
|
|
3605
4293
|
}) => {
|
|
3606
|
-
const
|
|
3607
|
-
const pdfDocumentRef = useRef14(null);
|
|
4294
|
+
const [pdfDocument, setPdfDocument] = useState11(null);
|
|
3608
4295
|
const [error, setError] = useState11(null);
|
|
3609
4296
|
const [loadingProgress, setLoadingProgress] = useState11(null);
|
|
4297
|
+
const onErrorRef = useRef14(onError);
|
|
4298
|
+
onErrorRef.current = onError;
|
|
4299
|
+
const httpHeadersKey = httpHeaders ? JSON.stringify(httpHeaders) : "";
|
|
3610
4300
|
useEffect12(() => {
|
|
3611
4301
|
GlobalWorkerOptions.workerSrc = workerSrc;
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
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();
|
|
3622
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);
|
|
3623
4343
|
}).finally(() => {
|
|
3624
|
-
setLoadingProgress(null);
|
|
4344
|
+
if (!cancelled) setLoadingProgress(null);
|
|
3625
4345
|
});
|
|
3626
4346
|
return () => {
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
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();
|
|
3632
4354
|
}
|
|
3633
4355
|
};
|
|
3634
|
-
}, [
|
|
3635
|
-
|
|
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);
|
|
3636
4369
|
};
|
|
3637
4370
|
|
|
3638
4371
|
// src/lib/extract-sentences.ts
|
|
@@ -4337,6 +5070,151 @@ var sentenceToHighlight = (sentence, options = {}) => {
|
|
|
4337
5070
|
position: sentence.position
|
|
4338
5071
|
};
|
|
4339
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];
|
|
5104
|
+
}
|
|
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;
|
|
5126
|
+
}
|
|
5127
|
+
}
|
|
5128
|
+
from = at + 1;
|
|
5129
|
+
}
|
|
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
|
+
};
|
|
4340
5218
|
|
|
4341
5219
|
// src/components/leftpanel/LeftPanel.tsx
|
|
4342
5220
|
import React21, { useState as useState17, useMemo as useMemo3, useCallback as useCallback8, useRef as useRef19, useEffect as useEffect18 } from "react";
|
|
@@ -4771,13 +5649,13 @@ var OutlineItem = ({
|
|
|
4771
5649
|
const defaultExpandIconStyle = {
|
|
4772
5650
|
width: 12,
|
|
4773
5651
|
height: 12,
|
|
4774
|
-
color: "#64748b"
|
|
5652
|
+
color: "var(--lp-muted, #64748b)"
|
|
4775
5653
|
};
|
|
4776
5654
|
const defaultActiveIndicatorStyle = {
|
|
4777
5655
|
width: 6,
|
|
4778
5656
|
height: 6,
|
|
4779
5657
|
borderRadius: "50%",
|
|
4780
|
-
backgroundColor: "#3b82f6",
|
|
5658
|
+
backgroundColor: "var(--lp-accent, #3b82f6)",
|
|
4781
5659
|
flexShrink: 0
|
|
4782
5660
|
};
|
|
4783
5661
|
const defaultTitleStyle = {
|
|
@@ -4788,23 +5666,23 @@ var OutlineItem = ({
|
|
|
4788
5666
|
fontSize: "13px",
|
|
4789
5667
|
fontWeight: 400,
|
|
4790
5668
|
fontStyle: item.italic ? "italic" : "normal",
|
|
4791
|
-
color: "#475569",
|
|
5669
|
+
color: "var(--lp-text, #475569)",
|
|
4792
5670
|
lineHeight: 1.4
|
|
4793
5671
|
};
|
|
4794
5672
|
const defaultTitleActiveStyle = {
|
|
4795
5673
|
fontWeight: 500,
|
|
4796
|
-
color: "#1e40af"
|
|
5674
|
+
color: "var(--lp-accent, #1e40af)"
|
|
4797
5675
|
};
|
|
4798
5676
|
const defaultPageNumberStyle = {
|
|
4799
5677
|
fontSize: "11px",
|
|
4800
|
-
color: "#94a3b8",
|
|
5678
|
+
color: "var(--lp-muted, #94a3b8)",
|
|
4801
5679
|
flexShrink: 0,
|
|
4802
5680
|
fontVariantNumeric: "tabular-nums",
|
|
4803
5681
|
minWidth: "20px",
|
|
4804
5682
|
textAlign: "right"
|
|
4805
5683
|
};
|
|
4806
5684
|
const defaultPageNumberActiveStyle = {
|
|
4807
|
-
color: "#3b82f6"
|
|
5685
|
+
color: "var(--lp-accent, #3b82f6)"
|
|
4808
5686
|
};
|
|
4809
5687
|
const defaultChildrenContainerStyle = {
|
|
4810
5688
|
position: "relative"
|
|
@@ -4812,7 +5690,7 @@ var OutlineItem = ({
|
|
|
4812
5690
|
const containerStyle = {
|
|
4813
5691
|
...defaultContainerStyle,
|
|
4814
5692
|
...styles?.container,
|
|
4815
|
-
...isHovered && !isActive ? { backgroundColor: "#f8fafc", ...styles?.containerHover } : {},
|
|
5693
|
+
...isHovered && !isActive ? { backgroundColor: "var(--lp-hover, #f8fafc)", ...styles?.containerHover } : {},
|
|
4816
5694
|
...isActive ? styles?.containerActive : {}
|
|
4817
5695
|
};
|
|
4818
5696
|
const expandButtonStyle = {
|
|
@@ -4881,7 +5759,7 @@ var OutlineItem = ({
|
|
|
4881
5759
|
},
|
|
4882
5760
|
style: expandButtonStyle,
|
|
4883
5761
|
onMouseEnter: (e) => {
|
|
4884
|
-
e.currentTarget.style.backgroundColor = "#e5e7eb";
|
|
5762
|
+
e.currentTarget.style.backgroundColor = "var(--lp-hover, #e5e7eb)";
|
|
4885
5763
|
},
|
|
4886
5764
|
onMouseLeave: (e) => {
|
|
4887
5765
|
e.currentTarget.style.backgroundColor = "transparent";
|
|
@@ -4938,13 +5816,13 @@ var DocumentOutline = ({
|
|
|
4938
5816
|
const defaultLoadingSpinnerStyle = {
|
|
4939
5817
|
width: 28,
|
|
4940
5818
|
height: 28,
|
|
4941
|
-
color: "#94a3b8",
|
|
5819
|
+
color: "var(--lp-muted, #94a3b8)",
|
|
4942
5820
|
marginBottom: 12,
|
|
4943
5821
|
animation: "spin 1s linear infinite"
|
|
4944
5822
|
};
|
|
4945
5823
|
const defaultLoadingTextStyle = {
|
|
4946
5824
|
fontSize: 13,
|
|
4947
|
-
color: "#64748b"
|
|
5825
|
+
color: "var(--lp-muted, #64748b)"
|
|
4948
5826
|
};
|
|
4949
5827
|
const defaultEmptyContainerStyle = {
|
|
4950
5828
|
display: "flex",
|
|
@@ -4957,7 +5835,7 @@ var DocumentOutline = ({
|
|
|
4957
5835
|
width: 56,
|
|
4958
5836
|
height: 56,
|
|
4959
5837
|
borderRadius: "50%",
|
|
4960
|
-
backgroundColor: "#f1f5f9",
|
|
5838
|
+
backgroundColor: "var(--lp-hover, #f1f5f9)",
|
|
4961
5839
|
display: "flex",
|
|
4962
5840
|
alignItems: "center",
|
|
4963
5841
|
justifyContent: "center",
|
|
@@ -4966,17 +5844,17 @@ var DocumentOutline = ({
|
|
|
4966
5844
|
const defaultEmptyIconStyle = {
|
|
4967
5845
|
width: 28,
|
|
4968
5846
|
height: 28,
|
|
4969
|
-
color: "#94a3b8"
|
|
5847
|
+
color: "var(--lp-muted, #94a3b8)"
|
|
4970
5848
|
};
|
|
4971
5849
|
const defaultEmptyTitleStyle = {
|
|
4972
5850
|
fontSize: 14,
|
|
4973
5851
|
fontWeight: 500,
|
|
4974
|
-
color: "#475569",
|
|
5852
|
+
color: "var(--lp-text, #475569)",
|
|
4975
5853
|
marginBottom: 4
|
|
4976
5854
|
};
|
|
4977
5855
|
const defaultEmptyDescriptionStyle = {
|
|
4978
5856
|
fontSize: 13,
|
|
4979
|
-
color: "#94a3b8",
|
|
5857
|
+
color: "var(--lp-muted, #94a3b8)",
|
|
4980
5858
|
textAlign: "center"
|
|
4981
5859
|
};
|
|
4982
5860
|
const defaultContainerStyle = {
|
|
@@ -5088,7 +5966,7 @@ var ThumbnailItem = React19.memo(({
|
|
|
5088
5966
|
width: "85%",
|
|
5089
5967
|
aspectRatio: "8.5 / 11",
|
|
5090
5968
|
// Standard page aspect ratio
|
|
5091
|
-
backgroundColor: "#ffffff",
|
|
5969
|
+
backgroundColor: "var(--lp-bg, #ffffff)",
|
|
5092
5970
|
borderRadius: "4px",
|
|
5093
5971
|
overflow: "hidden",
|
|
5094
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)",
|
|
@@ -5098,7 +5976,7 @@ var ThumbnailItem = React19.memo(({
|
|
|
5098
5976
|
marginTop: "6px",
|
|
5099
5977
|
fontSize: "11px",
|
|
5100
5978
|
fontWeight: 500,
|
|
5101
|
-
color: isActive ? "#3b82f6" : "#6b7280",
|
|
5979
|
+
color: isActive ? "var(--lp-accent, #3b82f6)" : "var(--lp-muted, #6b7280)",
|
|
5102
5980
|
transition: "color 0.15s ease"
|
|
5103
5981
|
};
|
|
5104
5982
|
return /* @__PURE__ */ React19.createElement(
|
|
@@ -5130,7 +6008,7 @@ var ThumbnailItem = React19.memo(({
|
|
|
5130
6008
|
display: "flex",
|
|
5131
6009
|
alignItems: "center",
|
|
5132
6010
|
justifyContent: "center",
|
|
5133
|
-
backgroundColor: "#f9fafb"
|
|
6011
|
+
backgroundColor: "var(--lp-hover, #f9fafb)"
|
|
5134
6012
|
}
|
|
5135
6013
|
},
|
|
5136
6014
|
/* @__PURE__ */ React19.createElement(
|
|
@@ -5139,7 +6017,7 @@ var ThumbnailItem = React19.memo(({
|
|
|
5139
6017
|
style: {
|
|
5140
6018
|
width: 24,
|
|
5141
6019
|
height: 24,
|
|
5142
|
-
color: "#9ca3af",
|
|
6020
|
+
color: "var(--lp-muted, #9ca3af)",
|
|
5143
6021
|
animation: "spin 1s linear infinite"
|
|
5144
6022
|
}
|
|
5145
6023
|
}
|
|
@@ -5180,7 +6058,7 @@ var ThumbnailItem = React19.memo(({
|
|
|
5180
6058
|
display: "flex",
|
|
5181
6059
|
alignItems: "center",
|
|
5182
6060
|
justifyContent: "center",
|
|
5183
|
-
backgroundColor: "#f9fafb"
|
|
6061
|
+
backgroundColor: "var(--lp-hover, #f9fafb)"
|
|
5184
6062
|
}
|
|
5185
6063
|
},
|
|
5186
6064
|
/* @__PURE__ */ React19.createElement(
|
|
@@ -5189,7 +6067,7 @@ var ThumbnailItem = React19.memo(({
|
|
|
5189
6067
|
style: {
|
|
5190
6068
|
fontSize: 24,
|
|
5191
6069
|
fontWeight: 300,
|
|
5192
|
-
color: "#d1d5db"
|
|
6070
|
+
color: "var(--lp-muted, #d1d5db)"
|
|
5193
6071
|
}
|
|
5194
6072
|
},
|
|
5195
6073
|
pageNumber
|
|
@@ -5246,7 +6124,7 @@ var ThumbnailPanel = ({
|
|
|
5246
6124
|
padding: "48px 16px"
|
|
5247
6125
|
}
|
|
5248
6126
|
},
|
|
5249
|
-
/* @__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")
|
|
5250
6128
|
);
|
|
5251
6129
|
}
|
|
5252
6130
|
return /* @__PURE__ */ React20.createElement(
|
|
@@ -5320,6 +6198,14 @@ var defaultTheme = {
|
|
|
5320
6198
|
mutedTextColor: "#6b7280",
|
|
5321
6199
|
hoverBackgroundColor: "#f9fafb"
|
|
5322
6200
|
};
|
|
6201
|
+
var defaultDarkTheme2 = {
|
|
6202
|
+
backgroundColor: "#1f1d1b",
|
|
6203
|
+
borderColor: "#3a3733",
|
|
6204
|
+
accentColor: "#7aa2f7",
|
|
6205
|
+
textColor: "#eae6e0",
|
|
6206
|
+
mutedTextColor: "#a8a29a",
|
|
6207
|
+
hoverBackgroundColor: "#2a2724"
|
|
6208
|
+
};
|
|
5323
6209
|
var LeftPanel = ({
|
|
5324
6210
|
pdfDocument,
|
|
5325
6211
|
viewer = null,
|
|
@@ -5338,6 +6224,7 @@ var LeftPanel = ({
|
|
|
5338
6224
|
onPageSelect,
|
|
5339
6225
|
thumbnailWidth = 180,
|
|
5340
6226
|
children,
|
|
6227
|
+
mode = "light",
|
|
5341
6228
|
theme: userTheme,
|
|
5342
6229
|
showFooter = true,
|
|
5343
6230
|
showToggleButton = true,
|
|
@@ -5352,7 +6239,13 @@ var LeftPanel = ({
|
|
|
5352
6239
|
outlineItemStyles,
|
|
5353
6240
|
outlineItemClassNames
|
|
5354
6241
|
}) => {
|
|
5355
|
-
const theme = useMemo3(
|
|
6242
|
+
const theme = useMemo3(
|
|
6243
|
+
() => ({
|
|
6244
|
+
...mode === "dark" ? defaultDarkTheme2 : defaultTheme,
|
|
6245
|
+
...userTheme
|
|
6246
|
+
}),
|
|
6247
|
+
[mode, userTheme]
|
|
6248
|
+
);
|
|
5356
6249
|
const [internalIsOpen, setInternalIsOpen] = useState17(true);
|
|
5357
6250
|
const [activeTab, setActiveTab] = useState17(defaultTab);
|
|
5358
6251
|
const isOpen = controlledIsOpen !== void 0 ? controlledIsOpen : internalIsOpen;
|
|
@@ -5692,6 +6585,7 @@ export {
|
|
|
5692
6585
|
extractPageTextItems,
|
|
5693
6586
|
extractSentences,
|
|
5694
6587
|
extractTextUnits,
|
|
6588
|
+
getTextPosition,
|
|
5695
6589
|
scaledPositionToViewport,
|
|
5696
6590
|
sentenceToHighlight,
|
|
5697
6591
|
useDocumentOutline,
|