pdf-diff-viewer 1.2.0 → 1.3.2
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 +46 -9
- package/package.json +1 -1
- package/src/PDFDiffViewer.js +36 -19
package/README.md
CHANGED
|
@@ -136,15 +136,17 @@ new PDFDiffViewer(container, options)
|
|
|
136
136
|
- `colorTolerance` (number) - Color difference threshold, default: 120
|
|
137
137
|
- `minHighlightArea` (number) - Min area to highlight in pixels, default: 60
|
|
138
138
|
- `minWordSize` (number) - Min word box size in pixels, default: 8
|
|
139
|
-
- `highlightAlpha` (number) - Highlight transparency, default: 0.32
|
|
139
|
+
- `highlightAlpha` (number) - Highlight transparency (0-1), default: 0.32
|
|
140
|
+
- **`highlightColorA` (string)** - Hex color for Document A highlights, default: '#FF1744' (red)
|
|
141
|
+
- **`highlightColorB` (string)** - Hex color for Document B highlights, default: '#2196F3' (blue)
|
|
142
|
+
- **`backgroundFillColor` (string)** - Canvas background fill color, default: 'white'
|
|
140
143
|
- `labelA` (string) - Label for first document, default: 'Document A'
|
|
141
144
|
- `labelB` (string) - Label for second document, default: 'Document B'
|
|
142
145
|
- `showPageNumbers` (boolean) - Show page numbers, default: true
|
|
143
146
|
- `cropRegions` (Array) - Regions to crop: `[{ page: 1, x, y, width, height }]`
|
|
144
147
|
- `maskRegions` (Array) - Regions to mask/ignore: `[{ page: 1, x, y, width, height }]`
|
|
145
|
-
-
|
|
146
|
-
-
|
|
147
|
-
- **`similarityThreshold` (number)** - Minimum text similarity (0-1) for page matching, default: 0.3
|
|
148
|
+
- `alignmentTolerance` (number) - Search range for matching pages (+/- pages), default: 2
|
|
149
|
+
- `similarityThreshold` (number) - Minimum text similarity (0-1) for page matching, default: 0.3
|
|
148
150
|
|
|
149
151
|
### Methods
|
|
150
152
|
|
|
@@ -238,6 +240,41 @@ const viewer = new PDFDiffViewer('#container', {
|
|
|
238
240
|
});
|
|
239
241
|
```
|
|
240
242
|
|
|
243
|
+
### Custom Highlight Colors
|
|
244
|
+
|
|
245
|
+
```javascript
|
|
246
|
+
// Perfect for gray backgrounds (#D9D9D9)
|
|
247
|
+
const viewer = new PDFDiffViewer('#container', {
|
|
248
|
+
highlightColorA: '#FF1744', // Vibrant red for Doc A changes
|
|
249
|
+
highlightColorB: '#2196F3', // Bright blue for Doc B changes
|
|
250
|
+
backgroundFillColor: '#D9D9D9', // Match your PDF background
|
|
251
|
+
highlightAlpha: 0.4 // Adjust transparency
|
|
252
|
+
});
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
**Recommended color combinations:**
|
|
256
|
+
|
|
257
|
+
For light backgrounds:
|
|
258
|
+
```javascript
|
|
259
|
+
// Red vs Blue (high contrast)
|
|
260
|
+
{ highlightColorA: '#FF1744', highlightColorB: '#2196F3' }
|
|
261
|
+
|
|
262
|
+
// Purple vs Orange
|
|
263
|
+
{ highlightColorA: '#9C27B0', highlightColorB: '#FF6F00' }
|
|
264
|
+
|
|
265
|
+
// Pink vs Teal
|
|
266
|
+
{ highlightColorA: '#E91E63', highlightColorB: '#00BCD4' }
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
For dark backgrounds:
|
|
270
|
+
```javascript
|
|
271
|
+
// Bright yellow vs Cyan
|
|
272
|
+
{ highlightColorA: '#FFEB3B', highlightColorB: '#00E5FF' }
|
|
273
|
+
|
|
274
|
+
// Lime vs Magenta
|
|
275
|
+
{ highlightColorA: '#CDDC39', highlightColorB: '#E040FB' }
|
|
276
|
+
```
|
|
277
|
+
|
|
241
278
|
### With Crop Regions (Compare Specific Areas)
|
|
242
279
|
|
|
243
280
|
```javascript
|
|
@@ -251,12 +288,12 @@ const viewer = new PDFDiffViewer('#container', {
|
|
|
251
288
|
### With Smart Alignment (Content Reflow Handling)
|
|
252
289
|
|
|
253
290
|
```javascript
|
|
291
|
+
// Smart alignment is now automatic! No configuration needed
|
|
254
292
|
// Handles cases where content shifts across pages
|
|
255
293
|
// (e.g., adding text pushes content to next page)
|
|
256
294
|
const viewer = new PDFDiffViewer('#container', {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
similarityThreshold: 0.3 // Require 30% content similarity
|
|
295
|
+
alignmentTolerance: 2, // Search +/- 2 pages for matches (default)
|
|
296
|
+
similarityThreshold: 0.3 // Require 30% content similarity (default)
|
|
260
297
|
});
|
|
261
298
|
|
|
262
299
|
const results = await viewer.compare(pdfA, pdfB);
|
|
@@ -266,10 +303,10 @@ console.log('Page mappings:', results.pageMapping);
|
|
|
266
303
|
```
|
|
267
304
|
|
|
268
305
|
**How it works:**
|
|
306
|
+
- Automatically detects and handles different page counts
|
|
269
307
|
- Extracts text from all pages in both documents
|
|
270
308
|
- Uses Jaccard similarity to find best-matching pages
|
|
271
|
-
-
|
|
272
|
-
- Shows similarity scores in the UI
|
|
309
|
+
- Shows similarity scores in the results
|
|
273
310
|
|
|
274
311
|
### With Mask Regions (Ignore Dynamic Content)
|
|
275
312
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pdf-diff-viewer",
|
|
3
|
-
"version": "1.2
|
|
3
|
+
"version": "1.3.2",
|
|
4
4
|
"description": "Browser-based PDF comparison tool with visual diff highlighting. Zero system dependencies, pure JavaScript, client-side processing.",
|
|
5
5
|
"main": "src/PDFDiffViewer.js",
|
|
6
6
|
"type": "module",
|
package/src/PDFDiffViewer.js
CHANGED
|
@@ -20,19 +20,21 @@ class PDFDiffViewer {
|
|
|
20
20
|
scale: options.scale || 3.0,
|
|
21
21
|
maxShift: options.maxShift || 3,
|
|
22
22
|
dilationRadius: options.dilationRadius || 0,
|
|
23
|
-
colorTolerance: options.colorTolerance ||
|
|
23
|
+
colorTolerance: options.colorTolerance || 120,
|
|
24
24
|
minHighlightArea: options.minHighlightArea || 60,
|
|
25
25
|
minWordSize: options.minWordSize || 8,
|
|
26
26
|
highlightAlpha: options.highlightAlpha || 0.32,
|
|
27
|
+
highlightColorA: options.highlightColorA || '#FF1744', // Red for Doc A changes
|
|
28
|
+
highlightColorB: options.highlightColorB || '#2196F3', // Blue for Doc B changes
|
|
29
|
+
backgroundFillColor: options.backgroundFillColor || 'white', // Canvas background
|
|
27
30
|
labelA: options.labelA || 'Document A',
|
|
28
31
|
labelB: options.labelB || 'Document B',
|
|
29
32
|
workerSrc: options.workerSrc || 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js',
|
|
30
33
|
showPageNumbers: options.showPageNumbers !== false,
|
|
31
34
|
cropRegions: options.cropRegions || [],
|
|
32
35
|
maskRegions: options.maskRegions || [],
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
similarityThreshold: options.similarityThreshold || 0.3 // Minimum similarity score (0-1)
|
|
36
|
+
alignmentTolerance: options.alignmentTolerance || 2,
|
|
37
|
+
similarityThreshold: options.similarityThreshold || 0.3
|
|
36
38
|
};
|
|
37
39
|
|
|
38
40
|
// Check if PDF.js is loaded
|
|
@@ -72,13 +74,9 @@ class PDFDiffViewer {
|
|
|
72
74
|
summaryDiv.className = 'pdf-diff-summary';
|
|
73
75
|
this.container.appendChild(summaryDiv);
|
|
74
76
|
|
|
75
|
-
//
|
|
76
|
-
if (
|
|
77
|
-
summaryDiv.innerHTML = '<p>Analyzing document structure for smart alignment...</p>';
|
|
77
|
+
// Automatically handle different page counts with smart alignment
|
|
78
|
+
if (docA.numPages !== docB.numPages) {
|
|
78
79
|
pageMapping = await this._findPageMappings(docA, docB);
|
|
79
|
-
summaryDiv.innerHTML = `<h3>Smart Alignment Active: Comparing ${pageMapping.length} matched page(s)</h3>`;
|
|
80
|
-
} else if (docA.numPages !== docB.numPages) {
|
|
81
|
-
throw new Error(`Page count mismatch: ${docA.numPages} vs ${docB.numPages}. Enable 'smartAlignment' option to handle different page counts.`);
|
|
82
80
|
} else {
|
|
83
81
|
// Direct 1-to-1 mapping
|
|
84
82
|
for (let i = 1; i <= docA.numPages; i++) {
|
|
@@ -105,11 +103,7 @@ class PDFDiffViewer {
|
|
|
105
103
|
|
|
106
104
|
// Update summary
|
|
107
105
|
if (this.options.showPageNumbers) {
|
|
108
|
-
|
|
109
|
-
summaryDiv.innerHTML = `<h3>Comparison Results: ${docA.numPages} page(s)</h3>`;
|
|
110
|
-
} else {
|
|
111
|
-
summaryDiv.innerHTML += `<p>Doc A: ${docA.numPages} pages | Doc B: ${docB.numPages} pages</p>`;
|
|
112
|
-
}
|
|
106
|
+
summaryDiv.innerHTML = `<h3>Comparison Results: ${pageMapping.length} page(s)</h3>`;
|
|
113
107
|
}
|
|
114
108
|
|
|
115
109
|
return this.results;
|
|
@@ -358,7 +352,7 @@ class PDFDiffViewer {
|
|
|
358
352
|
padded.height = targetHeight;
|
|
359
353
|
|
|
360
354
|
const ctx = padded.getContext('2d');
|
|
361
|
-
ctx.fillStyle =
|
|
355
|
+
ctx.fillStyle = this.options.backgroundFillColor;
|
|
362
356
|
ctx.fillRect(0, 0, targetWidth, targetHeight);
|
|
363
357
|
ctx.drawImage(srcCanvas, 0, 0);
|
|
364
358
|
return padded;
|
|
@@ -369,7 +363,7 @@ class PDFDiffViewer {
|
|
|
369
363
|
temp.width = width;
|
|
370
364
|
temp.height = height;
|
|
371
365
|
const ctx = temp.getContext('2d');
|
|
372
|
-
ctx.fillStyle =
|
|
366
|
+
ctx.fillStyle = this.options.backgroundFillColor;
|
|
373
367
|
ctx.fillRect(0, 0, width, height);
|
|
374
368
|
ctx.drawImage(srcCanvas, dx, dy);
|
|
375
369
|
return ctx.getImageData(0, 0, width, height);
|
|
@@ -529,11 +523,14 @@ class PDFDiffViewer {
|
|
|
529
523
|
_drawHighlightBoxes(ctx, boxes, color = 'red') {
|
|
530
524
|
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
531
525
|
const alpha = this.options.highlightAlpha;
|
|
526
|
+
|
|
527
|
+
// Use configured colors or fallback to color parameter
|
|
532
528
|
if (color === 'red') {
|
|
533
|
-
ctx.fillStyle =
|
|
529
|
+
ctx.fillStyle = this._hexToRgba(this.options.highlightColorA, alpha);
|
|
534
530
|
} else if (color === 'green') {
|
|
535
|
-
ctx.fillStyle =
|
|
531
|
+
ctx.fillStyle = this._hexToRgba(this.options.highlightColorB, alpha);
|
|
536
532
|
}
|
|
533
|
+
|
|
537
534
|
boxes.forEach(({ x, y, width, height }) => {
|
|
538
535
|
ctx.fillRect(x, y, width, height);
|
|
539
536
|
});
|
|
@@ -784,6 +781,26 @@ class PDFDiffViewer {
|
|
|
784
781
|
.split(/\s+/)
|
|
785
782
|
.filter(word => word.length > 2 && !stopwords.has(word));
|
|
786
783
|
}
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* Convert hex color to rgba with alpha
|
|
787
|
+
*/
|
|
788
|
+
_hexToRgba(hex, alpha) {
|
|
789
|
+
// Handle both #RGB and #RRGGBB formats
|
|
790
|
+
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
|
|
791
|
+
hex = hex.replace(shorthandRegex, (m, r, g, b) => r + r + g + g + b + b);
|
|
792
|
+
|
|
793
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
794
|
+
if (result) {
|
|
795
|
+
const r = parseInt(result[1], 16);
|
|
796
|
+
const g = parseInt(result[2], 16);
|
|
797
|
+
const b = parseInt(result[3], 16);
|
|
798
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// Fallback to red if invalid hex
|
|
802
|
+
return `rgba(255, 0, 0, ${alpha})`;
|
|
803
|
+
}
|
|
787
804
|
}
|
|
788
805
|
|
|
789
806
|
// Export for different module systems
|