@epam/pdf-highlighter-kit 0.0.6 → 0.0.8

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 CHANGED
@@ -91,10 +91,20 @@ export interface BBox {
91
91
  page: number;
92
92
  }
93
93
 
94
+ export interface BBoxDimensions {
95
+ width: number;
96
+ height: number;
97
+ }
98
+
99
+ export type BBoxOrigin = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
100
+
101
+ export type InteractionMode = 'select' | 'highlight' | 'hybrid';
102
+
94
103
  export interface HighlightStyle {
95
104
  backgroundColor: string;
96
105
  borderColor?: string;
97
106
  borderWidth?: string;
107
+ outline?: string;
98
108
  opacity?: number;
99
109
  hoverOpacity?: number;
100
110
  pulseAnimation?: boolean;
@@ -102,20 +112,30 @@ export interface HighlightStyle {
102
112
 
103
113
  export interface HighlightLabelStyle {
104
114
  fontSize?: string | number;
115
+ opacity?: number;
105
116
  color?: string;
106
117
  backgroundColor?: string;
107
118
  padding?: string;
108
- borderRadius?: string;
119
+ borderRadius?: string; // e.g. '2px' or shorthand '2px 2px 0 0'
109
120
  fontFamily?: string;
110
121
  fontWeight?: string | number;
111
122
  border?: string;
123
+ borderColor?: string;
124
+ borderWidth?: string;
125
+ outline?: string;
112
126
  whiteSpace?: string;
113
127
  iconSize?: string | number; // size for beforeIcon (e.g. 14 or '14px')
128
+ iconColor?: string;
129
+ offsetLeft?: number;
130
+ offsetTop?: number;
131
+ outlineRight?: string;
114
132
  }
115
133
 
116
134
  export interface InputHighlightData {
117
135
  id: string;
118
136
  bboxes: BBox[];
137
+ bboxOrigin?: BBoxOrigin;
138
+ bboxSourceDimensions?: BBoxDimensions;
119
139
  style?: HighlightStyle;
120
140
  label?: string;
121
141
  beforeIcon?: string; // inline SVG string (trusted content only, e.g. Tabler icons)
@@ -125,12 +145,29 @@ export interface InputHighlightData {
125
145
  }
126
146
  ```
127
147
 
148
+ If `bboxSourceDimensions` is provided, each bbox coordinate is recalculated against the actual page size:
149
+
150
+ ```ts
151
+ scaledX = (x / bboxSourceDimensions.width) * actualPageWidth;
152
+ scaledY = (y / bboxSourceDimensions.height) * actualPageHeight;
153
+ ```
154
+
155
+ Priority (highest to lowest):
156
+
157
+ - `highlight.bboxOrigin` / `highlight.bboxSourceDimensions`
158
+ - global viewer options (`bboxOrigin`, `bboxSourceDimensions`)
159
+
128
160
  ## Configuration Options
129
161
 
130
- ### ViewerConfig
162
+ ### ViewerOptions
131
163
 
132
164
  ```ts
133
- interface ViewerConfig {
165
+ export interface HighlightsConfig {
166
+ enableMultilineHover?: boolean;
167
+ defaultStyle?: HighlightStyle;
168
+ }
169
+
170
+ export interface ViewerOptions {
134
171
  // Enable text selection functionality
135
172
  enableTextSelection?: boolean;
136
173
 
@@ -144,22 +181,23 @@ interface ViewerConfig {
144
181
  maxCachedPages?: number;
145
182
 
146
183
  // Interaction mode: 'select' | 'highlight' | 'hybrid'
147
- interactionMode?: 'select' | 'highlight' | 'hybrid';
184
+ interactionMode?: InteractionMode;
148
185
 
149
- // Custom styles configuration (viewer/selection CSS). Highlight styles are per-highlight.
150
- customStyles?: StyleConfig;
186
+ // Performance mode (smaller frame budget)
187
+ performanceMode?: boolean;
151
188
 
152
- // PDF.js worker source URL
153
- workerSrc?: string;
189
+ // Enable accessibility helpers
190
+ accessibility?: boolean;
154
191
 
155
192
  // Highlight UI config (style is per highlight)
156
- highlightsConfig?: {
157
- enableMultilineHover?: boolean;
158
- };
193
+ highlightsConfig?: HighlightsConfig;
159
194
 
160
195
  // Coordinate origin for incoming bbox values
161
196
  // Default: 'bottom-right'
162
- bboxOrigin?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
197
+ bboxOrigin?: BBoxOrigin;
198
+
199
+ // Page dimensions for which bbox coordinates were calculated
200
+ bboxSourceDimensions?: BBoxDimensions;
163
201
  }
164
202
  ```
165
203
 
@@ -167,11 +205,11 @@ interface ViewerConfig {
167
205
 
168
206
  ### Main Methods
169
207
 
170
- #### `init(container: HTMLElement, config?: ViewerConfig): Promise<void>`
208
+ #### `init(container: HTMLElement, config?: ViewerOptions): Promise<void>`
171
209
 
172
210
  Initialize the viewer with a container element and optional configuration.
173
211
 
174
- #### `loadPDF(source: string | ArrayBuffer): Promise<void>`
212
+ #### `loadPDF(source: string | ArrayBuffer | Blob): Promise<void>`
175
213
 
176
214
  Load a PDF document from URL or ArrayBuffer.
177
215
 
@@ -187,22 +225,19 @@ Add a single highlight (incremental update).
187
225
 
188
226
  Remove highlight by its `id`.
189
227
 
190
- #### `updateHighlightStyle(termId: string, stylePatch: Partial<HighlightStyle>): void`
191
-
192
- Update highlight style by id (patch merge).
228
+ #### `updateHighlightStyle(termId: string, stylePatch: Partial<HighlightStyle>, labelStylePatch?: Partial<HighlightLabelStyle>): void`
193
229
 
194
- #### `goToHighlight(termId: string, bboxIndex?: number): void`
230
+ Update highlight style by id (patch merge). Optionally pass `labelStylePatch` to update label styles as well.
195
231
 
196
- Navigate to a specific highlight occurrence. `bboxIndex` defaults to `0`.
232
+ #### `goToHighlight(termId: string, occurrenceIndex?: number): void`
197
233
 
198
- #### `nextHighlight(termId?: string): void` / `previousHighlight(termId?: string): void`
234
+ Navigate to a specific highlight occurrence. `occurrenceIndex` defaults to `0`.
199
235
 
200
- Navigate across highlight occurrences:
236
+ #### `nextHighlight(): void` / `previousHighlight(): void`
201
237
 
202
- - without `termId` → across all highlights in document order
203
- - with `termId` → only within that highlight’s occurrences
238
+ Navigate across all highlight occurrences in document order.
204
239
 
205
- #### `goToPage(pageNumber: number): void`
240
+ #### `setPage(pageNumber: number): void`
206
241
 
207
242
  Navigate to a specific page (1-based).
208
243
 
@@ -210,7 +245,7 @@ Navigate to a specific page (1-based).
210
245
 
211
246
  Zoom in or out of the PDF.
212
247
 
213
- #### `setZoom(scale: number): void`
248
+ #### `setZoom(value: ZoomMode | number): void`
214
249
 
215
250
  Set a specific zoom level (e.g., 1.0 for 100%, 1.5 for 150%).
216
251
 
@@ -240,7 +275,7 @@ viewer.addEventListener('pdfLoaded', (e) => {
240
275
  });
241
276
 
242
277
  viewer.addEventListener('pageChanged', (e) => {
243
- console.log('Current page:', e.pageNumber);
278
+ console.log('Current page:', e.currentPage);
244
279
  });
245
280
 
246
281
  viewer.addEventListener('zoomChanged', (e) => {
@@ -256,7 +291,7 @@ viewer.addEventListener('renderError', (e) => {
256
291
  });
257
292
 
258
293
  viewer.addEventListener('highlightsLoaded', (e) => {
259
- console.log('Highlights loaded:', e.data?.length ?? 0);
294
+ console.log('Highlights loaded:', e.count);
260
295
  });
261
296
 
262
297
  viewer.addEventListener('highlightHover', (e) => {
@@ -1,5 +1,6 @@
1
1
  import { PDFHighlightViewer as IPDFHighlightViewer } from './api';
2
- import { ViewerOptions, TextRange, SelectionWithMetadata, PerformanceMetrics, HighlightAnalytics, AccessibilityFeatures, InteractionMode, InputHighlightData, HighlightStyle, ZoomValue, ThumbnailOptions } from './types';
2
+ import { ViewerOptions, TextRange, SelectionWithMetadata, PerformanceMetrics, HighlightAnalytics, AccessibilityFeatures, InteractionMode, InputHighlightData, HighlightStyle, HighlightLabelStyle, ZoomValue, ThumbnailOptions } from './types';
3
+ type EventCallback = (data?: unknown) => void;
3
4
  export declare class PDFHighlightViewer implements IPDFHighlightViewer {
4
5
  private pdfEngine;
5
6
  private viewportManager;
@@ -105,7 +106,7 @@ export declare class PDFHighlightViewer implements IPDFHighlightViewer {
105
106
  loadHighlights(data: InputHighlightData[]): void;
106
107
  addHighlight(highlight: InputHighlightData): void;
107
108
  removeHighlight(termId: string): void;
108
- updateHighlightStyle(termId: string, stylePatch: Partial<HighlightStyle>): void;
109
+ updateHighlightStyle(termId: string, stylePatch: Partial<HighlightStyle>, labelStylePatch?: Partial<HighlightLabelStyle>): void;
109
110
  /**
110
111
  * Update unified layer for a specific page
111
112
  */
@@ -150,9 +151,9 @@ export declare class PDFHighlightViewer implements IPDFHighlightViewer {
150
151
  enableProfiling(): void;
151
152
  disableProfiling(): void;
152
153
  accessibility: AccessibilityFeatures;
153
- addEventListener(event: string, callback: (...args: any[]) => void): void;
154
- removeEventListener(event: string, callback: (...args: any[]) => void): void;
155
- emit(event: string, data?: any): void;
154
+ addEventListener(event: string, callback: EventCallback): void;
155
+ removeEventListener(event: string, callback: EventCallback): void;
156
+ emit(event: string, data?: unknown): void;
156
157
  exportAsImage(format?: 'png' | 'jpeg', quality?: number): Promise<Blob>;
157
158
  getViewport(): {
158
159
  pageNumber: number;
@@ -173,6 +174,8 @@ export declare class PDFHighlightViewer implements IPDFHighlightViewer {
173
174
  private addHighlightsToPage;
174
175
  private getHighlightById;
175
176
  private getHighlightStyle;
177
+ private getLabelOffsets;
178
+ private getLabelsForTerm;
176
179
  private getHighlightElements;
177
180
  /**
178
181
  * Update highlights colors for specified page
@@ -1 +1 @@
1
- {"version":3,"file":"PDFHighlightViewer.d.ts","sourceRoot":"","sources":["../src/PDFHighlightViewer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"PDFHighlightViewer.d.ts","sourceRoot":"","sources":["../src/PDFHighlightViewer.ts"],"names":[],"mappings":"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"}
@@ -5,8 +5,8 @@ import { UnifiedLayerBuilder } from './core/unified-layer-builder';
5
5
  import { UnifiedInteractionHandler } from './core/interaction-handler';
6
6
  import { PerformanceOptimizer } from './core/performance-optimizer';
7
7
  import { buildHighlightsIndex } from './utils/highlight-adapter';
8
- import { applyLabelStyle, applyIconStyle, normalizeSize } from './utils/label-style';
9
- import { sanitizeIconHtml } from './utils/sanitize-icon-html';
8
+ import { applyHighlightVisualStyle, getHighlightBaseOpacity, getHighlightHoverOpacity, resolveHighlightStyle, } from './utils/highlight-style';
9
+ import { appendLabelIcon, applyBaseOutlineStyle, applyLabelOutlineStyle, applyLabelStyle, } from './utils/label-style';
10
10
  const CONTAINER_PADDING = 40;
11
11
  const ZOOM_STEP = 1.2;
12
12
  export class PDFHighlightViewer {
@@ -797,7 +797,7 @@ export class PDFHighlightViewer {
797
797
  }
798
798
  this.emit('highlightRemoved', { termId, pages: Array.from(affectedPages) });
799
799
  }
800
- updateHighlightStyle(termId, stylePatch) {
800
+ updateHighlightStyle(termId, stylePatch, labelStylePatch) {
801
801
  const prev = this.highlightsIndex.byId.get(termId);
802
802
  if (!prev)
803
803
  return;
@@ -807,6 +807,12 @@ export class PDFHighlightViewer {
807
807
  ...(prev.style ?? {}),
808
808
  ...stylePatch,
809
809
  },
810
+ labelStyle: labelStylePatch
811
+ ? {
812
+ ...(prev.labelStyle ?? {}),
813
+ ...labelStylePatch,
814
+ }
815
+ : prev.labelStyle,
810
816
  };
811
817
  const affectedPages = new Set();
812
818
  prev.bboxes.forEach((b) => affectedPages.add(b.page));
@@ -837,7 +843,7 @@ export class PDFHighlightViewer {
837
843
  const normalizedHighlights = this.highlightsIndex.highlights.map((highlight) => ({
838
844
  ...highlight,
839
845
  bboxes: highlight.bboxes.map((bbox) => {
840
- const normalized = this.normalizeBBoxForPage(bbox, bbox.page);
846
+ const normalized = this.normalizeBBoxForPage(bbox, bbox.page, highlight.bboxSourceDimensions, highlight.bboxOrigin);
841
847
  return {
842
848
  ...bbox,
843
849
  x1: normalized.x1,
@@ -925,7 +931,7 @@ export class PDFHighlightViewer {
925
931
  for (const h of this.highlightsIndex.highlights) {
926
932
  for (let i = 0; i < h.bboxes.length; i++) {
927
933
  const b = h.bboxes[i];
928
- const normalized = this.normalizeBBoxForPage(b, b.page);
934
+ const normalized = this.normalizeBBoxForPage(b, b.page, h.bboxSourceDimensions, h.bboxOrigin);
929
935
  list.push({
930
936
  termId: h.id,
931
937
  pageNumber: b.page,
@@ -946,7 +952,7 @@ export class PDFHighlightViewer {
946
952
  if (!bbox)
947
953
  return;
948
954
  const page = bbox.page;
949
- const normalizedBBox = this.normalizeBBoxForPage(bbox, page);
955
+ const normalizedBBox = this.normalizeBBoxForPage(bbox, page, highlight.bboxSourceDimensions, highlight.bboxOrigin);
950
956
  this.highlightSelectedTerm(termId);
951
957
  this.setPage(page);
952
958
  void this.renderPage(page)
@@ -1174,14 +1180,12 @@ export class PDFHighlightViewer {
1174
1180
  highlightLayer.style.pointerEvents = 'none';
1175
1181
  for (const highlight of this.highlightsIndex.highlights) {
1176
1182
  const style = highlight.style;
1177
- const backgroundColor = style?.backgroundColor ?? '#666666';
1178
- const borderColor = style?.borderColor ?? backgroundColor;
1179
- const borderWidth = style?.borderWidth ?? '1px';
1183
+ const resolvedStyle = resolveHighlightStyle(style);
1180
1184
  for (let bboxIndex = 0; bboxIndex < highlight.bboxes.length; bboxIndex++) {
1181
1185
  const bbox = highlight.bboxes[bboxIndex];
1182
1186
  if (bbox.page !== pageNumber)
1183
1187
  continue;
1184
- const normalizedBBox = this.normalizeBBoxForPage(bbox, pageNumber);
1188
+ const normalizedBBox = this.normalizeBBoxForPage(bbox, pageNumber, highlight.bboxSourceDimensions, highlight.bboxOrigin);
1185
1189
  const highlightDiv = document.createElement('div');
1186
1190
  highlightDiv.className = 'highlight';
1187
1191
  highlightDiv.setAttribute('data-term-id', highlight.id);
@@ -1196,62 +1200,108 @@ export class PDFHighlightViewer {
1196
1200
  highlightDiv.style.top = `${top}px`;
1197
1201
  highlightDiv.style.width = `${width}px`;
1198
1202
  highlightDiv.style.height = `${height}px`;
1199
- highlightDiv.style.backgroundColor = backgroundColor;
1200
- highlightDiv.style.border = `${borderWidth} solid ${borderColor}`;
1201
1203
  highlightDiv.style.pointerEvents = 'auto';
1202
1204
  highlightDiv.style.cursor = 'pointer';
1203
1205
  highlightDiv.style.boxSizing = 'border-box';
1204
1206
  highlightDiv.style.userSelect = 'none';
1205
- highlightDiv.style.mixBlendMode = 'multiply';
1206
- const baseOpacity = typeof style?.opacity === 'number' ? style.opacity : 0.3;
1207
+ const highlightVisual = document.createElement('span');
1208
+ highlightVisual.className = 'highlight-visual';
1209
+ applyHighlightVisualStyle(highlightVisual, resolvedStyle);
1210
+ applyBaseOutlineStyle(highlightVisual, style);
1211
+ highlightDiv.appendChild(highlightVisual);
1212
+ const baseOpacity = getHighlightBaseOpacity(style);
1207
1213
  const overlappingCount = this.countOverlappingHighlights(highlightLayer, normalizedBBox, scale);
1208
1214
  const effectiveOpacity = Math.max(0.05, baseOpacity / Math.max(1, overlappingCount * 0.7));
1209
- highlightDiv.style.opacity = effectiveOpacity.toString();
1215
+ highlightVisual.style.opacity = effectiveOpacity.toString();
1210
1216
  highlightDiv.dataset.originalOpacity = effectiveOpacity.toString();
1211
- const hoverOpacity = typeof style?.hoverOpacity === 'number'
1212
- ? style.hoverOpacity
1213
- : Math.min(0.6, effectiveOpacity + 0.2);
1214
- const labelColor = style?.borderColor ?? style?.backgroundColor ?? '#666666';
1217
+ const hoverOpacity = getHighlightHoverOpacity(style, effectiveOpacity);
1218
+ const termLabelsSelector = `.highlight-label[data-term-id="${highlight.id}"]`;
1215
1219
  highlightDiv.addEventListener('mouseenter', () => {
1216
1220
  if (this.options.highlightsConfig?.enableMultilineHover) {
1217
1221
  const same = highlightLayer.querySelectorAll(`.highlight[data-term-id="${highlight.id}"]`);
1218
- same.forEach((el) => (el.style.opacity = String(hoverOpacity)));
1222
+ same.forEach((el) => {
1223
+ const htmlEl = el;
1224
+ const visual = htmlEl.querySelector('.highlight-visual');
1225
+ if (visual)
1226
+ visual.style.opacity = String(hoverOpacity);
1227
+ });
1228
+ this.getLabelsForTerm(highlightLayer, highlight.id).forEach((sameLabel) => {
1229
+ sameLabel.style.opacity = String(hoverOpacity);
1230
+ });
1219
1231
  const other = highlightLayer.querySelectorAll(`.highlight[data-term-id]:not([data-term-id="${highlight.id}"])`);
1220
- other.forEach((el) => (el.style.opacity = '0.1'));
1232
+ other.forEach((el) => {
1233
+ const htmlEl = el;
1234
+ const visual = htmlEl.querySelector('.highlight-visual');
1235
+ if (visual)
1236
+ visual.style.opacity = '0.1';
1237
+ });
1238
+ highlightLayer
1239
+ .querySelectorAll(`.highlight-label:not([data-term-id="${highlight.id}"])`)
1240
+ .forEach((otherLabel) => {
1241
+ otherLabel.style.opacity = '0.1';
1242
+ });
1221
1243
  }
1222
1244
  else {
1223
- highlightDiv.style.opacity = String(hoverOpacity);
1224
- }
1225
- const labelEl = highlightDiv.querySelector('.highlight-label');
1226
- if (labelEl) {
1227
- labelEl.style.borderColor = borderColor;
1245
+ highlightVisual.style.opacity = String(hoverOpacity);
1228
1246
  }
1247
+ highlightLayer.querySelectorAll(termLabelsSelector).forEach((labelEl) => {
1248
+ labelEl.style.opacity = String(hoverOpacity);
1249
+ });
1229
1250
  });
1230
1251
  highlightDiv.addEventListener('mouseleave', () => {
1231
1252
  if (this.options.highlightsConfig?.enableMultilineHover) {
1232
1253
  const all = highlightLayer.querySelectorAll('.highlight[data-term-id]');
1254
+ const baseOpacityByTermId = new Map();
1233
1255
  all.forEach((el) => {
1234
- const original = el.dataset.originalOpacity ?? '0.3';
1235
- el.style.opacity = original;
1256
+ const htmlEl = el;
1257
+ const original = htmlEl.dataset.originalOpacity ?? '0.3';
1258
+ const visual = htmlEl.querySelector('.highlight-visual');
1259
+ if (visual)
1260
+ visual.style.opacity = original;
1261
+ const termId = htmlEl.getAttribute('data-term-id');
1262
+ if (termId) {
1263
+ baseOpacityByTermId.set(termId, original);
1264
+ }
1265
+ });
1266
+ highlightLayer
1267
+ .querySelectorAll('.highlight-label')
1268
+ .forEach((allLabel) => {
1269
+ const allTermId = allLabel.getAttribute('data-term-id');
1270
+ if (!allTermId)
1271
+ return;
1272
+ const fallbackBaseOpacity = baseOpacityByTermId.get(allTermId);
1273
+ if (fallbackBaseOpacity !== undefined) {
1274
+ allLabel.style.opacity = fallbackBaseOpacity;
1275
+ return;
1276
+ }
1277
+ const allHighlight = this.getHighlightById(allTermId);
1278
+ allLabel.style.opacity = String(typeof allHighlight?.style?.opacity === 'number'
1279
+ ? allHighlight.style.opacity
1280
+ : 0.3);
1236
1281
  });
1237
1282
  }
1238
1283
  else {
1239
- highlightDiv.style.opacity = highlightDiv.dataset.originalOpacity ?? '0.3';
1240
- }
1241
- const labelEl = highlightDiv.querySelector('.highlight-label');
1242
- if (labelEl) {
1243
- labelEl.style.borderColor = labelColor;
1284
+ highlightVisual.style.opacity = highlightDiv.dataset.originalOpacity ?? '0.3';
1244
1285
  }
1286
+ highlightLayer.querySelectorAll(termLabelsSelector).forEach((labelEl) => {
1287
+ labelEl.style.opacity = String(effectiveOpacity);
1288
+ });
1245
1289
  });
1246
1290
  highlightLayer.appendChild(highlightDiv);
1247
1291
  if (highlight.label || highlight.beforeIcon) {
1248
1292
  const labelEl = document.createElement('span');
1249
1293
  labelEl.className = 'highlight-label';
1250
1294
  labelEl.setAttribute('data-term-id', highlight.id);
1295
+ labelEl.setAttribute('data-bbox-index', String(bboxIndex));
1296
+ labelEl.dataset.baseLeft = String(left);
1297
+ labelEl.dataset.baseTop = String(top);
1298
+ const { left: labelOffsetLeft, top: labelOffsetTop } = this.getLabelOffsets(highlight.labelStyle);
1251
1299
  labelEl.style.position = 'absolute';
1252
- labelEl.style.left = '0';
1300
+ labelEl.style.left = `${left + labelOffsetLeft}px`;
1301
+ labelEl.style.top = `${top + labelOffsetTop}px`;
1302
+ labelEl.style.boxSizing = 'border-box';
1303
+ labelEl.style.zIndex = '3';
1253
1304
  labelEl.style.transform = 'translateX(-100%)';
1254
- labelEl.style.top = '-1px';
1255
1305
  labelEl.style.display = 'flex';
1256
1306
  labelEl.style.alignItems = 'center';
1257
1307
  labelEl.style.justifyContent = 'flex-end';
@@ -1259,28 +1309,20 @@ export class PDFHighlightViewer {
1259
1309
  labelEl.style.pointerEvents = 'auto';
1260
1310
  labelEl.style.cursor = 'pointer';
1261
1311
  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);
1312
+ if (highlight.labelStyle?.borderColor !== undefined ||
1313
+ highlight.labelStyle?.borderWidth !== undefined) {
1314
+ const borderColor = highlight.labelStyle?.borderColor ?? 'currentColor';
1315
+ const borderWidth = highlight.labelStyle?.borderWidth ?? '1px';
1316
+ labelEl.style.border = `${borderWidth} solid ${borderColor}`;
1279
1317
  }
1318
+ applyLabelOutlineStyle(labelEl, highlight.labelStyle);
1319
+ applyLabelStyle(labelEl, highlight.labelStyle);
1320
+ labelEl.style.opacity = String(effectiveOpacity);
1321
+ appendLabelIcon(labelEl, highlight.beforeIcon, highlight.labelStyle);
1280
1322
  if (highlight.label) {
1281
1323
  labelEl.appendChild(document.createTextNode(highlight.label));
1282
1324
  }
1283
- highlightDiv.appendChild(labelEl);
1325
+ highlightLayer.appendChild(labelEl);
1284
1326
  }
1285
1327
  }
1286
1328
  }
@@ -1305,6 +1347,15 @@ export class PDFHighlightViewer {
1305
1347
  getHighlightStyle(termId) {
1306
1348
  return this.getHighlightById(termId)?.style;
1307
1349
  }
1350
+ getLabelOffsets(style) {
1351
+ return {
1352
+ left: style?.offsetLeft ?? 0,
1353
+ top: style?.offsetTop ?? -1,
1354
+ };
1355
+ }
1356
+ getLabelsForTerm(highlightLayer, termId) {
1357
+ return Array.from(highlightLayer.querySelectorAll(`.highlight-label[data-term-id="${termId}"]`));
1358
+ }
1308
1359
  getHighlightElements(root, termId) {
1309
1360
  const selector = termId
1310
1361
  ? `.highlight[data-term-id="${termId}"], .highlight-wrapper[data-term-id="${termId}"]`
@@ -1326,29 +1377,60 @@ export class PDFHighlightViewer {
1326
1377
  const termId = el.getAttribute('data-term-id');
1327
1378
  if (!termId)
1328
1379
  return;
1380
+ const highlight = this.getHighlightById(termId);
1329
1381
  const style = this.getHighlightStyle(termId);
1330
- const bg = style?.backgroundColor ?? el.style.backgroundColor ?? '#666666';
1331
- const borderColor = style?.borderColor ?? bg;
1332
- const borderWidth = style?.borderWidth ?? '1px';
1333
- el.style.backgroundColor = bg;
1334
- el.style.border = `${borderWidth} solid ${borderColor}`;
1382
+ const { backgroundColor: bg, borderColor, borderWidth, } = resolveHighlightStyle(style, el.style.backgroundColor || '#666666');
1383
+ const visual = el.querySelector('.highlight-visual') ?? el;
1384
+ visual.style.backgroundColor = bg;
1385
+ visual.style.border = `${borderWidth} solid ${borderColor}`;
1386
+ applyBaseOutlineStyle(visual, style);
1387
+ const highlightBaseOpacity = typeof style?.opacity === 'number'
1388
+ ? style.opacity
1389
+ : parseFloat(el.dataset.originalOpacity ?? '0.3');
1390
+ const bboxIdx = el.getAttribute('data-bbox-index');
1391
+ const isWrapper = el.classList.contains('highlight-wrapper');
1392
+ const labelStyle = highlight?.labelStyle;
1393
+ const labelEl = isWrapper
1394
+ ? el.querySelector('.highlight-label')
1395
+ : bboxIdx !== null
1396
+ ? highlightLayer.querySelector(`.highlight-label[data-term-id="${termId}"][data-bbox-index="${bboxIdx}"]`)
1397
+ : highlightLayer.querySelector(`.highlight-label[data-term-id="${termId}"]`);
1398
+ if (labelEl) {
1399
+ if (!isWrapper) {
1400
+ const baseLabelLeft = parseFloat(labelEl.dataset.baseLeft ?? '0');
1401
+ const baseLabelTop = parseFloat(labelEl.dataset.baseTop ?? '0');
1402
+ const { left: labelOffsetLeft, top: labelOffsetTop } = this.getLabelOffsets(labelStyle);
1403
+ labelEl.style.left = `${baseLabelLeft + labelOffsetLeft}px`;
1404
+ labelEl.style.top = `${baseLabelTop + labelOffsetTop}px`;
1405
+ }
1406
+ applyLabelStyle(labelEl, labelStyle);
1407
+ applyLabelOutlineStyle(labelEl, labelStyle);
1408
+ labelEl.style.opacity = String(highlightBaseOpacity);
1409
+ }
1335
1410
  if (this.options.highlightsConfig?.enableMultilineHover &&
1336
1411
  hoveredIds &&
1337
1412
  Array.isArray(hoveredIds)) {
1338
- const baseOpacity = typeof style?.opacity === 'number'
1339
- ? style.opacity
1413
+ const baseOpacity = style
1414
+ ? getHighlightBaseOpacity(style)
1340
1415
  : parseFloat(el.dataset.originalOpacity ?? '0.3');
1341
- const hoverOpacity = typeof style?.hoverOpacity === 'number'
1342
- ? style.hoverOpacity
1343
- : Math.min(0.6, baseOpacity + 0.2);
1416
+ const hoverOpacity = getHighlightHoverOpacity(style, baseOpacity);
1344
1417
  if (hoveredIds.includes(termId)) {
1345
- el.style.opacity = String(hoverOpacity);
1418
+ visual.style.opacity = String(hoverOpacity);
1419
+ if (labelEl) {
1420
+ labelEl.style.opacity = String(hoverOpacity);
1421
+ }
1346
1422
  }
1347
1423
  else if (hoveredIds.length > 0) {
1348
- el.style.opacity = '0.1';
1424
+ visual.style.opacity = '0.1';
1425
+ if (labelEl) {
1426
+ labelEl.style.opacity = '0.1';
1427
+ }
1349
1428
  }
1350
1429
  else {
1351
- el.style.opacity = String(baseOpacity);
1430
+ visual.style.opacity = String(baseOpacity);
1431
+ if (labelEl) {
1432
+ labelEl.style.opacity = String(baseOpacity);
1433
+ }
1352
1434
  }
1353
1435
  }
1354
1436
  });
@@ -1358,10 +1440,16 @@ export class PDFHighlightViewer {
1358
1440
  */
1359
1441
  buildSpatialIndexForPage(pageNumber) {
1360
1442
  const refs = this.highlightsIndex.pages[String(pageNumber)] ?? [];
1361
- const normalizedRefs = refs.map((ref) => ({
1362
- ...ref,
1363
- bbox: this.normalizeBBoxForPage({ ...ref.bbox, page: ref.page }, ref.page),
1364
- }));
1443
+ const normalizedRefs = refs.map((ref) => {
1444
+ const highlight = this.highlightsIndex.byId.get(ref.id);
1445
+ return {
1446
+ ...ref,
1447
+ bbox: this.normalizeBBoxForPage(highlight?.bboxes[ref.bboxIndex] ?? {
1448
+ ...ref.bbox,
1449
+ page: ref.page,
1450
+ }, ref.page, highlight?.bboxSourceDimensions, highlight?.bboxOrigin),
1451
+ };
1452
+ });
1365
1453
  this.performanceOptimizer.buildSpatialIndex(normalizedRefs, pageNumber);
1366
1454
  }
1367
1455
  toPageCoordinateDimensions(pixelDimensions) {
@@ -1371,8 +1459,9 @@ export class PDFHighlightViewer {
1371
1459
  height: pixelDimensions.height / scale,
1372
1460
  };
1373
1461
  }
1374
- normalizeBBoxForPage(bbox, pageNumber) {
1375
- const origin = this.options.bboxOrigin ?? 'bottom-right';
1462
+ normalizeBBoxForPage(bbox, pageNumber, bboxSourceDimensions, bboxOrigin) {
1463
+ const origin = bboxOrigin ?? this.options.bboxOrigin ?? 'bottom-right';
1464
+ const computedSourceDimensions = bboxSourceDimensions ?? this.options.bboxSourceDimensions;
1376
1465
  const pixelDimensions = this.pageDimensions.get(pageNumber);
1377
1466
  if (!pixelDimensions) {
1378
1467
  throw new Error(`Page dimensions for page ${pageNumber} are not available`);
@@ -1382,13 +1471,23 @@ export class PDFHighlightViewer {
1382
1471
  let x2 = bbox.x2;
1383
1472
  let y1 = bbox.y1;
1384
1473
  let y2 = bbox.y2;
1474
+ if (computedSourceDimensions &&
1475
+ computedSourceDimensions.width &&
1476
+ computedSourceDimensions.height) {
1477
+ const xScale = pageWidth / computedSourceDimensions.width;
1478
+ const yScale = pageHeight / computedSourceDimensions.height;
1479
+ x1 *= xScale;
1480
+ x2 *= xScale;
1481
+ y1 *= yScale;
1482
+ y2 *= yScale;
1483
+ }
1385
1484
  if (origin.endsWith('right')) {
1386
- x1 = pageWidth - bbox.x1;
1387
- x2 = pageWidth - bbox.x2;
1485
+ x1 = pageWidth - x1;
1486
+ x2 = pageWidth - x2;
1388
1487
  }
1389
1488
  if (origin.startsWith('bottom')) {
1390
- y1 = pageHeight - bbox.y1;
1391
- y2 = pageHeight - bbox.y2;
1489
+ y1 = pageHeight - y1;
1490
+ y2 = pageHeight - y2;
1392
1491
  }
1393
1492
  return {
1394
1493
  x1: Math.min(x1, x2),
@@ -1549,6 +1648,9 @@ export class PDFHighlightViewer {
1549
1648
  // textLayer.style.opacity = '0.1'; // Uncomment for debugging text boundaries
1550
1649
  // Process text items with proper PDF coordinate transformation
1551
1650
  textContent.items.forEach((item) => {
1651
+ if (!('str' in item) || !('transform' in item) || !('fontName' in item)) {
1652
+ return;
1653
+ }
1552
1654
  if (!item.str || !item.str.trim())
1553
1655
  return; // Skip empty text
1554
1656
  const textSpan = document.createElement('span');