@epam/pdf-highlighter-kit 0.0.4 → 0.0.6
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 +88 -3
- package/dist/PDFHighlightViewer.d.ts +12 -3
- package/dist/PDFHighlightViewer.d.ts.map +1 -1
- package/dist/PDFHighlightViewer.js +210 -62
- package/dist/PDFHighlightViewer.js.map +1 -1
- package/dist/api.d.ts +4 -2
- package/dist/api.d.ts.map +1 -1
- package/dist/core/pdf-engine.d.ts +7 -1
- package/dist/core/pdf-engine.d.ts.map +1 -1
- package/dist/core/pdf-engine.js +61 -0
- package/dist/core/pdf-engine.js.map +1 -1
- package/dist/core/unified-layer-builder.d.ts +1 -0
- package/dist/core/unified-layer-builder.d.ts.map +1 -1
- package/dist/core/unified-layer-builder.js +54 -4
- package/dist/core/unified-layer-builder.js.map +1 -1
- package/dist/index.d.ts +2 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -42
- package/dist/index.js.map +1 -1
- package/dist/pdf-highlight-viewer.css +39 -0
- package/dist/types.d.ts +37 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +5 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/label-style.d.ts +20 -0
- package/dist/utils/label-style.d.ts.map +1 -0
- package/dist/utils/label-style.js +52 -0
- package/dist/utils/label-style.js.map +1 -0
- package/dist/utils/sanitize-icon-html.d.ts +12 -0
- package/dist/utils/sanitize-icon-html.d.ts.map +1 -0
- package/dist/utils/sanitize-icon-html.js +139 -0
- package/dist/utils/sanitize-icon-html.js.map +1 -0
- package/package.json +1 -1
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import { ZoomMode, } from './types';
|
|
1
2
|
import { PDFEngine } from './core/pdf-engine';
|
|
2
3
|
import { ViewportManager } from './core/viewport-manager';
|
|
3
4
|
import { UnifiedLayerBuilder } from './core/unified-layer-builder';
|
|
4
5
|
import { UnifiedInteractionHandler } from './core/interaction-handler';
|
|
5
6
|
import { PerformanceOptimizer } from './core/performance-optimizer';
|
|
6
7
|
import { buildHighlightsIndex } from './utils/highlight-adapter';
|
|
8
|
+
import { applyLabelStyle, applyIconStyle, normalizeSize } from './utils/label-style';
|
|
9
|
+
import { sanitizeIconHtml } from './utils/sanitize-icon-html';
|
|
10
|
+
const CONTAINER_PADDING = 40;
|
|
11
|
+
const ZOOM_STEP = 1.2;
|
|
7
12
|
export class PDFHighlightViewer {
|
|
8
13
|
constructor() {
|
|
9
14
|
this.container = null;
|
|
@@ -123,6 +128,7 @@ export class PDFHighlightViewer {
|
|
|
123
128
|
interactionMode: 'hybrid',
|
|
124
129
|
performanceMode: false,
|
|
125
130
|
accessibility: true,
|
|
131
|
+
bboxOrigin: 'bottom-right',
|
|
126
132
|
};
|
|
127
133
|
this.pdfEngine = new PDFEngine(this.options);
|
|
128
134
|
this.viewportManager = new ViewportManager(this.options.bufferPages, this.options.maxCachedPages);
|
|
@@ -407,22 +413,16 @@ export class PDFHighlightViewer {
|
|
|
407
413
|
const cached = this.pageDimensions.get(pageNumber);
|
|
408
414
|
if (cached)
|
|
409
415
|
return cached;
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
return dimensions;
|
|
421
|
-
}
|
|
422
|
-
catch (error) {
|
|
423
|
-
console.error(`Failed to get dimensions for page ${pageNumber}:`, error);
|
|
424
|
-
return { width: 600, height: this.defaultPageHeight };
|
|
425
|
-
}
|
|
416
|
+
// Get PDF page and its viewport at current scale
|
|
417
|
+
const page = await this.pdfEngine.getPage(pageNumber);
|
|
418
|
+
const viewport = page.getViewport({ scale: this.currentScale });
|
|
419
|
+
const dimensions = {
|
|
420
|
+
width: viewport.width,
|
|
421
|
+
height: viewport.height,
|
|
422
|
+
};
|
|
423
|
+
// Cache the dimensions
|
|
424
|
+
this.pageDimensions.set(pageNumber, dimensions);
|
|
425
|
+
return dimensions;
|
|
426
426
|
}
|
|
427
427
|
/**
|
|
428
428
|
* Setup accessibility features
|
|
@@ -462,11 +462,11 @@ export class PDFHighlightViewer {
|
|
|
462
462
|
break;
|
|
463
463
|
case '+':
|
|
464
464
|
case '=':
|
|
465
|
-
this.setZoom(this.currentScale *
|
|
465
|
+
this.setZoom(this.currentScale * ZOOM_STEP);
|
|
466
466
|
event.preventDefault();
|
|
467
467
|
break;
|
|
468
468
|
case '-':
|
|
469
|
-
this.setZoom(this.currentScale /
|
|
469
|
+
this.setZoom(this.currentScale / ZOOM_STEP);
|
|
470
470
|
event.preventDefault();
|
|
471
471
|
break;
|
|
472
472
|
}
|
|
@@ -508,31 +508,17 @@ export class PDFHighlightViewer {
|
|
|
508
508
|
// Clear existing containers
|
|
509
509
|
this.pdfContainer.innerHTML = '';
|
|
510
510
|
this.pageContainers.clear();
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
try {
|
|
514
|
-
const firstPageDimensions = await this.getPageDimensions(1);
|
|
515
|
-
avgPageHeight = firstPageDimensions.height;
|
|
516
|
-
}
|
|
517
|
-
catch (_error) {
|
|
518
|
-
console.warn('Could not get first page dimensions, using default');
|
|
519
|
-
}
|
|
511
|
+
const firstPageDimensions = await this.getPageDimensions(1);
|
|
512
|
+
const avgPageHeight = firstPageDimensions.height;
|
|
520
513
|
for (let pageNumber = 1; pageNumber <= this.totalPages; pageNumber++) {
|
|
521
514
|
const pageContainer = document.createElement('div');
|
|
522
515
|
pageContainer.className = 'pdf-page-container';
|
|
523
516
|
pageContainer.setAttribute('data-page-number', pageNumber.toString());
|
|
524
517
|
pageContainer.style.marginBottom = '20px';
|
|
525
518
|
pageContainer.style.position = 'relative';
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
pageContainer.style.height = `${dimensions.height}px`;
|
|
530
|
-
pageContainer.style.width = `${dimensions.width}px`;
|
|
531
|
-
}
|
|
532
|
-
catch (_error) {
|
|
533
|
-
// Use average height as fallback
|
|
534
|
-
pageContainer.style.height = `${avgPageHeight}px`;
|
|
535
|
-
}
|
|
519
|
+
const dimensions = await this.getPageDimensions(pageNumber);
|
|
520
|
+
pageContainer.style.height = `${dimensions.height}px`;
|
|
521
|
+
pageContainer.style.width = `${dimensions.width}px`;
|
|
536
522
|
this.pdfContainer.appendChild(pageContainer);
|
|
537
523
|
this.pageContainers.set(pageNumber, pageContainer);
|
|
538
524
|
}
|
|
@@ -638,21 +624,50 @@ export class PDFHighlightViewer {
|
|
|
638
624
|
getZoom() {
|
|
639
625
|
return this.currentScale;
|
|
640
626
|
}
|
|
641
|
-
setZoom(
|
|
627
|
+
setZoom(value) {
|
|
628
|
+
if (value === ZoomMode.AUTO) {
|
|
629
|
+
void this.setAutoZoom();
|
|
630
|
+
}
|
|
631
|
+
else if (value === ZoomMode.PAGE_FIT) {
|
|
632
|
+
void this.setPageFitZoom();
|
|
633
|
+
}
|
|
634
|
+
else {
|
|
635
|
+
this.applyZoom(value);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
applyZoom(scale) {
|
|
642
639
|
const previousScale = this.currentScale;
|
|
643
640
|
this.currentScale = Math.max(0.5, Math.min(5.0, scale));
|
|
644
641
|
// Re-render visible pages with new scale
|
|
645
642
|
this.reRenderVisiblePages();
|
|
646
643
|
this.emit('zoomChanged', { scale: this.currentScale, previousScale });
|
|
647
644
|
}
|
|
645
|
+
async setAutoZoom() {
|
|
646
|
+
const scales = await this.computeFitScales();
|
|
647
|
+
this.applyZoom(scales.scaleX);
|
|
648
|
+
}
|
|
649
|
+
async setPageFitZoom() {
|
|
650
|
+
const scales = await this.computeFitScales();
|
|
651
|
+
this.applyZoom(Math.min(scales.scaleX, scales.scaleY));
|
|
652
|
+
}
|
|
653
|
+
async computeFitScales() {
|
|
654
|
+
if (!this.container) {
|
|
655
|
+
return { scaleX: 1, scaleY: 1 };
|
|
656
|
+
}
|
|
657
|
+
const page = await this.pdfEngine.getPage(this.currentPage || 1);
|
|
658
|
+
const viewport = page.getViewport({ scale: 1 });
|
|
659
|
+
const scaleX = (this.container.clientWidth - CONTAINER_PADDING) / viewport.width;
|
|
660
|
+
const scaleY = (this.container.clientHeight - CONTAINER_PADDING) / viewport.height;
|
|
661
|
+
return { scaleX, scaleY };
|
|
662
|
+
}
|
|
648
663
|
zoomIn() {
|
|
649
|
-
this.
|
|
664
|
+
this.applyZoom(this.currentScale * ZOOM_STEP);
|
|
650
665
|
}
|
|
651
666
|
zoomOut() {
|
|
652
|
-
this.
|
|
667
|
+
this.applyZoom(this.currentScale / ZOOM_STEP);
|
|
653
668
|
}
|
|
654
669
|
resetZoom() {
|
|
655
|
-
this.
|
|
670
|
+
this.applyZoom(1.5);
|
|
656
671
|
}
|
|
657
672
|
getCurrentPage() {
|
|
658
673
|
return this.currentPage;
|
|
@@ -660,6 +675,19 @@ export class PDFHighlightViewer {
|
|
|
660
675
|
getTotalPages() {
|
|
661
676
|
return this.totalPages;
|
|
662
677
|
}
|
|
678
|
+
async getThumbnails(pageNumbers, options) {
|
|
679
|
+
return this.pdfEngine.getThumbnails(pageNumbers, options);
|
|
680
|
+
}
|
|
681
|
+
async getThumbnailsDataUrl(pageNumbers, options) {
|
|
682
|
+
const canvases = await this.getThumbnails(pageNumbers, options);
|
|
683
|
+
const format = options?.format ?? 'image/webp';
|
|
684
|
+
const quality = options?.quality ?? 0.85;
|
|
685
|
+
const result = new Map();
|
|
686
|
+
canvases.forEach((canvas, pageNumber) => {
|
|
687
|
+
result.set(pageNumber, canvas.toDataURL(format, quality));
|
|
688
|
+
});
|
|
689
|
+
return result;
|
|
690
|
+
}
|
|
663
691
|
// =============================================================================
|
|
664
692
|
// Text Selection Management
|
|
665
693
|
// =============================================================================
|
|
@@ -806,7 +834,20 @@ export class PDFHighlightViewer {
|
|
|
806
834
|
const pageData = this.pdfEngine.getPageData(pageNumber);
|
|
807
835
|
if (!pageData?.textContent)
|
|
808
836
|
return;
|
|
809
|
-
|
|
837
|
+
const normalizedHighlights = this.highlightsIndex.highlights.map((highlight) => ({
|
|
838
|
+
...highlight,
|
|
839
|
+
bboxes: highlight.bboxes.map((bbox) => {
|
|
840
|
+
const normalized = this.normalizeBBoxForPage(bbox, bbox.page);
|
|
841
|
+
return {
|
|
842
|
+
...bbox,
|
|
843
|
+
x1: normalized.x1,
|
|
844
|
+
y1: normalized.y1,
|
|
845
|
+
x2: normalized.x2,
|
|
846
|
+
y2: normalized.y2,
|
|
847
|
+
};
|
|
848
|
+
}),
|
|
849
|
+
}));
|
|
850
|
+
this.layerBuilder.updateHighlights(pageContainer, normalizedHighlights, pageNumber, pageData.textContent, this.currentScale);
|
|
810
851
|
}
|
|
811
852
|
/**
|
|
812
853
|
* Update all unified layers
|
|
@@ -884,7 +925,14 @@ export class PDFHighlightViewer {
|
|
|
884
925
|
for (const h of this.highlightsIndex.highlights) {
|
|
885
926
|
for (let i = 0; i < h.bboxes.length; i++) {
|
|
886
927
|
const b = h.bboxes[i];
|
|
887
|
-
|
|
928
|
+
const normalized = this.normalizeBBoxForPage(b, b.page);
|
|
929
|
+
list.push({
|
|
930
|
+
termId: h.id,
|
|
931
|
+
pageNumber: b.page,
|
|
932
|
+
occurrenceIndex: i,
|
|
933
|
+
x1: normalized.x1,
|
|
934
|
+
y1: normalized.y1,
|
|
935
|
+
});
|
|
888
936
|
}
|
|
889
937
|
}
|
|
890
938
|
list.sort((a, b) => a.pageNumber - b.pageNumber || a.y1 - b.y1 || a.x1 - b.x1);
|
|
@@ -898,6 +946,7 @@ export class PDFHighlightViewer {
|
|
|
898
946
|
if (!bbox)
|
|
899
947
|
return;
|
|
900
948
|
const page = bbox.page;
|
|
949
|
+
const normalizedBBox = this.normalizeBBoxForPage(bbox, page);
|
|
901
950
|
this.highlightSelectedTerm(termId);
|
|
902
951
|
this.setPage(page);
|
|
903
952
|
void this.renderPage(page)
|
|
@@ -908,7 +957,7 @@ export class PDFHighlightViewer {
|
|
|
908
957
|
return;
|
|
909
958
|
}
|
|
910
959
|
const pageTop = pageContainer.offsetTop;
|
|
911
|
-
const y =
|
|
960
|
+
const y = normalizedBBox.y1 * this.currentScale;
|
|
912
961
|
this.container.scrollTop = Math.max(0, pageTop + y - 60);
|
|
913
962
|
this.emit('navigationComplete', { termId, pageNumber: page, occurrenceIndex });
|
|
914
963
|
})
|
|
@@ -938,7 +987,14 @@ export class PDFHighlightViewer {
|
|
|
938
987
|
if (!this.container)
|
|
939
988
|
return;
|
|
940
989
|
const pageTop = this.getPageScrollTop(pageNumber);
|
|
941
|
-
const
|
|
990
|
+
const origin = this.options.bboxOrigin ?? 'bottom-right';
|
|
991
|
+
const pixelDimensions = this.pageDimensions.get(pageNumber);
|
|
992
|
+
if (!pixelDimensions) {
|
|
993
|
+
throw new Error(`Page dimensions for page ${pageNumber} are not available`);
|
|
994
|
+
}
|
|
995
|
+
const dimensions = this.toPageCoordinateDimensions(pixelDimensions);
|
|
996
|
+
const normalizedY = origin.startsWith('bottom') ? dimensions.height - y : y;
|
|
997
|
+
const targetY = pageTop + normalizedY * this.currentScale - this.container.clientHeight * 0.3;
|
|
942
998
|
this.container.scrollTop = Math.max(0, targetY);
|
|
943
999
|
this.emit('coordinateNavigation', { pageNumber, x, y });
|
|
944
1000
|
}
|
|
@@ -1125,15 +1181,16 @@ export class PDFHighlightViewer {
|
|
|
1125
1181
|
const bbox = highlight.bboxes[bboxIndex];
|
|
1126
1182
|
if (bbox.page !== pageNumber)
|
|
1127
1183
|
continue;
|
|
1184
|
+
const normalizedBBox = this.normalizeBBoxForPage(bbox, pageNumber);
|
|
1128
1185
|
const highlightDiv = document.createElement('div');
|
|
1129
1186
|
highlightDiv.className = 'highlight';
|
|
1130
1187
|
highlightDiv.setAttribute('data-term-id', highlight.id);
|
|
1131
1188
|
highlightDiv.setAttribute('data-page', String(pageNumber));
|
|
1132
1189
|
highlightDiv.setAttribute('data-bbox-index', String(bboxIndex));
|
|
1133
|
-
const left =
|
|
1134
|
-
const top =
|
|
1135
|
-
const width = (
|
|
1136
|
-
const height = (
|
|
1190
|
+
const left = normalizedBBox.x1 * scale;
|
|
1191
|
+
const top = normalizedBBox.y1 * scale;
|
|
1192
|
+
const width = (normalizedBBox.x2 - normalizedBBox.x1) * scale;
|
|
1193
|
+
const height = (normalizedBBox.y2 - normalizedBBox.y1) * scale;
|
|
1137
1194
|
highlightDiv.style.position = 'absolute';
|
|
1138
1195
|
highlightDiv.style.left = `${left}px`;
|
|
1139
1196
|
highlightDiv.style.top = `${top}px`;
|
|
@@ -1147,27 +1204,32 @@ export class PDFHighlightViewer {
|
|
|
1147
1204
|
highlightDiv.style.userSelect = 'none';
|
|
1148
1205
|
highlightDiv.style.mixBlendMode = 'multiply';
|
|
1149
1206
|
const baseOpacity = typeof style?.opacity === 'number' ? style.opacity : 0.3;
|
|
1150
|
-
const overlappingCount = this.countOverlappingHighlights(highlightLayer,
|
|
1207
|
+
const overlappingCount = this.countOverlappingHighlights(highlightLayer, normalizedBBox, scale);
|
|
1151
1208
|
const effectiveOpacity = Math.max(0.05, baseOpacity / Math.max(1, overlappingCount * 0.7));
|
|
1152
1209
|
highlightDiv.style.opacity = effectiveOpacity.toString();
|
|
1153
1210
|
highlightDiv.dataset.originalOpacity = effectiveOpacity.toString();
|
|
1154
1211
|
const hoverOpacity = typeof style?.hoverOpacity === 'number'
|
|
1155
1212
|
? style.hoverOpacity
|
|
1156
1213
|
: Math.min(0.6, effectiveOpacity + 0.2);
|
|
1214
|
+
const labelColor = style?.borderColor ?? style?.backgroundColor ?? '#666666';
|
|
1157
1215
|
highlightDiv.addEventListener('mouseenter', () => {
|
|
1158
1216
|
if (this.options.highlightsConfig?.enableMultilineHover) {
|
|
1159
|
-
const same = highlightLayer.querySelectorAll(
|
|
1217
|
+
const same = highlightLayer.querySelectorAll(`.highlight[data-term-id="${highlight.id}"]`);
|
|
1160
1218
|
same.forEach((el) => (el.style.opacity = String(hoverOpacity)));
|
|
1161
|
-
const other = highlightLayer.querySelectorAll(
|
|
1219
|
+
const other = highlightLayer.querySelectorAll(`.highlight[data-term-id]:not([data-term-id="${highlight.id}"])`);
|
|
1162
1220
|
other.forEach((el) => (el.style.opacity = '0.1'));
|
|
1163
1221
|
}
|
|
1164
1222
|
else {
|
|
1165
1223
|
highlightDiv.style.opacity = String(hoverOpacity);
|
|
1166
1224
|
}
|
|
1225
|
+
const labelEl = highlightDiv.querySelector('.highlight-label');
|
|
1226
|
+
if (labelEl) {
|
|
1227
|
+
labelEl.style.borderColor = borderColor;
|
|
1228
|
+
}
|
|
1167
1229
|
});
|
|
1168
1230
|
highlightDiv.addEventListener('mouseleave', () => {
|
|
1169
1231
|
if (this.options.highlightsConfig?.enableMultilineHover) {
|
|
1170
|
-
const all = highlightLayer.querySelectorAll(
|
|
1232
|
+
const all = highlightLayer.querySelectorAll('.highlight[data-term-id]');
|
|
1171
1233
|
all.forEach((el) => {
|
|
1172
1234
|
const original = el.dataset.originalOpacity ?? '0.3';
|
|
1173
1235
|
el.style.opacity = original;
|
|
@@ -1176,8 +1238,50 @@ export class PDFHighlightViewer {
|
|
|
1176
1238
|
else {
|
|
1177
1239
|
highlightDiv.style.opacity = highlightDiv.dataset.originalOpacity ?? '0.3';
|
|
1178
1240
|
}
|
|
1241
|
+
const labelEl = highlightDiv.querySelector('.highlight-label');
|
|
1242
|
+
if (labelEl) {
|
|
1243
|
+
labelEl.style.borderColor = labelColor;
|
|
1244
|
+
}
|
|
1179
1245
|
});
|
|
1180
1246
|
highlightLayer.appendChild(highlightDiv);
|
|
1247
|
+
if (highlight.label || highlight.beforeIcon) {
|
|
1248
|
+
const labelEl = document.createElement('span');
|
|
1249
|
+
labelEl.className = 'highlight-label';
|
|
1250
|
+
labelEl.setAttribute('data-term-id', highlight.id);
|
|
1251
|
+
labelEl.style.position = 'absolute';
|
|
1252
|
+
labelEl.style.left = '0';
|
|
1253
|
+
labelEl.style.transform = 'translateX(-100%)';
|
|
1254
|
+
labelEl.style.top = '-1px';
|
|
1255
|
+
labelEl.style.display = 'flex';
|
|
1256
|
+
labelEl.style.alignItems = 'center';
|
|
1257
|
+
labelEl.style.justifyContent = 'flex-end';
|
|
1258
|
+
labelEl.style.gap = '4px';
|
|
1259
|
+
labelEl.style.pointerEvents = 'auto';
|
|
1260
|
+
labelEl.style.cursor = 'pointer';
|
|
1261
|
+
labelEl.style.whiteSpace = 'nowrap';
|
|
1262
|
+
labelEl.style.border = `1px solid ${labelColor}`;
|
|
1263
|
+
applyLabelStyle(labelEl, highlight.labelStyle);
|
|
1264
|
+
if (highlight.beforeIcon) {
|
|
1265
|
+
const iconWrap = document.createElement('span');
|
|
1266
|
+
iconWrap.className = 'highlight-label-icon';
|
|
1267
|
+
iconWrap.innerHTML = sanitizeIconHtml(highlight.beforeIcon);
|
|
1268
|
+
const svg = iconWrap.querySelector('svg');
|
|
1269
|
+
if (svg) {
|
|
1270
|
+
svg.removeAttribute('width');
|
|
1271
|
+
svg.removeAttribute('height');
|
|
1272
|
+
}
|
|
1273
|
+
const iconSize = highlight.labelStyle?.iconSize;
|
|
1274
|
+
const size = normalizeSize(iconSize);
|
|
1275
|
+
iconWrap.style.width = size;
|
|
1276
|
+
iconWrap.style.height = size;
|
|
1277
|
+
applyIconStyle(iconWrap, highlight.labelStyle);
|
|
1278
|
+
labelEl.appendChild(iconWrap);
|
|
1279
|
+
}
|
|
1280
|
+
if (highlight.label) {
|
|
1281
|
+
labelEl.appendChild(document.createTextNode(highlight.label));
|
|
1282
|
+
}
|
|
1283
|
+
highlightDiv.appendChild(labelEl);
|
|
1284
|
+
}
|
|
1181
1285
|
}
|
|
1182
1286
|
}
|
|
1183
1287
|
pageContainer.appendChild(highlightLayer);
|
|
@@ -1201,6 +1305,12 @@ export class PDFHighlightViewer {
|
|
|
1201
1305
|
getHighlightStyle(termId) {
|
|
1202
1306
|
return this.getHighlightById(termId)?.style;
|
|
1203
1307
|
}
|
|
1308
|
+
getHighlightElements(root, termId) {
|
|
1309
|
+
const selector = termId
|
|
1310
|
+
? `.highlight[data-term-id="${termId}"], .highlight-wrapper[data-term-id="${termId}"]`
|
|
1311
|
+
: '.highlight, .highlight-wrapper';
|
|
1312
|
+
return Array.from(root.querySelectorAll(selector));
|
|
1313
|
+
}
|
|
1204
1314
|
/**
|
|
1205
1315
|
* Update highlights colors for specified page
|
|
1206
1316
|
* */
|
|
@@ -1248,7 +1358,44 @@ export class PDFHighlightViewer {
|
|
|
1248
1358
|
*/
|
|
1249
1359
|
buildSpatialIndexForPage(pageNumber) {
|
|
1250
1360
|
const refs = this.highlightsIndex.pages[String(pageNumber)] ?? [];
|
|
1251
|
-
|
|
1361
|
+
const normalizedRefs = refs.map((ref) => ({
|
|
1362
|
+
...ref,
|
|
1363
|
+
bbox: this.normalizeBBoxForPage({ ...ref.bbox, page: ref.page }, ref.page),
|
|
1364
|
+
}));
|
|
1365
|
+
this.performanceOptimizer.buildSpatialIndex(normalizedRefs, pageNumber);
|
|
1366
|
+
}
|
|
1367
|
+
toPageCoordinateDimensions(pixelDimensions) {
|
|
1368
|
+
const scale = this.currentScale > 0 ? this.currentScale : 1;
|
|
1369
|
+
return {
|
|
1370
|
+
width: pixelDimensions.width / scale,
|
|
1371
|
+
height: pixelDimensions.height / scale,
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
1374
|
+
normalizeBBoxForPage(bbox, pageNumber) {
|
|
1375
|
+
const origin = this.options.bboxOrigin ?? 'bottom-right';
|
|
1376
|
+
const pixelDimensions = this.pageDimensions.get(pageNumber);
|
|
1377
|
+
if (!pixelDimensions) {
|
|
1378
|
+
throw new Error(`Page dimensions for page ${pageNumber} are not available`);
|
|
1379
|
+
}
|
|
1380
|
+
const { width: pageWidth, height: pageHeight } = this.toPageCoordinateDimensions(pixelDimensions);
|
|
1381
|
+
let x1 = bbox.x1;
|
|
1382
|
+
let x2 = bbox.x2;
|
|
1383
|
+
let y1 = bbox.y1;
|
|
1384
|
+
let y2 = bbox.y2;
|
|
1385
|
+
if (origin.endsWith('right')) {
|
|
1386
|
+
x1 = pageWidth - bbox.x1;
|
|
1387
|
+
x2 = pageWidth - bbox.x2;
|
|
1388
|
+
}
|
|
1389
|
+
if (origin.startsWith('bottom')) {
|
|
1390
|
+
y1 = pageHeight - bbox.y1;
|
|
1391
|
+
y2 = pageHeight - bbox.y2;
|
|
1392
|
+
}
|
|
1393
|
+
return {
|
|
1394
|
+
x1: Math.min(x1, x2),
|
|
1395
|
+
y1: Math.min(y1, y2),
|
|
1396
|
+
x2: Math.max(x1, x2),
|
|
1397
|
+
y2: Math.max(y1, y2),
|
|
1398
|
+
};
|
|
1252
1399
|
}
|
|
1253
1400
|
/**
|
|
1254
1401
|
* Build spatial indices for all pages
|
|
@@ -1297,9 +1444,9 @@ export class PDFHighlightViewer {
|
|
|
1297
1444
|
// Store the selected term ID for persistence across page renders
|
|
1298
1445
|
this.selectedTermId = termId;
|
|
1299
1446
|
// Remove previous selection highlighting
|
|
1300
|
-
this.clearSelectedTermHighlighting();
|
|
1447
|
+
this.clearSelectedTermHighlighting(false);
|
|
1301
1448
|
// Add selected class to all instances of this term
|
|
1302
|
-
const termElements = this.container
|
|
1449
|
+
const termElements = this.getHighlightElements(this.container, termId);
|
|
1303
1450
|
termElements.forEach((element) => {
|
|
1304
1451
|
element.classList.add('selected-term');
|
|
1305
1452
|
// Override inline styles for selected term
|
|
@@ -1314,7 +1461,7 @@ export class PDFHighlightViewer {
|
|
|
1314
1461
|
htmlElement.style.transition = 'all 0.3s ease';
|
|
1315
1462
|
});
|
|
1316
1463
|
// Also dim all other highlights
|
|
1317
|
-
const allHighlights = this.
|
|
1464
|
+
const allHighlights = this.getHighlightElements(this.container);
|
|
1318
1465
|
allHighlights.forEach((element) => {
|
|
1319
1466
|
const elementTermId = element.getAttribute('data-term-id');
|
|
1320
1467
|
if (!elementTermId || elementTermId !== termId) {
|
|
@@ -1330,12 +1477,13 @@ export class PDFHighlightViewer {
|
|
|
1330
1477
|
/**
|
|
1331
1478
|
* Clear selected term highlighting
|
|
1332
1479
|
*/
|
|
1333
|
-
clearSelectedTermHighlighting() {
|
|
1480
|
+
clearSelectedTermHighlighting(clearStoredSelection = true) {
|
|
1334
1481
|
if (!this.container)
|
|
1335
1482
|
return;
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1483
|
+
if (clearStoredSelection) {
|
|
1484
|
+
this.selectedTermId = null;
|
|
1485
|
+
}
|
|
1486
|
+
const selectedElements = this.container.querySelectorAll('.highlight.selected-term, .highlight-wrapper.selected-term');
|
|
1339
1487
|
selectedElements.forEach((element) => {
|
|
1340
1488
|
element.classList.remove('selected-term');
|
|
1341
1489
|
// Reset inline styles for selected elements
|
|
@@ -1346,7 +1494,7 @@ export class PDFHighlightViewer {
|
|
|
1346
1494
|
htmlElement.style.borderWidth = '';
|
|
1347
1495
|
// Keep original opacity as it was set by the original rendering
|
|
1348
1496
|
});
|
|
1349
|
-
const dimmedElements = this.container.querySelectorAll('.dimmed-highlight');
|
|
1497
|
+
const dimmedElements = this.container.querySelectorAll('.highlight.dimmed-highlight, .highlight-wrapper.dimmed-highlight');
|
|
1350
1498
|
dimmedElements.forEach((element) => {
|
|
1351
1499
|
element.classList.remove('dimmed-highlight');
|
|
1352
1500
|
// Reset inline styles for dimmed elements
|