@epam/pdf-highlighter-kit 0.0.7 → 0.0.9

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
@@ -82,6 +82,8 @@ Each highlight carries its own style. No categories are required.
82
82
 
83
83
  **Icon before label:** Optionally set `beforeIcon` to an inline SVG string (e.g. from [Tabler Icons](https://tabler.io/icons)) to render an icon inside the label frame, to the left of the text. The icon inherits the label color via `currentColor`. Use `labelStyle.iconSize` to set the icon size (e.g. `14` or `'14px'`) and `labelStyle.iconColor` to set the icon color (e.g. `'#ff6b6b'`); if `iconColor` is not set, the icon uses the label text color. Only pass trusted SVG content (e.g. from your bundle or `@tabler/icons`); in React with Vite you can use `import iconSvg from '@tabler/icons/icons/outline/alert-circle.svg?raw'` and pass `iconSvg` as `beforeIcon`.
84
84
 
85
+ **Scalable label:** Set `isLabelScalable: true` on the highlight so the label’s size (font, padding, icon, border radius) scales with zoom; the label stays proportional to the highlight. Border and outline thickness are not scaled (they stay fixed like the highlight’s outline). Best used with `labelStyle` values in `px` or numbers.
86
+
85
87
  ```ts
86
88
  export interface BBox {
87
89
  x1: number;
@@ -96,10 +98,15 @@ export interface BBoxDimensions {
96
98
  height: number;
97
99
  }
98
100
 
101
+ export type BBoxOrigin = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
102
+
103
+ export type InteractionMode = 'select' | 'highlight' | 'hybrid';
104
+
99
105
  export interface HighlightStyle {
100
106
  backgroundColor: string;
101
107
  borderColor?: string;
102
108
  borderWidth?: string;
109
+ outline?: string;
103
110
  opacity?: number;
104
111
  hoverOpacity?: number;
105
112
  pulseAnimation?: boolean;
@@ -107,25 +114,34 @@ export interface HighlightStyle {
107
114
 
108
115
  export interface HighlightLabelStyle {
109
116
  fontSize?: string | number;
117
+ opacity?: number;
110
118
  color?: string;
111
119
  backgroundColor?: string;
112
120
  padding?: string;
113
- borderRadius?: string;
121
+ borderRadius?: string; // e.g. '2px' or shorthand '2px 2px 0 0'
114
122
  fontFamily?: string;
115
123
  fontWeight?: string | number;
116
124
  border?: string;
125
+ borderColor?: string;
126
+ borderWidth?: string;
127
+ outline?: string;
117
128
  whiteSpace?: string;
118
129
  iconSize?: string | number; // size for beforeIcon (e.g. 14 or '14px')
130
+ iconColor?: string;
131
+ offsetLeft?: number;
132
+ offsetTop?: number;
133
+ outlineRight?: string;
119
134
  }
120
135
 
121
136
  export interface InputHighlightData {
122
137
  id: string;
123
138
  bboxes: BBox[];
124
- bboxOrigin?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
139
+ bboxOrigin?: BBoxOrigin;
125
140
  bboxSourceDimensions?: BBoxDimensions;
126
141
  style?: HighlightStyle;
127
142
  label?: string;
128
143
  beforeIcon?: string; // inline SVG string (trusted content only, e.g. Tabler icons)
144
+ isLabelScalable?: boolean; // when true, label size scales with zoom (borders stay fixed)
129
145
  labelStyle?: HighlightLabelStyle;
130
146
  tooltipText?: string;
131
147
  metadata?: Record<string, any>;
@@ -146,10 +162,15 @@ Priority (highest to lowest):
146
162
 
147
163
  ## Configuration Options
148
164
 
149
- ### ViewerConfig
165
+ ### ViewerOptions
150
166
 
151
167
  ```ts
152
- interface ViewerConfig {
168
+ export interface HighlightsConfig {
169
+ enableMultilineHover?: boolean;
170
+ defaultStyle?: HighlightStyle;
171
+ }
172
+
173
+ export interface ViewerOptions {
153
174
  // Enable text selection functionality
154
175
  enableTextSelection?: boolean;
155
176
 
@@ -163,25 +184,23 @@ interface ViewerConfig {
163
184
  maxCachedPages?: number;
164
185
 
165
186
  // Interaction mode: 'select' | 'highlight' | 'hybrid'
166
- interactionMode?: 'select' | 'highlight' | 'hybrid';
187
+ interactionMode?: InteractionMode;
167
188
 
168
- // Custom styles configuration (viewer/selection CSS). Highlight styles are per-highlight.
169
- customStyles?: StyleConfig;
189
+ // Performance mode (smaller frame budget)
190
+ performanceMode?: boolean;
170
191
 
171
- // PDF.js worker source URL
172
- workerSrc?: string;
192
+ // Enable accessibility helpers
193
+ accessibility?: boolean;
173
194
 
174
195
  // Highlight UI config (style is per highlight)
175
- highlightsConfig?: {
176
- enableMultilineHover?: boolean;
177
- };
196
+ highlightsConfig?: HighlightsConfig;
178
197
 
179
198
  // Coordinate origin for incoming bbox values
180
199
  // Default: 'bottom-right'
181
- bboxOrigin?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
200
+ bboxOrigin?: BBoxOrigin;
182
201
 
183
202
  // Page dimensions for which bbox coordinates were calculated
184
- bboxSourceDimensions?: { width: number; height: number };
203
+ bboxSourceDimensions?: BBoxDimensions;
185
204
  }
186
205
  ```
187
206
 
@@ -189,11 +208,11 @@ interface ViewerConfig {
189
208
 
190
209
  ### Main Methods
191
210
 
192
- #### `init(container: HTMLElement, config?: ViewerConfig): Promise<void>`
211
+ #### `init(container: HTMLElement, config?: ViewerOptions): Promise<void>`
193
212
 
194
213
  Initialize the viewer with a container element and optional configuration.
195
214
 
196
- #### `loadPDF(source: string | ArrayBuffer): Promise<void>`
215
+ #### `loadPDF(source: string | ArrayBuffer | Blob): Promise<void>`
197
216
 
198
217
  Load a PDF document from URL or ArrayBuffer.
199
218
 
@@ -209,22 +228,19 @@ Add a single highlight (incremental update).
209
228
 
210
229
  Remove highlight by its `id`.
211
230
 
212
- #### `updateHighlightStyle(termId: string, stylePatch: Partial<HighlightStyle>): void`
213
-
214
- Update highlight style by id (patch merge).
231
+ #### `updateHighlightStyle(termId: string, stylePatch: Partial<HighlightStyle>, labelStylePatch?: Partial<HighlightLabelStyle>): void`
215
232
 
216
- #### `goToHighlight(termId: string, bboxIndex?: number): void`
233
+ Update highlight style by id (patch merge). Optionally pass `labelStylePatch` to update label styles as well.
217
234
 
218
- Navigate to a specific highlight occurrence. `bboxIndex` defaults to `0`.
235
+ #### `goToHighlight(termId: string, occurrenceIndex?: number): void`
219
236
 
220
- #### `nextHighlight(termId?: string): void` / `previousHighlight(termId?: string): void`
237
+ Navigate to a specific highlight occurrence. `occurrenceIndex` defaults to `0`.
221
238
 
222
- Navigate across highlight occurrences:
239
+ #### `nextHighlight(): void` / `previousHighlight(): void`
223
240
 
224
- - without `termId` → across all highlights in document order
225
- - with `termId` → only within that highlight’s occurrences
241
+ Navigate across all highlight occurrences in document order.
226
242
 
227
- #### `goToPage(pageNumber: number): void`
243
+ #### `setPage(pageNumber: number): void`
228
244
 
229
245
  Navigate to a specific page (1-based).
230
246
 
@@ -232,7 +248,7 @@ Navigate to a specific page (1-based).
232
248
 
233
249
  Zoom in or out of the PDF.
234
250
 
235
- #### `setZoom(scale: number): void`
251
+ #### `setZoom(value: ZoomMode | number): void`
236
252
 
237
253
  Set a specific zoom level (e.g., 1.0 for 100%, 1.5 for 150%).
238
254
 
@@ -262,7 +278,7 @@ viewer.addEventListener('pdfLoaded', (e) => {
262
278
  });
263
279
 
264
280
  viewer.addEventListener('pageChanged', (e) => {
265
- console.log('Current page:', e.pageNumber);
281
+ console.log('Current page:', e.currentPage);
266
282
  });
267
283
 
268
284
  viewer.addEventListener('zoomChanged', (e) => {
@@ -278,7 +294,7 @@ viewer.addEventListener('renderError', (e) => {
278
294
  });
279
295
 
280
296
  viewer.addEventListener('highlightsLoaded', (e) => {
281
- console.log('Highlights loaded:', e.data?.length ?? 0);
297
+ console.log('Highlights loaded:', e.count);
282
298
  });
283
299
 
284
300
  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":"AAAA,OAAO,EAAE,kBAAkB,IAAI,mBAAmB,EAAE,MAAM,OAAO,CAAC;AAClE,OAAO,EACL,aAAa,EAKb,SAAS,EACT,qBAAqB,EACrB,kBAAkB,EAClB,kBAAkB,EAClB,qBAAqB,EACrB,eAAe,EACf,kBAAkB,EAElB,cAAc,EACd,SAAS,EAET,gBAAgB,EACjB,MAAM,SAAS,CAAC;AAajB,qBAAa,kBAAmB,YAAW,mBAAmB;IAC5D,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,YAAY,CAAsB;IAC1C,OAAO,CAAC,kBAAkB,CAA4B;IACtD,OAAO,CAAC,oBAAoB,CAAuB;IAEnD,OAAO,CAAC,SAAS,CAA4B;IAC7C,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,cAAc,CAAkC;IAExD,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,eAAe,CAKrB;IACF,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,YAAY,CAAO;IAC3B,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,aAAa,CAAS;IAE9B,OAAO,CAAC,QAAQ,CAAM;IAEtB,OAAO,CAAC,cAAc,CAAwD;IAC9E,OAAO,CAAC,iBAAiB,CAAO;IAEhC,OAAO,CAAC,cAAc,CAA+D;IACrF,OAAO,CAAC,cAAc,CAA6B;IACnD,OAAO,CAAC,SAAS,CAKf;;IAyCI,IAAI,CAAC,SAAS,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAgC1E;;OAEG;IACH,OAAO,CAAC,cAAc;IAgBtB;;OAEG;IACH,OAAO,CAAC,OAAO;IAiHf;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAuB3B,OAAO,CAAC,gBAAgB;IAMxB;;OAEG;IACH,OAAO,CAAC,YAAY;IA8DpB;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAW9B;;OAEG;YACW,iBAAiB;IAmB/B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAY1B;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAoC1B,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAgCjE;;OAEG;YACW,oBAAoB;IA6BlC;;OAEG;YACW,gBAAgB;YAyBhB,qBAAqB;IAoBnC;;OAEG;YACW,UAAU;IAgDxB,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAMlD,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAQjC,OAAO,IAAI,MAAM;IAIjB,OAAO,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI;IAU/B,OAAO,CAAC,SAAS;YAUH,WAAW;YAKX,cAAc;YAKd,gBAAgB;IAW9B,MAAM,IAAI,IAAI;IAId,OAAO,IAAI,IAAI;IAIf,SAAS,IAAI,IAAI;IAIjB,cAAc,IAAI,MAAM;IAIxB,aAAa,IAAI,MAAM;IAIjB,aAAa,CACjB,WAAW,EAAE,MAAM,EAAE,EACrB,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IAIpC,oBAAoB,CACxB,WAAW,EAAE,MAAM,EAAE,EACrB,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAc/B;;OAEG;IACH,mBAAmB,IAAI,IAAI;IAe3B;;OAEG;IACH,oBAAoB,IAAI,IAAI;IAgB5B;;OAEG;IACH,mBAAmB,IAAI,OAAO;IAU9B;;OAEG;IACH,sBAAsB,IAAI,OAAO;IAQjC,cAAc,CAAC,IAAI,EAAE,kBAAkB,EAAE,GAAG,IAAI;IAchD,YAAY,CAAC,SAAS,EAAE,kBAAkB,GAAG,IAAI;IA4BjD,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IA4BrC,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,IAAI;IAkC/E;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAkC9B;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAmB9B;;OAEG;YACW,oBAAoB;IA0DlC,aAAa;sBACC,IAAI;uBAIH,IAAI;4BAIC,MAAM;uCAKK,qBAAqB,GAAG,IAAI;8BAIrC,IAAI;4BAIJ,SAAS,KAAG,IAAI;iCAKZ,OAAO,GAAG,WAAW,GAAG,UAAU,KAAa,IAAI;+CAQpC,cAAc,KAAG,kBAAkB,GAAG,IAAI;MAuBjF;IAMF,OAAO,CAAC,iBAAiB;IAsCzB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,eAAe,SAAI,GAAG,IAAI;IAuCxD,aAAa,IAAI,IAAI;IASrB,iBAAiB,IAAI,IAAI;IASzB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI;IAyB9D,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,kBAAkB,EAAE;IAwBrD,kBAAkB,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI;IAI/C,kBAAkB,IAAI,eAAe;IAQrC,qBAAqB,IAAI,kBAAkB;IAmB3C;;OAEG;IACH,cAAc,IAAI;QAChB,aAAa,EAAE,MAAM,CAAC;QACtB,UAAU,EAAE,MAAM,CAAC;QACnB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,gBAAgB,EAAE,MAAM,CAAC;KAC1B;IAaD,YAAY,IAAI,kBAAkB;IAIlC,eAAe,IAAI,IAAI;IAKvB,gBAAgB,IAAI,IAAI;IASxB,aAAa,EAAE,qBAAqB,CAgClC;IAMF,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,GAAG,IAAI;IAIzE,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,GAAG,IAAI;IAS5E,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,GAAG,IAAI;IAgBrC,aAAa,CAAC,MAAM,GAAE,KAAK,GAAG,MAAc,EAAE,OAAO,SAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAyB3E,WAAW;;;;;;;;;;;IAcX,OAAO,IAAI,IAAI;IAWf,OAAO,CAAC,4BAA4B;IAapC;;OAEG;YACW,mBAAmB;IAwLjC,OAAO,CAAC,gBAAgB;IAUxB,OAAO,CAAC,iBAAiB;IAIzB,OAAO,CAAC,oBAAoB;IAQ5B;;SAEK;IACL,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE;IAgDhE;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAoBhC,OAAO,CAAC,0BAA0B;IAWlC,OAAO,CAAC,oBAAoB;IAmD5B;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAM9B;;;;;;;;;;OAUG;IACH,OAAO,CAAC,wBAAwB;IAKhC;;;;;;;OAOG;IACH,OAAO,CAAC,eAAe;IASvB;;OAEG;IACH,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IA4C3C;;OAEG;IACH,6BAA6B,CAAC,oBAAoB,UAAO,GAAG,IAAI;IA8ChE;;OAEG;YACW,kBAAkB;IAyIhC;;OAEG;IACH,OAAO,CAAC,0BAA0B;IAwClC;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAmD5B,OAAO,IAAI,IAAI;CAsBhB;AAED,eAAe,kBAAkB,CAAC"}
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, scaleLabelStyle, } 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));
@@ -875,6 +881,7 @@ export class PDFHighlightViewer {
875
881
  async reRenderVisiblePages() {
876
882
  if (!this.container)
877
883
  return;
884
+ const pageToKeep = this.currentPage;
878
885
  console.log('Zoom changed to:', this.currentScale);
879
886
  // Clear ALL cached data for the new scale
880
887
  this.pdfEngine.clearAllPageCache();
@@ -903,12 +910,15 @@ export class PDFHighlightViewer {
903
910
  const avgPageHeight = validDimensions > 0 ? totalHeight / validDimensions : this.defaultPageHeight;
904
911
  this.viewportManager.updateDimensions(this.container?.clientHeight || 600, avgPageHeight);
905
912
  // Render currently visible pages
906
- const scrollTop = this.container.scrollTop;
907
913
  const containerHeight = this.container.clientHeight;
908
- const visiblePages = this.viewportManager.getVisiblePages(scrollTop, containerHeight);
909
- console.log('Re-rendering visible pages at new scale:', visiblePages);
914
+ // Restore scroll so we stay on the same page after zoom
915
+ this.container.scrollTop = this.getPageScrollTop(pageToKeep);
916
+ // Recompute visible pages after scroll restore for correct rendering
917
+ const scrollTopAfterRestore = this.container.scrollTop;
918
+ const visiblePagesAfterRestore = this.viewportManager.getVisiblePages(scrollTopAfterRestore, containerHeight);
919
+ console.log('Re-rendering visible pages at new scale:', visiblePagesAfterRestore);
910
920
  // Render visible pages immediately
911
- for (const pageNumber of visiblePages) {
921
+ for (const pageNumber of visiblePagesAfterRestore) {
912
922
  try {
913
923
  await this.renderPage(pageNumber);
914
924
  }
@@ -1174,9 +1184,7 @@ export class PDFHighlightViewer {
1174
1184
  highlightLayer.style.pointerEvents = 'none';
1175
1185
  for (const highlight of this.highlightsIndex.highlights) {
1176
1186
  const style = highlight.style;
1177
- const backgroundColor = style?.backgroundColor ?? '#666666';
1178
- const borderColor = style?.borderColor ?? backgroundColor;
1179
- const borderWidth = style?.borderWidth ?? '1px';
1187
+ const resolvedStyle = resolveHighlightStyle(style);
1180
1188
  for (let bboxIndex = 0; bboxIndex < highlight.bboxes.length; bboxIndex++) {
1181
1189
  const bbox = highlight.bboxes[bboxIndex];
1182
1190
  if (bbox.page !== pageNumber)
@@ -1187,100 +1195,141 @@ export class PDFHighlightViewer {
1187
1195
  highlightDiv.setAttribute('data-term-id', highlight.id);
1188
1196
  highlightDiv.setAttribute('data-page', String(pageNumber));
1189
1197
  highlightDiv.setAttribute('data-bbox-index', String(bboxIndex));
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;
1198
+ const left = Math.round(normalizedBBox.x1 * scale);
1199
+ const top = Math.round(normalizedBBox.y1 * scale);
1200
+ const width = Math.round((normalizedBBox.x2 - normalizedBBox.x1) * scale);
1201
+ const height = Math.round((normalizedBBox.y2 - normalizedBBox.y1) * scale);
1194
1202
  highlightDiv.style.position = 'absolute';
1195
1203
  highlightDiv.style.left = `${left}px`;
1196
1204
  highlightDiv.style.top = `${top}px`;
1197
1205
  highlightDiv.style.width = `${width}px`;
1198
1206
  highlightDiv.style.height = `${height}px`;
1199
- highlightDiv.style.backgroundColor = backgroundColor;
1200
- highlightDiv.style.border = `${borderWidth} solid ${borderColor}`;
1201
1207
  highlightDiv.style.pointerEvents = 'auto';
1202
1208
  highlightDiv.style.cursor = 'pointer';
1203
1209
  highlightDiv.style.boxSizing = 'border-box';
1204
1210
  highlightDiv.style.userSelect = 'none';
1205
- highlightDiv.style.mixBlendMode = 'multiply';
1206
- const baseOpacity = typeof style?.opacity === 'number' ? style.opacity : 0.3;
1211
+ const highlightVisual = document.createElement('span');
1212
+ highlightVisual.className = 'highlight-visual';
1213
+ applyHighlightVisualStyle(highlightVisual, resolvedStyle);
1214
+ applyBaseOutlineStyle(highlightVisual, style);
1215
+ highlightDiv.appendChild(highlightVisual);
1216
+ const baseOpacity = getHighlightBaseOpacity(style);
1207
1217
  const overlappingCount = this.countOverlappingHighlights(highlightLayer, normalizedBBox, scale);
1208
1218
  const effectiveOpacity = Math.max(0.05, baseOpacity / Math.max(1, overlappingCount * 0.7));
1209
- highlightDiv.style.opacity = effectiveOpacity.toString();
1219
+ highlightVisual.style.opacity = effectiveOpacity.toString();
1210
1220
  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';
1221
+ const hoverOpacity = getHighlightHoverOpacity(style, effectiveOpacity);
1222
+ const termLabelsSelector = `.highlight-label[data-term-id="${highlight.id}"]`;
1215
1223
  highlightDiv.addEventListener('mouseenter', () => {
1216
1224
  if (this.options.highlightsConfig?.enableMultilineHover) {
1217
1225
  const same = highlightLayer.querySelectorAll(`.highlight[data-term-id="${highlight.id}"]`);
1218
- same.forEach((el) => (el.style.opacity = String(hoverOpacity)));
1226
+ same.forEach((el) => {
1227
+ const htmlEl = el;
1228
+ const visual = htmlEl.querySelector('.highlight-visual');
1229
+ if (visual)
1230
+ visual.style.opacity = String(hoverOpacity);
1231
+ });
1232
+ this.getLabelsForTerm(highlightLayer, highlight.id).forEach((sameLabel) => {
1233
+ sameLabel.style.opacity = String(hoverOpacity);
1234
+ });
1219
1235
  const other = highlightLayer.querySelectorAll(`.highlight[data-term-id]:not([data-term-id="${highlight.id}"])`);
1220
- other.forEach((el) => (el.style.opacity = '0.1'));
1236
+ other.forEach((el) => {
1237
+ const htmlEl = el;
1238
+ const visual = htmlEl.querySelector('.highlight-visual');
1239
+ if (visual)
1240
+ visual.style.opacity = '0.1';
1241
+ });
1242
+ highlightLayer
1243
+ .querySelectorAll(`.highlight-label:not([data-term-id="${highlight.id}"])`)
1244
+ .forEach((otherLabel) => {
1245
+ otherLabel.style.opacity = '0.1';
1246
+ });
1221
1247
  }
1222
1248
  else {
1223
- highlightDiv.style.opacity = String(hoverOpacity);
1224
- }
1225
- const labelEl = highlightDiv.querySelector('.highlight-label');
1226
- if (labelEl) {
1227
- labelEl.style.borderColor = borderColor;
1249
+ highlightVisual.style.opacity = String(hoverOpacity);
1228
1250
  }
1251
+ highlightLayer.querySelectorAll(termLabelsSelector).forEach((labelEl) => {
1252
+ labelEl.style.opacity = String(hoverOpacity);
1253
+ });
1229
1254
  });
1230
1255
  highlightDiv.addEventListener('mouseleave', () => {
1231
1256
  if (this.options.highlightsConfig?.enableMultilineHover) {
1232
1257
  const all = highlightLayer.querySelectorAll('.highlight[data-term-id]');
1258
+ const baseOpacityByTermId = new Map();
1233
1259
  all.forEach((el) => {
1234
- const original = el.dataset.originalOpacity ?? '0.3';
1235
- el.style.opacity = original;
1260
+ const htmlEl = el;
1261
+ const original = htmlEl.dataset.originalOpacity ?? '0.3';
1262
+ const visual = htmlEl.querySelector('.highlight-visual');
1263
+ if (visual)
1264
+ visual.style.opacity = original;
1265
+ const termId = htmlEl.getAttribute('data-term-id');
1266
+ if (termId) {
1267
+ baseOpacityByTermId.set(termId, original);
1268
+ }
1269
+ });
1270
+ highlightLayer
1271
+ .querySelectorAll('.highlight-label')
1272
+ .forEach((allLabel) => {
1273
+ const allTermId = allLabel.getAttribute('data-term-id');
1274
+ if (!allTermId)
1275
+ return;
1276
+ const fallbackBaseOpacity = baseOpacityByTermId.get(allTermId);
1277
+ if (fallbackBaseOpacity !== undefined) {
1278
+ allLabel.style.opacity = fallbackBaseOpacity;
1279
+ return;
1280
+ }
1281
+ const allHighlight = this.getHighlightById(allTermId);
1282
+ allLabel.style.opacity = String(typeof allHighlight?.style?.opacity === 'number'
1283
+ ? allHighlight.style.opacity
1284
+ : 0.3);
1236
1285
  });
1237
1286
  }
1238
1287
  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;
1288
+ highlightVisual.style.opacity = highlightDiv.dataset.originalOpacity ?? '0.3';
1244
1289
  }
1290
+ highlightLayer.querySelectorAll(termLabelsSelector).forEach((labelEl) => {
1291
+ labelEl.style.opacity = String(effectiveOpacity);
1292
+ });
1245
1293
  });
1246
1294
  highlightLayer.appendChild(highlightDiv);
1247
1295
  if (highlight.label || highlight.beforeIcon) {
1296
+ const effectiveLabelStyle = highlight.isLabelScalable && scale !== 1
1297
+ ? scaleLabelStyle(highlight.labelStyle, scale)
1298
+ : highlight.labelStyle;
1248
1299
  const labelEl = document.createElement('span');
1249
1300
  labelEl.className = 'highlight-label';
1250
1301
  labelEl.setAttribute('data-term-id', highlight.id);
1302
+ labelEl.setAttribute('data-bbox-index', String(bboxIndex));
1303
+ labelEl.dataset.baseLeft = String(left);
1304
+ labelEl.dataset.baseTop = String(top);
1305
+ const { left: labelOffsetLeft, top: labelOffsetTop } = this.getLabelOffsets(effectiveLabelStyle);
1251
1306
  labelEl.style.position = 'absolute';
1252
- labelEl.style.left = '0';
1307
+ labelEl.style.left = `${left + labelOffsetLeft}px`;
1308
+ labelEl.style.top = `${top + labelOffsetTop}px`;
1309
+ labelEl.style.boxSizing = 'border-box';
1310
+ labelEl.style.zIndex = '3';
1253
1311
  labelEl.style.transform = 'translateX(-100%)';
1254
- labelEl.style.top = '-1px';
1255
1312
  labelEl.style.display = 'flex';
1256
1313
  labelEl.style.alignItems = 'center';
1257
1314
  labelEl.style.justifyContent = 'flex-end';
1258
- labelEl.style.gap = '4px';
1315
+ labelEl.style.gap = highlight.isLabelScalable ? `${4 * scale}px` : '4px';
1259
1316
  labelEl.style.pointerEvents = 'auto';
1260
1317
  labelEl.style.cursor = 'pointer';
1261
1318
  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);
1319
+ if (effectiveLabelStyle?.borderColor !== undefined ||
1320
+ effectiveLabelStyle?.borderWidth !== undefined) {
1321
+ const borderColor = effectiveLabelStyle?.borderColor ?? 'currentColor';
1322
+ const borderWidth = effectiveLabelStyle?.borderWidth ?? '1px';
1323
+ labelEl.style.border = `${borderWidth} solid ${borderColor}`;
1279
1324
  }
1325
+ applyLabelOutlineStyle(labelEl, effectiveLabelStyle);
1326
+ applyLabelStyle(labelEl, effectiveLabelStyle);
1327
+ labelEl.style.opacity = String(effectiveOpacity);
1328
+ appendLabelIcon(labelEl, highlight.beforeIcon, effectiveLabelStyle);
1280
1329
  if (highlight.label) {
1281
1330
  labelEl.appendChild(document.createTextNode(highlight.label));
1282
1331
  }
1283
- highlightDiv.appendChild(labelEl);
1332
+ highlightLayer.appendChild(labelEl);
1284
1333
  }
1285
1334
  }
1286
1335
  }
@@ -1305,6 +1354,15 @@ export class PDFHighlightViewer {
1305
1354
  getHighlightStyle(termId) {
1306
1355
  return this.getHighlightById(termId)?.style;
1307
1356
  }
1357
+ getLabelOffsets(style) {
1358
+ return {
1359
+ left: style?.offsetLeft ?? 0,
1360
+ top: style?.offsetTop ?? -1,
1361
+ };
1362
+ }
1363
+ getLabelsForTerm(highlightLayer, termId) {
1364
+ return Array.from(highlightLayer.querySelectorAll(`.highlight-label[data-term-id="${termId}"]`));
1365
+ }
1308
1366
  getHighlightElements(root, termId) {
1309
1367
  const selector = termId
1310
1368
  ? `.highlight[data-term-id="${termId}"], .highlight-wrapper[data-term-id="${termId}"]`
@@ -1326,29 +1384,63 @@ export class PDFHighlightViewer {
1326
1384
  const termId = el.getAttribute('data-term-id');
1327
1385
  if (!termId)
1328
1386
  return;
1387
+ const highlight = this.getHighlightById(termId);
1329
1388
  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}`;
1389
+ const { backgroundColor: bg, borderColor, borderWidth, } = resolveHighlightStyle(style, el.style.backgroundColor || '#666666');
1390
+ const visual = el.querySelector('.highlight-visual') ?? el;
1391
+ visual.style.backgroundColor = bg;
1392
+ visual.style.border = `${borderWidth} solid ${borderColor}`;
1393
+ applyBaseOutlineStyle(visual, style);
1394
+ const highlightBaseOpacity = typeof style?.opacity === 'number'
1395
+ ? style.opacity
1396
+ : parseFloat(el.dataset.originalOpacity ?? '0.3');
1397
+ const bboxIdx = el.getAttribute('data-bbox-index');
1398
+ const isWrapper = el.classList.contains('highlight-wrapper');
1399
+ const scale = this.currentScale;
1400
+ const effectiveLabelStyle = highlight?.isLabelScalable && scale !== 1
1401
+ ? scaleLabelStyle(highlight.labelStyle, scale)
1402
+ : highlight?.labelStyle;
1403
+ const labelEl = isWrapper
1404
+ ? el.querySelector('.highlight-label')
1405
+ : bboxIdx !== null
1406
+ ? highlightLayer.querySelector(`.highlight-label[data-term-id="${termId}"][data-bbox-index="${bboxIdx}"]`)
1407
+ : highlightLayer.querySelector(`.highlight-label[data-term-id="${termId}"]`);
1408
+ if (labelEl) {
1409
+ if (!isWrapper) {
1410
+ const baseLabelLeft = parseFloat(labelEl.dataset.baseLeft ?? '0');
1411
+ const baseLabelTop = parseFloat(labelEl.dataset.baseTop ?? '0');
1412
+ const { left: labelOffsetLeft, top: labelOffsetTop } = this.getLabelOffsets(effectiveLabelStyle);
1413
+ labelEl.style.left = `${baseLabelLeft + labelOffsetLeft}px`;
1414
+ labelEl.style.top = `${baseLabelTop + labelOffsetTop}px`;
1415
+ }
1416
+ applyLabelStyle(labelEl, effectiveLabelStyle);
1417
+ applyLabelOutlineStyle(labelEl, effectiveLabelStyle);
1418
+ labelEl.style.opacity = String(highlightBaseOpacity);
1419
+ }
1335
1420
  if (this.options.highlightsConfig?.enableMultilineHover &&
1336
1421
  hoveredIds &&
1337
1422
  Array.isArray(hoveredIds)) {
1338
- const baseOpacity = typeof style?.opacity === 'number'
1339
- ? style.opacity
1423
+ const baseOpacity = style
1424
+ ? getHighlightBaseOpacity(style)
1340
1425
  : parseFloat(el.dataset.originalOpacity ?? '0.3');
1341
- const hoverOpacity = typeof style?.hoverOpacity === 'number'
1342
- ? style.hoverOpacity
1343
- : Math.min(0.6, baseOpacity + 0.2);
1426
+ const hoverOpacity = getHighlightHoverOpacity(style, baseOpacity);
1344
1427
  if (hoveredIds.includes(termId)) {
1345
- el.style.opacity = String(hoverOpacity);
1428
+ visual.style.opacity = String(hoverOpacity);
1429
+ if (labelEl) {
1430
+ labelEl.style.opacity = String(hoverOpacity);
1431
+ }
1346
1432
  }
1347
1433
  else if (hoveredIds.length > 0) {
1348
- el.style.opacity = '0.1';
1434
+ visual.style.opacity = '0.1';
1435
+ if (labelEl) {
1436
+ labelEl.style.opacity = '0.1';
1437
+ }
1349
1438
  }
1350
1439
  else {
1351
- el.style.opacity = String(baseOpacity);
1440
+ visual.style.opacity = String(baseOpacity);
1441
+ if (labelEl) {
1442
+ labelEl.style.opacity = String(baseOpacity);
1443
+ }
1352
1444
  }
1353
1445
  }
1354
1446
  });
@@ -1566,6 +1658,9 @@ export class PDFHighlightViewer {
1566
1658
  // textLayer.style.opacity = '0.1'; // Uncomment for debugging text boundaries
1567
1659
  // Process text items with proper PDF coordinate transformation
1568
1660
  textContent.items.forEach((item) => {
1661
+ if (!('str' in item) || !('transform' in item) || !('fontName' in item)) {
1662
+ return;
1663
+ }
1569
1664
  if (!item.str || !item.str.trim())
1570
1665
  return; // Skip empty text
1571
1666
  const textSpan = document.createElement('span');