@anaralabs/lector 3.14.11 → 3.14.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_tsup-dts-rollup.d.ts +28 -0
- package/dist/index.js +134 -128
- package/package.json +1 -1
|
@@ -267,6 +267,13 @@ export declare function createRecolorCanvasFactory(mapRef: RenderColorMapRef): {
|
|
|
267
267
|
};
|
|
268
268
|
};
|
|
269
269
|
|
|
270
|
+
/** Paint native selection once per page; overlapping PDF spans must not
|
|
271
|
+
* composite their translucent backgrounds on top of each other. */
|
|
272
|
+
export declare const createSelectionBackground: () => {
|
|
273
|
+
update: (selection: Selection | null, layers: Iterable<HTMLElement>) => void;
|
|
274
|
+
clear: (layer: HTMLElement) => void;
|
|
275
|
+
};
|
|
276
|
+
|
|
270
277
|
export declare const CurrentPage: ({ ...props }: HTMLProps<HTMLInputElement>) => JSX.Element;
|
|
271
278
|
|
|
272
279
|
export declare const CurrentZoom: ({ ...props }: HTMLProps<HTMLInputElement>) => JSX.Element;
|
|
@@ -305,6 +312,13 @@ export declare const getFitWidthZoom: (containerWidth: number, viewports: PageVi
|
|
|
305
312
|
|
|
306
313
|
export declare const getMidHeightOfHighlightLine: (selection: ColoredHighlight) => number;
|
|
307
314
|
|
|
315
|
+
/**
|
|
316
|
+
* Per-text-node client rects for a selection. Avoids the block-level rects
|
|
317
|
+
* that `range.getClientRects()` returns for `.textLayer` / page wrappers,
|
|
318
|
+
* which would render as full-page highlights on multi-page selections.
|
|
319
|
+
*/
|
|
320
|
+
export declare const getTextNodeClientRects: (range: Range) => TextNodeRect[];
|
|
321
|
+
|
|
308
322
|
export declare const HighlightLayer: ForwardRefExoticComponent<HighlightLayerProps & RefAttributes<HTMLDivElement>>;
|
|
309
323
|
|
|
310
324
|
declare interface HighlightLayerProps extends ComponentPropsWithoutRef<"div"> {
|
|
@@ -384,6 +398,8 @@ export declare const MAX_CANVAS_DIMENSION = 32767;
|
|
|
384
398
|
|
|
385
399
|
export declare const MAX_CANVAS_PIXELS = 16777216;
|
|
386
400
|
|
|
401
|
+
export declare const mergeTextRuns: <T extends Rect>(rects: T[]) => T[];
|
|
402
|
+
|
|
387
403
|
export declare const NextPage: () => void;
|
|
388
404
|
|
|
389
405
|
export declare const Outline: ({ children, ...props }: HTMLProps<HTMLUListElement> & {
|
|
@@ -502,6 +518,13 @@ export declare interface RecolorContextOptions {
|
|
|
502
518
|
pageArea?: number;
|
|
503
519
|
}
|
|
504
520
|
|
|
521
|
+
declare type Rect = {
|
|
522
|
+
left: number;
|
|
523
|
+
top: number;
|
|
524
|
+
width: number;
|
|
525
|
+
height: number;
|
|
526
|
+
};
|
|
527
|
+
|
|
505
528
|
/**
|
|
506
529
|
* Removes any recolor wrapper installed on the context. For long-lived
|
|
507
530
|
* contexts (the visible canvas in the no-buffer fallback, thumbnails) that
|
|
@@ -654,6 +677,11 @@ declare type TextContent = {
|
|
|
654
677
|
|
|
655
678
|
export declare const TextLayer: MemoExoticComponent<({ className, style, ...props }: HTMLProps<HTMLDivElement>) => JSX.Element>;
|
|
656
679
|
|
|
680
|
+
declare type TextNodeRect = {
|
|
681
|
+
rect: DOMRect;
|
|
682
|
+
element: Element | null;
|
|
683
|
+
};
|
|
684
|
+
|
|
657
685
|
declare interface TextPosition {
|
|
658
686
|
pageNumber: number;
|
|
659
687
|
text: string;
|
package/dist/index.js
CHANGED
|
@@ -2894,9 +2894,7 @@ var CanvasLayer = memo(
|
|
|
2894
2894
|
);
|
|
2895
2895
|
CanvasLayer.displayName = "CanvasLayer";
|
|
2896
2896
|
|
|
2897
|
-
// src/
|
|
2898
|
-
var MERGE_THRESHOLD = 2;
|
|
2899
|
-
var LAYER_ATTRIBUTION_TOLERANCE_PX = 4;
|
|
2897
|
+
// src/lib/selection-rects.ts
|
|
2900
2898
|
var getTextNodeClientRects = (range) => {
|
|
2901
2899
|
const root = range.commonAncestorContainer;
|
|
2902
2900
|
const ownerDoc = root.ownerDocument ?? document;
|
|
@@ -2948,6 +2946,39 @@ var getTextNodeClientRects = (range) => {
|
|
|
2948
2946
|
}
|
|
2949
2947
|
return results;
|
|
2950
2948
|
};
|
|
2949
|
+
var connected = (a, b) => {
|
|
2950
|
+
const overlap = Math.min(a.top + a.height, b.top + b.height) - Math.max(a.top, b.top);
|
|
2951
|
+
const gap = Math.max(a.left, b.left) - Math.min(a.left + a.width, b.left + b.width);
|
|
2952
|
+
return overlap >= Math.min(a.height, b.height) / 2 && gap <= Math.min(a.height, b.height) * 0.5;
|
|
2953
|
+
};
|
|
2954
|
+
var mergeTextRuns = (rects) => {
|
|
2955
|
+
const groups = [];
|
|
2956
|
+
for (const rect of rects) {
|
|
2957
|
+
const group = [rect];
|
|
2958
|
+
for (let i = groups.length - 1; i >= 0; i--) {
|
|
2959
|
+
if (groups[i].some((member) => connected(member, rect))) {
|
|
2960
|
+
group.push(...groups[i]);
|
|
2961
|
+
groups.splice(i, 1);
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
2964
|
+
groups.push(group);
|
|
2965
|
+
}
|
|
2966
|
+
return groups.map((group) => {
|
|
2967
|
+
const left = Math.min(...group.map((rect) => rect.left));
|
|
2968
|
+
const top = Math.min(...group.map((rect) => rect.top));
|
|
2969
|
+
return {
|
|
2970
|
+
...group[0],
|
|
2971
|
+
left,
|
|
2972
|
+
top,
|
|
2973
|
+
width: Math.max(...group.map((rect) => rect.left + rect.width)) - left,
|
|
2974
|
+
height: Math.max(...group.map((rect) => rect.top + rect.height)) - top
|
|
2975
|
+
};
|
|
2976
|
+
});
|
|
2977
|
+
};
|
|
2978
|
+
|
|
2979
|
+
// src/hooks/useSelectionDimensions.tsx
|
|
2980
|
+
var MERGE_THRESHOLD = 2;
|
|
2981
|
+
var LAYER_ATTRIBUTION_TOLERANCE_PX = 4;
|
|
2951
2982
|
var mapSelectionRectsToLayers = (range) => {
|
|
2952
2983
|
const clientRects = getTextNodeClientRects(range).filter(
|
|
2953
2984
|
({ rect }) => rect.width > 2 && rect.height > 2
|
|
@@ -2990,122 +3021,6 @@ var mapSelectionRectsToLayers = (range) => {
|
|
|
2990
3021
|
}
|
|
2991
3022
|
return result;
|
|
2992
3023
|
};
|
|
2993
|
-
var shouldMergeRects = (rect1, rect2) => {
|
|
2994
|
-
const verticalOverlap = !(rect1.top > rect2.top + rect2.height || rect2.top > rect1.top + rect1.height);
|
|
2995
|
-
const horizontallyConnected = Math.abs(rect1.left + rect1.width - rect2.left) <= MERGE_THRESHOLD || Math.abs(rect2.left + rect2.width - rect1.left) <= MERGE_THRESHOLD || rect1.left < rect2.left + rect2.width && rect2.left < rect1.left + rect1.width;
|
|
2996
|
-
return verticalOverlap && horizontallyConnected;
|
|
2997
|
-
};
|
|
2998
|
-
var consolidateHighlightRects = (rects) => {
|
|
2999
|
-
if (rects.length <= 1) return rects;
|
|
3000
|
-
const consolidated = [];
|
|
3001
|
-
const sorted = [...rects].sort((a, b) => {
|
|
3002
|
-
const pageCompare = a.pageNumber - b.pageNumber;
|
|
3003
|
-
if (pageCompare !== 0) return pageCompare;
|
|
3004
|
-
const topDiff = a.top - b.top;
|
|
3005
|
-
return Math.abs(topDiff) < 2 ? a.left - b.left : topDiff;
|
|
3006
|
-
});
|
|
3007
|
-
let current = sorted[0];
|
|
3008
|
-
if (!current) return rects;
|
|
3009
|
-
for (let i = 1; i < sorted.length; i++) {
|
|
3010
|
-
const next = sorted[i];
|
|
3011
|
-
if (!next) continue;
|
|
3012
|
-
const samePageAndLine = current.pageNumber === next.pageNumber && Math.abs(current.top - next.top) < Math.max(current.height, next.height) * 0.5;
|
|
3013
|
-
const horizontallyConnected = samePageAndLine && // Adjacent (touching or very close)
|
|
3014
|
-
(Math.abs(current.left + current.width - next.left) <= MERGE_THRESHOLD || Math.abs(next.left + next.width - current.left) <= MERGE_THRESHOLD || // Overlapping
|
|
3015
|
-
current.left < next.left + next.width && next.left < current.left + current.width || // Very close (small gap)
|
|
3016
|
-
Math.abs(current.left + current.width - next.left) <= current.height * 0.2);
|
|
3017
|
-
if (horizontallyConnected) {
|
|
3018
|
-
const newLeft = Math.min(current.left, next.left);
|
|
3019
|
-
const newRight = Math.max(
|
|
3020
|
-
current.left + current.width,
|
|
3021
|
-
next.left + next.width
|
|
3022
|
-
);
|
|
3023
|
-
const newTop = Math.min(current.top, next.top);
|
|
3024
|
-
const newBottom = Math.max(
|
|
3025
|
-
current.top + current.height,
|
|
3026
|
-
next.top + next.height
|
|
3027
|
-
);
|
|
3028
|
-
current = {
|
|
3029
|
-
left: newLeft,
|
|
3030
|
-
top: newTop,
|
|
3031
|
-
width: newRight - newLeft,
|
|
3032
|
-
height: newBottom - newTop,
|
|
3033
|
-
pageNumber: current.pageNumber
|
|
3034
|
-
};
|
|
3035
|
-
} else {
|
|
3036
|
-
consolidated.push(current);
|
|
3037
|
-
current = next;
|
|
3038
|
-
}
|
|
3039
|
-
}
|
|
3040
|
-
if (current) {
|
|
3041
|
-
consolidated.push(current);
|
|
3042
|
-
}
|
|
3043
|
-
return consolidated;
|
|
3044
|
-
};
|
|
3045
|
-
var consolidateRects = (rects) => {
|
|
3046
|
-
if (rects.length <= 1) return rects;
|
|
3047
|
-
const result = [];
|
|
3048
|
-
const visited = /* @__PURE__ */ new Set();
|
|
3049
|
-
for (let i = 0; i < rects.length; i++) {
|
|
3050
|
-
if (visited.has(i)) continue;
|
|
3051
|
-
const currentRect = rects[i];
|
|
3052
|
-
if (!currentRect) continue;
|
|
3053
|
-
const currentGroup = [currentRect];
|
|
3054
|
-
visited.add(i);
|
|
3055
|
-
let foundNew = true;
|
|
3056
|
-
while (foundNew) {
|
|
3057
|
-
foundNew = false;
|
|
3058
|
-
for (let j = 0; j < rects.length; j++) {
|
|
3059
|
-
if (visited.has(j)) continue;
|
|
3060
|
-
const candidateRect = rects[j];
|
|
3061
|
-
if (!candidateRect) continue;
|
|
3062
|
-
const shouldMergeWithGroup = currentGroup.some(
|
|
3063
|
-
(groupRect) => doRectsOverlap(groupRect, candidateRect)
|
|
3064
|
-
);
|
|
3065
|
-
if (shouldMergeWithGroup) {
|
|
3066
|
-
currentGroup.push(candidateRect);
|
|
3067
|
-
visited.add(j);
|
|
3068
|
-
foundNew = true;
|
|
3069
|
-
}
|
|
3070
|
-
}
|
|
3071
|
-
}
|
|
3072
|
-
result.push(mergeRectGroup(currentGroup));
|
|
3073
|
-
}
|
|
3074
|
-
return result;
|
|
3075
|
-
};
|
|
3076
|
-
var doRectsOverlap = (rect1, rect2) => {
|
|
3077
|
-
const horizontalOverlap = rect1.left < rect2.left + rect2.width && rect2.left < rect1.left + rect1.width;
|
|
3078
|
-
const verticalOverlap = rect1.top < rect2.top + rect2.height && rect2.top < rect1.top + rect1.height;
|
|
3079
|
-
const closeEnough = shouldMergeRects(rect1, rect2);
|
|
3080
|
-
return horizontalOverlap && verticalOverlap || closeEnough;
|
|
3081
|
-
};
|
|
3082
|
-
var mergeRectGroup = (rects) => {
|
|
3083
|
-
if (rects.length === 1) {
|
|
3084
|
-
const rect = rects[0];
|
|
3085
|
-
if (!rect) throw new Error("Invalid rect in group");
|
|
3086
|
-
return rect;
|
|
3087
|
-
}
|
|
3088
|
-
const firstRect = rects[0];
|
|
3089
|
-
if (!firstRect) throw new Error("Invalid first rect in group");
|
|
3090
|
-
let minLeft = firstRect.left;
|
|
3091
|
-
let minTop = firstRect.top;
|
|
3092
|
-
let maxRight = firstRect.left + firstRect.width;
|
|
3093
|
-
let maxBottom = firstRect.top + firstRect.height;
|
|
3094
|
-
rects.forEach((rect) => {
|
|
3095
|
-
if (!rect) return;
|
|
3096
|
-
minLeft = Math.min(minLeft, rect.left);
|
|
3097
|
-
minTop = Math.min(minTop, rect.top);
|
|
3098
|
-
maxRight = Math.max(maxRight, rect.left + rect.width);
|
|
3099
|
-
maxBottom = Math.max(maxBottom, rect.top + rect.height);
|
|
3100
|
-
});
|
|
3101
|
-
return {
|
|
3102
|
-
left: minLeft,
|
|
3103
|
-
top: minTop,
|
|
3104
|
-
width: maxRight - minLeft,
|
|
3105
|
-
height: maxBottom - minTop,
|
|
3106
|
-
pageNumber: firstRect.pageNumber
|
|
3107
|
-
};
|
|
3108
|
-
};
|
|
3109
3024
|
var useSelectionDimensions = () => {
|
|
3110
3025
|
const store = PDFStore.useContext();
|
|
3111
3026
|
const getAnnotationDimension = () => {
|
|
@@ -3178,14 +3093,7 @@ var useSelectionDimensions = () => {
|
|
|
3178
3093
|
}
|
|
3179
3094
|
});
|
|
3180
3095
|
textLayerMapHighlight.forEach((rects) => {
|
|
3181
|
-
|
|
3182
|
-
if (rects.length === 1) {
|
|
3183
|
-
highlightRects.push(...rects);
|
|
3184
|
-
} else {
|
|
3185
|
-
const consolidated = consolidateHighlightRects(rects);
|
|
3186
|
-
highlightRects.push(...consolidated);
|
|
3187
|
-
}
|
|
3188
|
-
}
|
|
3096
|
+
highlightRects.push(...mergeTextRuns(rects));
|
|
3189
3097
|
});
|
|
3190
3098
|
textLayerMapUnderline.forEach((rects) => {
|
|
3191
3099
|
if (rects.length > 0) {
|
|
@@ -3332,7 +3240,7 @@ var useSelectionDimensions = () => {
|
|
|
3332
3240
|
});
|
|
3333
3241
|
textLayerMap.forEach((rects) => {
|
|
3334
3242
|
if (rects.length > 0) {
|
|
3335
|
-
const consolidated =
|
|
3243
|
+
const consolidated = mergeTextRuns(rects);
|
|
3336
3244
|
highlights.push(...consolidated);
|
|
3337
3245
|
}
|
|
3338
3246
|
});
|
|
@@ -3777,13 +3685,110 @@ var HighlightLayer = forwardRef(({ asChild, className, style, ...props }, ref) =
|
|
|
3777
3685
|
}) });
|
|
3778
3686
|
});
|
|
3779
3687
|
HighlightLayer.displayName = "HighlightLayer";
|
|
3688
|
+
|
|
3689
|
+
// src/lib/selection-background.ts
|
|
3690
|
+
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
3691
|
+
var createSelectionBackground = () => {
|
|
3692
|
+
const overlays = /* @__PURE__ */ new Map();
|
|
3693
|
+
let style;
|
|
3694
|
+
const clear = (layer) => {
|
|
3695
|
+
overlays.get(layer)?.remove();
|
|
3696
|
+
overlays.delete(layer);
|
|
3697
|
+
layer.removeAttribute("data-lector-selection-active");
|
|
3698
|
+
if (overlays.size === 0) {
|
|
3699
|
+
style?.remove();
|
|
3700
|
+
style = void 0;
|
|
3701
|
+
}
|
|
3702
|
+
};
|
|
3703
|
+
const update = (selection, layers) => {
|
|
3704
|
+
const registered = new Set(layers);
|
|
3705
|
+
const byLayer = /* @__PURE__ */ new Map();
|
|
3706
|
+
if (selection && !selection.isCollapsed) {
|
|
3707
|
+
for (let i = 0; i < selection.rangeCount; i++) {
|
|
3708
|
+
for (const { rect, element } of getTextNodeClientRects(
|
|
3709
|
+
selection.getRangeAt(i)
|
|
3710
|
+
)) {
|
|
3711
|
+
const layer = element?.closest(".textLayer");
|
|
3712
|
+
if (!layer || !element || !registered.has(layer) || rect.width <= 0 || rect.height <= 0)
|
|
3713
|
+
continue;
|
|
3714
|
+
let entry = byLayer.get(layer);
|
|
3715
|
+
if (!entry) {
|
|
3716
|
+
entry = { rects: [], element };
|
|
3717
|
+
byLayer.set(layer, entry);
|
|
3718
|
+
}
|
|
3719
|
+
entry.rects.push(rect);
|
|
3720
|
+
}
|
|
3721
|
+
}
|
|
3722
|
+
}
|
|
3723
|
+
for (const layer of registered) {
|
|
3724
|
+
const entry = byLayer.get(layer);
|
|
3725
|
+
if (!entry) {
|
|
3726
|
+
clear(layer);
|
|
3727
|
+
continue;
|
|
3728
|
+
}
|
|
3729
|
+
layer.removeAttribute("data-lector-selection-active");
|
|
3730
|
+
const color = getComputedStyle(
|
|
3731
|
+
entry.element,
|
|
3732
|
+
"::selection"
|
|
3733
|
+
).backgroundColor;
|
|
3734
|
+
if (!color || color === "transparent" || color === "rgba(0, 0, 0, 0)") {
|
|
3735
|
+
clear(layer);
|
|
3736
|
+
continue;
|
|
3737
|
+
}
|
|
3738
|
+
const bounds = layer.getBoundingClientRect();
|
|
3739
|
+
if (!bounds.width || !bounds.height || !layer.clientWidth || !layer.clientHeight) {
|
|
3740
|
+
clear(layer);
|
|
3741
|
+
continue;
|
|
3742
|
+
}
|
|
3743
|
+
const scaleX = bounds.width / layer.clientWidth;
|
|
3744
|
+
const scaleY = bounds.height / layer.clientHeight;
|
|
3745
|
+
const rects = mergeTextRuns(
|
|
3746
|
+
entry.rects.map((rect) => ({
|
|
3747
|
+
left: (rect.left - bounds.left) / scaleX,
|
|
3748
|
+
top: (rect.top - bounds.top) / scaleY,
|
|
3749
|
+
width: rect.width / scaleX,
|
|
3750
|
+
height: rect.height / scaleY
|
|
3751
|
+
}))
|
|
3752
|
+
);
|
|
3753
|
+
let overlay = overlays.get(layer);
|
|
3754
|
+
if (!overlay) {
|
|
3755
|
+
overlay = document.createElementNS(SVG_NS, "svg");
|
|
3756
|
+
overlay.dataset.lectorSelection = "";
|
|
3757
|
+
overlay.setAttribute("aria-hidden", "true");
|
|
3758
|
+
overlay.style.cssText = "position:absolute;inset:0;width:100%;height:100%;pointer-events:none;user-select:none;transform:none;z-index:0;overflow:visible";
|
|
3759
|
+
overlay.append(document.createElementNS(SVG_NS, "path"));
|
|
3760
|
+
layer.append(overlay);
|
|
3761
|
+
overlays.set(layer, overlay);
|
|
3762
|
+
}
|
|
3763
|
+
const path = overlay.firstElementChild;
|
|
3764
|
+
path.setAttribute(
|
|
3765
|
+
"d",
|
|
3766
|
+
rects.map(
|
|
3767
|
+
(rect) => `M${rect.left} ${rect.top}h${rect.width}v${rect.height}h${-rect.width}Z`
|
|
3768
|
+
).join(" ")
|
|
3769
|
+
);
|
|
3770
|
+
path.setAttribute("fill", color);
|
|
3771
|
+
if (!style) {
|
|
3772
|
+
style = document.createElement("style");
|
|
3773
|
+
style.textContent = ".textLayer[data-lector-selection-active] ::selection { background-color: transparent !important; }";
|
|
3774
|
+
document.head.append(style);
|
|
3775
|
+
}
|
|
3776
|
+
layer.setAttribute("data-lector-selection-active", "");
|
|
3777
|
+
}
|
|
3778
|
+
};
|
|
3779
|
+
return { update, clear };
|
|
3780
|
+
};
|
|
3781
|
+
|
|
3782
|
+
// src/hooks/layers/useTextLayer.tsx
|
|
3780
3783
|
var createTextSelectionManager = () => {
|
|
3781
3784
|
const textLayers = /* @__PURE__ */ new Map();
|
|
3785
|
+
const selectionBackground = createSelectionBackground();
|
|
3782
3786
|
let selectionChangeAbortController = null;
|
|
3783
3787
|
let isPointerDown = false;
|
|
3784
3788
|
let prevRange = null;
|
|
3785
3789
|
let isFirefox;
|
|
3786
3790
|
const removeGlobalSelectionListener = (textLayerDiv) => {
|
|
3791
|
+
selectionBackground.clear(textLayerDiv);
|
|
3787
3792
|
textLayers.delete(textLayerDiv);
|
|
3788
3793
|
if (textLayers.size === 0) {
|
|
3789
3794
|
selectionChangeAbortController?.abort();
|
|
@@ -3840,6 +3845,7 @@ var createTextSelectionManager = () => {
|
|
|
3840
3845
|
"selectionchange",
|
|
3841
3846
|
() => {
|
|
3842
3847
|
const selection = document.getSelection();
|
|
3848
|
+
selectionBackground.update(selection, textLayers.keys());
|
|
3843
3849
|
if (!selection || selection.rangeCount === 0) {
|
|
3844
3850
|
textLayers.forEach(reset);
|
|
3845
3851
|
return;
|