@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 +45 -29
- package/dist/PDFHighlightViewer.d.ts +8 -5
- package/dist/PDFHighlightViewer.d.ts.map +1 -1
- package/dist/PDFHighlightViewer.js +166 -71
- package/dist/PDFHighlightViewer.js.map +1 -1
- package/dist/api.d.ts +2 -2
- package/dist/api.d.ts.map +1 -1
- package/dist/core/pdf-engine.d.ts.map +1 -1
- package/dist/core/pdf-engine.js +1 -0
- package/dist/core/pdf-engine.js.map +1 -1
- package/dist/core/unified-layer-builder.d.ts +0 -1
- package/dist/core/unified-layer-builder.d.ts.map +1 -1
- package/dist/core/unified-layer-builder.js +16 -34
- package/dist/core/unified-layer-builder.js.map +1 -1
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/highlight-style.d.ts +7 -0
- package/dist/utils/highlight-style.d.ts.map +1 -0
- package/dist/utils/highlight-style.js +31 -0
- package/dist/utils/highlight-style.js.map +1 -0
- package/dist/utils/label-style.d.ts +13 -1
- package/dist/utils/label-style.d.ts.map +1 -1
- package/dist/utils/label-style.js +100 -0
- package/dist/utils/label-style.js.map +1 -1
- package/package.json +1 -1
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?:
|
|
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
|
-
###
|
|
165
|
+
### ViewerOptions
|
|
150
166
|
|
|
151
167
|
```ts
|
|
152
|
-
interface
|
|
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?:
|
|
187
|
+
interactionMode?: InteractionMode;
|
|
167
188
|
|
|
168
|
-
//
|
|
169
|
-
|
|
189
|
+
// Performance mode (smaller frame budget)
|
|
190
|
+
performanceMode?: boolean;
|
|
170
191
|
|
|
171
|
-
//
|
|
172
|
-
|
|
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?:
|
|
200
|
+
bboxOrigin?: BBoxOrigin;
|
|
182
201
|
|
|
183
202
|
// Page dimensions for which bbox coordinates were calculated
|
|
184
|
-
bboxSourceDimensions?:
|
|
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?:
|
|
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
|
-
|
|
233
|
+
Update highlight style by id (patch merge). Optionally pass `labelStylePatch` to update label styles as well.
|
|
217
234
|
|
|
218
|
-
|
|
235
|
+
#### `goToHighlight(termId: string, occurrenceIndex?: number): void`
|
|
219
236
|
|
|
220
|
-
|
|
237
|
+
Navigate to a specific highlight occurrence. `occurrenceIndex` defaults to `0`.
|
|
221
238
|
|
|
222
|
-
|
|
239
|
+
#### `nextHighlight(): void` / `previousHighlight(): void`
|
|
223
240
|
|
|
224
|
-
|
|
225
|
-
- with `termId` → only within that highlight’s occurrences
|
|
241
|
+
Navigate across all highlight occurrences in document order.
|
|
226
242
|
|
|
227
|
-
#### `
|
|
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(
|
|
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.
|
|
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.
|
|
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:
|
|
154
|
-
removeEventListener(event: string, callback:
|
|
155
|
-
emit(event: string, data?:
|
|
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;
|
|
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,mBAAmB,EACnB,SAAS,EAET,gBAAgB,EACjB,MAAM,SAAS,CAAC;AAuBjB,KAAK,aAAa,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;AAE9C,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,CAAoD;IAC1E,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,CAClB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,OAAO,CAAC,cAAc,CAAC,EACnC,eAAe,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,GAC7C,IAAI;IAwCP;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAkC9B;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAmB9B;;OAEG;YACW,oBAAoB;IAoElC,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,aAAa,GAAG,IAAI;IAI9D,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,GAAG,IAAI;IASjE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI;IAgBzC,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;IAiOjC,OAAO,CAAC,gBAAgB;IAUxB,OAAO,CAAC,iBAAiB;IAIzB,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,gBAAgB;IAMxB,OAAO,CAAC,oBAAoB;IAQ5B;;SAEK;IACL,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE;IA0FhE;;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;IA4IhC;;OAEG;IACH,OAAO,CAAC,0BAA0B;IAwClC;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAmD5B,OAAO,IAAI,IAAI;CAsBhB;AAED,eAAe,kBAAkB,CAAC"}
|
|
@@ -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 {
|
|
9
|
-
import {
|
|
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
|
-
|
|
909
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
1206
|
-
|
|
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
|
-
|
|
1219
|
+
highlightVisual.style.opacity = effectiveOpacity.toString();
|
|
1210
1220
|
highlightDiv.dataset.originalOpacity = effectiveOpacity.toString();
|
|
1211
|
-
const hoverOpacity =
|
|
1212
|
-
|
|
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) =>
|
|
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) =>
|
|
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
|
-
|
|
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
|
|
1235
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
const
|
|
1266
|
-
|
|
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
|
-
|
|
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
|
|
1331
|
-
const
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
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 =
|
|
1339
|
-
? style
|
|
1423
|
+
const baseOpacity = style
|
|
1424
|
+
? getHighlightBaseOpacity(style)
|
|
1340
1425
|
: parseFloat(el.dataset.originalOpacity ?? '0.3');
|
|
1341
|
-
const hoverOpacity =
|
|
1342
|
-
? style.hoverOpacity
|
|
1343
|
-
: Math.min(0.6, baseOpacity + 0.2);
|
|
1426
|
+
const hoverOpacity = getHighlightHoverOpacity(style, baseOpacity);
|
|
1344
1427
|
if (hoveredIds.includes(termId)) {
|
|
1345
|
-
|
|
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
|
-
|
|
1434
|
+
visual.style.opacity = '0.1';
|
|
1435
|
+
if (labelEl) {
|
|
1436
|
+
labelEl.style.opacity = '0.1';
|
|
1437
|
+
}
|
|
1349
1438
|
}
|
|
1350
1439
|
else {
|
|
1351
|
-
|
|
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');
|