@polotno/pdf-export 0.1.38 → 0.1.40
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 +61 -8
- package/lib/index.d.ts +66 -8
- package/lib/index.js +25 -145
- package/package.json +17 -18
- package/lib/compare-render.d.ts +0 -1
- package/lib/compare-render.js +0 -185
- package/lib/figure.d.ts +0 -10
- package/lib/figure.js +0 -54
- package/lib/filters.d.ts +0 -2
- package/lib/filters.js +0 -163
- package/lib/ghostscript.d.ts +0 -21
- package/lib/ghostscript.js +0 -132
- package/lib/group.d.ts +0 -5
- package/lib/group.js +0 -5
- package/lib/image.d.ts +0 -38
- package/lib/image.js +0 -279
- package/lib/line.d.ts +0 -10
- package/lib/line.js +0 -66
- package/lib/pdf-import/coordinate-transform.d.ts +0 -51
- package/lib/pdf-import/coordinate-transform.js +0 -99
- package/lib/pdf-import/element-builder.d.ts +0 -21
- package/lib/pdf-import/element-builder.js +0 -163
- package/lib/pdf-import/font-mapper.d.ts +0 -17
- package/lib/pdf-import/font-mapper.js +0 -142
- package/lib/pdf-import/index.d.ts +0 -35
- package/lib/pdf-import/index.js +0 -105
- package/lib/pdf-import/parser.d.ts +0 -29
- package/lib/pdf-import/parser.js +0 -285
- package/lib/pdf-import/text-analysis.d.ts +0 -17
- package/lib/pdf-import/text-analysis.js +0 -186
- package/lib/pdf-import/types.d.ts +0 -101
- package/lib/pdf-import/types.js +0 -1
- package/lib/scripts/compare-json.d.ts +0 -1
- package/lib/scripts/compare-json.js +0 -141
- package/lib/spot-colors.d.ts +0 -38
- package/lib/spot-colors.js +0 -141
- package/lib/svg-render.d.ts +0 -9
- package/lib/svg-render.js +0 -63
- package/lib/svg.d.ts +0 -12
- package/lib/svg.js +0 -224
- package/lib/text/fonts.d.ts +0 -16
- package/lib/text/fonts.js +0 -113
- package/lib/text/index.d.ts +0 -8
- package/lib/text/index.js +0 -42
- package/lib/text/layout.d.ts +0 -22
- package/lib/text/layout.js +0 -522
- package/lib/text/parser.d.ts +0 -46
- package/lib/text/parser.js +0 -415
- package/lib/text/render.d.ts +0 -8
- package/lib/text/render.js +0 -237
- package/lib/text/types.d.ts +0 -91
- package/lib/text/types.js +0 -1
- package/lib/text.d.ts +0 -39
- package/lib/text.js +0 -576
- package/lib/utils.d.ts +0 -16
- package/lib/utils.js +0 -124
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
import { extractPosition, extractRotation, pdfToPolotnoX, pdfToPolotnoY, pdfColorToHex, } from './coordinate-transform.js';
|
|
2
|
-
import { mapFont } from './font-mapper.js';
|
|
3
|
-
/**
|
|
4
|
-
* Sort text items by position (top-to-bottom, left-to-right)
|
|
5
|
-
*/
|
|
6
|
-
export function sortTextItems(items, pageHeight) {
|
|
7
|
-
return items.sort((a, b) => {
|
|
8
|
-
const posA = extractPosition(a.transform);
|
|
9
|
-
const posB = extractPosition(b.transform);
|
|
10
|
-
// Convert PDF Y (bottom-origin) to top-origin for comparison
|
|
11
|
-
const yA = pageHeight - posA.y;
|
|
12
|
-
const yB = pageHeight - posB.y;
|
|
13
|
-
// Same line threshold (5 pixels)
|
|
14
|
-
const lineThreshold = 5;
|
|
15
|
-
if (Math.abs(yA - yB) < lineThreshold) {
|
|
16
|
-
// Same line, sort by X (left to right)
|
|
17
|
-
return posA.x - posB.x;
|
|
18
|
-
}
|
|
19
|
-
// Different lines, sort by Y (top to bottom)
|
|
20
|
-
return yA - yB;
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Check if two text items should be in the same block based on proximity and font
|
|
25
|
-
*/
|
|
26
|
-
function shouldMerge(item1, item2, pageHeight, options) {
|
|
27
|
-
const pos1 = extractPosition(item1.transform);
|
|
28
|
-
const pos2 = extractPosition(item2.transform);
|
|
29
|
-
// Get thresholds from options
|
|
30
|
-
const verticalThreshold = item1.fontSize; // options.textClusterThreshold?.vertical ?? 20;
|
|
31
|
-
const horizontalThreshold = options.textClusterThreshold?.horizontal ?? 10;
|
|
32
|
-
// Convert PDF Y to top-origin
|
|
33
|
-
const y1 = pageHeight - pos1.y;
|
|
34
|
-
const y2 = pageHeight - pos2.y;
|
|
35
|
-
// Check vertical distance
|
|
36
|
-
const verticalDistance = Math.abs(y1 - y2);
|
|
37
|
-
if (verticalDistance > verticalThreshold) {
|
|
38
|
-
return false;
|
|
39
|
-
}
|
|
40
|
-
// Check horizontal distance (for same line)
|
|
41
|
-
const horizontalDistance = Math.abs(pos2.x - (pos1.x + item1.width));
|
|
42
|
-
if (verticalDistance < 5 && horizontalDistance > horizontalThreshold) {
|
|
43
|
-
return false;
|
|
44
|
-
}
|
|
45
|
-
// Check font consistency
|
|
46
|
-
if (item1.fontName !== item2.fontName) {
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
// Check font size consistency (within 1pt tolerance)
|
|
50
|
-
if (Math.abs((item1.fontSize || 0) - (item2.fontSize || 0)) > 1) {
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
53
|
-
return true;
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Cluster text items into text blocks
|
|
57
|
-
*/
|
|
58
|
-
export function clusterTextItems(items, pageHeight, pageWidth, options) {
|
|
59
|
-
if (items.length === 0) {
|
|
60
|
-
return [];
|
|
61
|
-
}
|
|
62
|
-
// Sort items first
|
|
63
|
-
const sortedItems = sortTextItems(items, pageHeight);
|
|
64
|
-
// Filter out items that are too small
|
|
65
|
-
const minSize = options.minTextBlockSize ?? 8;
|
|
66
|
-
const filteredItems = sortedItems.filter(item => (item.fontSize || 0) >= minSize);
|
|
67
|
-
if (filteredItems.length === 0) {
|
|
68
|
-
return [];
|
|
69
|
-
}
|
|
70
|
-
const blocks = [];
|
|
71
|
-
let currentBlock = [filteredItems[0]];
|
|
72
|
-
for (let i = 1; i < filteredItems.length; i++) {
|
|
73
|
-
const prevItem = filteredItems[i - 1];
|
|
74
|
-
const currentItem = filteredItems[i];
|
|
75
|
-
if (shouldMerge(prevItem, currentItem, pageHeight, options)) {
|
|
76
|
-
// Add to current block
|
|
77
|
-
currentBlock.push(currentItem);
|
|
78
|
-
}
|
|
79
|
-
else {
|
|
80
|
-
// Finalize current block and start new one
|
|
81
|
-
if (currentBlock.length > 0) {
|
|
82
|
-
blocks.push(createTextBlock(currentBlock, pageHeight, options));
|
|
83
|
-
}
|
|
84
|
-
currentBlock = [currentItem];
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
// Don't forget the last block
|
|
88
|
-
if (currentBlock.length > 0) {
|
|
89
|
-
blocks.push(createTextBlock(currentBlock, pageHeight, options));
|
|
90
|
-
}
|
|
91
|
-
return blocks;
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Create a text block from a cluster of text items
|
|
95
|
-
*/
|
|
96
|
-
function createTextBlock(items, pageHeight, options) {
|
|
97
|
-
// Combine text with proper spacing
|
|
98
|
-
let text = '';
|
|
99
|
-
for (let i = 0; i < items.length; i++) {
|
|
100
|
-
const item = items[i];
|
|
101
|
-
text += item.str;
|
|
102
|
-
// Add space if needed (not at end, and next item doesn't start with space)
|
|
103
|
-
if (i < items.length - 1) {
|
|
104
|
-
const nextItem = items[i + 1];
|
|
105
|
-
const pos = extractPosition(item.transform);
|
|
106
|
-
const nextPos = extractPosition(nextItem.transform);
|
|
107
|
-
// Check if items are on same line
|
|
108
|
-
const y = pageHeight - pos.y;
|
|
109
|
-
const nextY = pageHeight - nextPos.y;
|
|
110
|
-
if (Math.abs(y - nextY) < 5) {
|
|
111
|
-
// Same line - add space if there's a gap
|
|
112
|
-
const gap = nextPos.x - (pos.x + item.width);
|
|
113
|
-
if (gap > 2 && !nextItem.str.startsWith(' ') && !item.str.endsWith(' ')) {
|
|
114
|
-
text += ' ';
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
// Different line - add newline
|
|
119
|
-
if (!text.endsWith('\n')) {
|
|
120
|
-
text += '\n';
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
// Calculate bounding box
|
|
126
|
-
const positions = items.map(item => extractPosition(item.transform));
|
|
127
|
-
const minX = Math.min(...positions.map(p => p.x));
|
|
128
|
-
const maxX = Math.max(...items.map((item, i) => positions[i].x + item.width));
|
|
129
|
-
const minY = Math.min(...positions.map(p => p.y));
|
|
130
|
-
const maxY = Math.max(...items.map((item, i) => positions[i].y + item.height));
|
|
131
|
-
const width = maxX - minX;
|
|
132
|
-
const height = maxY - minY;
|
|
133
|
-
// Use first item for font properties
|
|
134
|
-
const firstItem = items[0];
|
|
135
|
-
const fontSize = firstItem.fontSize || 12;
|
|
136
|
-
const rotation = extractRotation(firstItem.transform);
|
|
137
|
-
// Map font
|
|
138
|
-
const mappedFont = mapFont(firstItem.fontName, options.fontMapping);
|
|
139
|
-
// Extract color
|
|
140
|
-
const color = pdfColorToHex(firstItem.color || [0, 0, 0]);
|
|
141
|
-
// Transform coordinates
|
|
142
|
-
const x = pdfToPolotnoX(minX);
|
|
143
|
-
const y = pdfToPolotnoY(minY, height, pageHeight);
|
|
144
|
-
return {
|
|
145
|
-
text,
|
|
146
|
-
x,
|
|
147
|
-
y,
|
|
148
|
-
width,
|
|
149
|
-
height,
|
|
150
|
-
fontName: mappedFont.family,
|
|
151
|
-
fontSize,
|
|
152
|
-
fontWeight: mappedFont.weight,
|
|
153
|
-
fontStyle: mappedFont.style,
|
|
154
|
-
color,
|
|
155
|
-
rotation,
|
|
156
|
-
align: 'left', // Default to left alignment
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* Detect text alignment based on position within page
|
|
161
|
-
*/
|
|
162
|
-
export function detectAlignment(block, pageWidth) {
|
|
163
|
-
const blockCenter = block.x + block.width / 2;
|
|
164
|
-
const pageCenter = pageWidth / 2;
|
|
165
|
-
// Check if block is centered (within 10% of page width)
|
|
166
|
-
const centerThreshold = pageWidth * 0.1;
|
|
167
|
-
if (Math.abs(blockCenter - pageCenter) < centerThreshold) {
|
|
168
|
-
return 'center';
|
|
169
|
-
}
|
|
170
|
-
// Check if block is right-aligned (within 10% of right edge)
|
|
171
|
-
const rightEdge = pageWidth;
|
|
172
|
-
if (Math.abs(block.x + block.width - rightEdge) < centerThreshold) {
|
|
173
|
-
return 'right';
|
|
174
|
-
}
|
|
175
|
-
// Default to left
|
|
176
|
-
return 'left';
|
|
177
|
-
}
|
|
178
|
-
/**
|
|
179
|
-
* Apply alignment detection to all blocks
|
|
180
|
-
*/
|
|
181
|
-
export function applyAlignmentDetection(blocks, pageWidth) {
|
|
182
|
-
return blocks.map(block => ({
|
|
183
|
-
...block,
|
|
184
|
-
align: detectAlignment(block, pageWidth),
|
|
185
|
-
}));
|
|
186
|
-
}
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Options for PDF to JSON conversion
|
|
3
|
-
*/
|
|
4
|
-
export interface PDFImportOptions {
|
|
5
|
-
pageNumbers?: number[];
|
|
6
|
-
imageMode?: 'dataURL' | 'upload';
|
|
7
|
-
imageUploadFn?: (buffer: Buffer, mimeType: string) => Promise<string>;
|
|
8
|
-
fontMapping?: Record<string, string>;
|
|
9
|
-
minTextBlockSize?: number;
|
|
10
|
-
textClusterThreshold?: {
|
|
11
|
-
vertical?: number;
|
|
12
|
-
horizontal?: number;
|
|
13
|
-
};
|
|
14
|
-
outputUnit?: 'px' | 'cm' | 'in';
|
|
15
|
-
dpi?: number;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Raw text item extracted from PDF with position and font metadata
|
|
19
|
-
*/
|
|
20
|
-
export interface PDFTextItem {
|
|
21
|
-
str: string;
|
|
22
|
-
transform: number[];
|
|
23
|
-
width: number;
|
|
24
|
-
height: number;
|
|
25
|
-
fontName: string;
|
|
26
|
-
fontSize?: number;
|
|
27
|
-
hasEOL?: boolean;
|
|
28
|
-
dir?: string;
|
|
29
|
-
color?: number[];
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Clustered text block representing a semantic unit
|
|
33
|
-
*/
|
|
34
|
-
export interface TextBlock {
|
|
35
|
-
text: string;
|
|
36
|
-
x: number;
|
|
37
|
-
y: number;
|
|
38
|
-
width: number;
|
|
39
|
-
height: number;
|
|
40
|
-
fontName: string;
|
|
41
|
-
fontSize: number;
|
|
42
|
-
fontWeight: string;
|
|
43
|
-
fontStyle: string;
|
|
44
|
-
color: string;
|
|
45
|
-
rotation: number;
|
|
46
|
-
align?: string;
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Raw image object extracted from PDF
|
|
50
|
-
*/
|
|
51
|
-
export interface PDFImageObject {
|
|
52
|
-
buffer: Buffer;
|
|
53
|
-
mimeType: string;
|
|
54
|
-
width: number;
|
|
55
|
-
height: number;
|
|
56
|
-
transform: number[];
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Polotno image element ready for JSON
|
|
60
|
-
*/
|
|
61
|
-
export interface ImageBlock {
|
|
62
|
-
src: string;
|
|
63
|
-
x: number;
|
|
64
|
-
y: number;
|
|
65
|
-
width: number;
|
|
66
|
-
height: number;
|
|
67
|
-
rotation: number;
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Parsed font information from PDF font name
|
|
71
|
-
*/
|
|
72
|
-
export interface ParsedFont {
|
|
73
|
-
family: string;
|
|
74
|
-
weight: string;
|
|
75
|
-
style: string;
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Coordinate transformation context
|
|
79
|
-
*/
|
|
80
|
-
export interface CoordinateContext {
|
|
81
|
-
pageHeight: number;
|
|
82
|
-
pageWidth: number;
|
|
83
|
-
rotation?: number;
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* PDF page metadata
|
|
87
|
-
*/
|
|
88
|
-
export interface PDFPageMetadata {
|
|
89
|
-
pageNumber: number;
|
|
90
|
-
width: number;
|
|
91
|
-
height: number;
|
|
92
|
-
rotate?: number;
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* Result of PDF parsing for one page
|
|
96
|
-
*/
|
|
97
|
-
export interface ParsedPage {
|
|
98
|
-
metadata: PDFPageMetadata;
|
|
99
|
-
textItems: PDFTextItem[];
|
|
100
|
-
images: PDFImageObject[];
|
|
101
|
-
}
|
package/lib/pdf-import/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
function compareImages(original, converted, index) {
|
|
3
|
-
const differences = [];
|
|
4
|
-
// Compare position
|
|
5
|
-
const xDiff = Math.abs(original.x - converted.x);
|
|
6
|
-
const yDiff = Math.abs(original.y - converted.y);
|
|
7
|
-
if (xDiff > 1) {
|
|
8
|
-
differences.push(` X: ${original.x.toFixed(2)} → ${converted.x.toFixed(2)} (Δ ${(converted.x - original.x).toFixed(2)})`);
|
|
9
|
-
}
|
|
10
|
-
if (yDiff > 1) {
|
|
11
|
-
differences.push(` Y: ${original.y.toFixed(2)} → ${converted.y.toFixed(2)} (Δ ${(converted.y - original.y).toFixed(2)})`);
|
|
12
|
-
}
|
|
13
|
-
// Compare dimensions
|
|
14
|
-
const widthDiff = Math.abs(original.width - converted.width);
|
|
15
|
-
const heightDiff = Math.abs(original.height - converted.height);
|
|
16
|
-
if (widthDiff > 1) {
|
|
17
|
-
differences.push(` Width: ${original.width.toFixed(2)} → ${converted.width.toFixed(2)} (Δ ${(converted.width - original.width).toFixed(2)})`);
|
|
18
|
-
}
|
|
19
|
-
if (heightDiff > 1) {
|
|
20
|
-
differences.push(` Height: ${original.height.toFixed(2)} → ${converted.height.toFixed(2)} (Δ ${(converted.height - original.height).toFixed(2)})`);
|
|
21
|
-
}
|
|
22
|
-
// Compare rotation
|
|
23
|
-
const origRotation = original.rotation || 0;
|
|
24
|
-
const convRotation = converted.rotation || 0;
|
|
25
|
-
if (Math.abs(origRotation - convRotation) > 0.1) {
|
|
26
|
-
differences.push(` Rotation: ${origRotation} → ${convRotation}`);
|
|
27
|
-
}
|
|
28
|
-
return differences;
|
|
29
|
-
}
|
|
30
|
-
function compareJSON(originalPath, convertedPath) {
|
|
31
|
-
console.log('='.repeat(80));
|
|
32
|
-
console.log('PDF Import Comparison Tool');
|
|
33
|
-
console.log('='.repeat(80));
|
|
34
|
-
console.log();
|
|
35
|
-
// Read JSON files
|
|
36
|
-
const original = JSON.parse(fs.readFileSync(originalPath, 'utf-8'));
|
|
37
|
-
const converted = JSON.parse(fs.readFileSync(convertedPath, 'utf-8'));
|
|
38
|
-
// Compare document dimensions
|
|
39
|
-
console.log('📄 Document Dimensions:');
|
|
40
|
-
console.log(` Original: ${original.width.toFixed(2)} x ${original.height.toFixed(2)}`);
|
|
41
|
-
console.log(` Converted: ${converted.width.toFixed(2)} x ${converted.height.toFixed(2)}`);
|
|
42
|
-
const dimMatch = Math.abs(original.width - converted.width) < 1 &&
|
|
43
|
-
Math.abs(original.height - converted.height) < 1;
|
|
44
|
-
console.log(` Status: ${dimMatch ? '✅ Match' : '❌ Mismatch'}`);
|
|
45
|
-
console.log();
|
|
46
|
-
// Compare page count
|
|
47
|
-
console.log('📑 Page Count:');
|
|
48
|
-
console.log(` Original: ${original.pages.length}`);
|
|
49
|
-
console.log(` Converted: ${converted.pages.length}`);
|
|
50
|
-
console.log(` Status: ${original.pages.length === converted.pages.length ? '✅ Match' : '❌ Mismatch'}`);
|
|
51
|
-
console.log();
|
|
52
|
-
// Compare each page
|
|
53
|
-
const maxPages = Math.max(original.pages.length, converted.pages.length);
|
|
54
|
-
for (let pageIdx = 0; pageIdx < maxPages; pageIdx++) {
|
|
55
|
-
console.log(`${'─'.repeat(80)}`);
|
|
56
|
-
console.log(`Page ${pageIdx + 1}:`);
|
|
57
|
-
console.log(`${'─'.repeat(80)}`);
|
|
58
|
-
const origPage = original.pages[pageIdx];
|
|
59
|
-
const convPage = converted.pages[pageIdx];
|
|
60
|
-
if (!origPage || !convPage) {
|
|
61
|
-
console.log('❌ Page missing in one of the documents');
|
|
62
|
-
console.log();
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
// Filter to only image elements for comparison
|
|
66
|
-
const origImages = origPage.children.filter(el => el.type === 'image');
|
|
67
|
-
const convImages = convPage.children.filter(el => el.type === 'image');
|
|
68
|
-
console.log();
|
|
69
|
-
console.log(`🖼️ Image Elements:`);
|
|
70
|
-
console.log(` Original: ${origImages.length} images`);
|
|
71
|
-
console.log(` Converted: ${convImages.length} images`);
|
|
72
|
-
console.log();
|
|
73
|
-
// Compare each image
|
|
74
|
-
const maxImages = Math.max(origImages.length, convImages.length);
|
|
75
|
-
for (let imgIdx = 0; imgIdx < maxImages; imgIdx++) {
|
|
76
|
-
const origImg = origImages[imgIdx];
|
|
77
|
-
const convImg = convImages[imgIdx];
|
|
78
|
-
console.log(` Image ${imgIdx + 1}:`);
|
|
79
|
-
if (!origImg) {
|
|
80
|
-
console.log(' ❌ Missing in original');
|
|
81
|
-
console.log();
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
if (!convImg) {
|
|
85
|
-
console.log(' ❌ Missing in converted');
|
|
86
|
-
console.log();
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
// Show original values
|
|
90
|
-
console.log(` Original: x=${origImg.x.toFixed(2)}, y=${origImg.y.toFixed(2)}, w=${origImg.width.toFixed(2)}, h=${origImg.height.toFixed(2)}`);
|
|
91
|
-
console.log(` Converted: x=${convImg.x.toFixed(2)}, y=${convImg.y.toFixed(2)}, w=${convImg.width.toFixed(2)}, h=${convImg.height.toFixed(2)}`);
|
|
92
|
-
// Compare and show differences
|
|
93
|
-
const differences = compareImages(origImg, convImg, imgIdx);
|
|
94
|
-
if (differences.length === 0) {
|
|
95
|
-
console.log(' ✅ Perfect match');
|
|
96
|
-
}
|
|
97
|
-
else {
|
|
98
|
-
console.log(' ❌ Differences:');
|
|
99
|
-
differences.forEach(diff => console.log(` ${diff}`));
|
|
100
|
-
}
|
|
101
|
-
console.log();
|
|
102
|
-
}
|
|
103
|
-
// Compare text elements
|
|
104
|
-
const origTexts = origPage.children.filter(el => el.type === 'text');
|
|
105
|
-
const convTexts = convPage.children.filter(el => el.type === 'text');
|
|
106
|
-
if (origTexts.length > 0 || convTexts.length > 0) {
|
|
107
|
-
console.log(`📝 Text Elements:`);
|
|
108
|
-
console.log(` Original: ${origTexts.length} text elements`);
|
|
109
|
-
console.log(` Converted: ${convTexts.length} text elements`);
|
|
110
|
-
console.log();
|
|
111
|
-
}
|
|
112
|
-
// Show other element types
|
|
113
|
-
const origOther = origPage.children.filter(el => el.type !== 'image' && el.type !== 'text');
|
|
114
|
-
const convOther = convPage.children.filter(el => el.type !== 'image' && el.type !== 'text');
|
|
115
|
-
if (origOther.length > 0 || convOther.length > 0) {
|
|
116
|
-
console.log(`🔧 Other Elements:`);
|
|
117
|
-
console.log(` Original: ${origOther.length} elements`);
|
|
118
|
-
console.log(` Converted: ${convOther.length} elements`);
|
|
119
|
-
console.log();
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
console.log('='.repeat(80));
|
|
123
|
-
}
|
|
124
|
-
// CLI usage
|
|
125
|
-
const args = process.argv.slice(2);
|
|
126
|
-
if (args.length < 2) {
|
|
127
|
-
console.log('Usage: npm run compare-json <original.json> <converted.json>');
|
|
128
|
-
console.log('Example: npm run compare-json tests/files/pdf-img-design.json tests/files/pdf-img-converted.json');
|
|
129
|
-
process.exit(1);
|
|
130
|
-
}
|
|
131
|
-
const [originalPath, convertedPath] = args;
|
|
132
|
-
// Check files exist
|
|
133
|
-
if (!fs.existsSync(originalPath)) {
|
|
134
|
-
console.error(`Error: Original file not found: ${originalPath}`);
|
|
135
|
-
process.exit(1);
|
|
136
|
-
}
|
|
137
|
-
if (!fs.existsSync(convertedPath)) {
|
|
138
|
-
console.error(`Error: Converted file not found: ${convertedPath}`);
|
|
139
|
-
process.exit(1);
|
|
140
|
-
}
|
|
141
|
-
compareJSON(originalPath, convertedPath);
|
package/lib/spot-colors.d.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
export interface SpotColorDefinition {
|
|
2
|
-
name?: string;
|
|
3
|
-
cmyk?: number[];
|
|
4
|
-
}
|
|
5
|
-
export interface SpotColorConfig {
|
|
6
|
-
[color: string]: SpotColorDefinition;
|
|
7
|
-
}
|
|
8
|
-
/**
|
|
9
|
-
* Normalize color to a consistent format for matching using Konva's color parser
|
|
10
|
-
* This ensures consistency with how Polotno/Konva handles colors
|
|
11
|
-
* @param color - Color in any format (string or [r, g, b, a] array)
|
|
12
|
-
* @returns Normalized color string in rgba format
|
|
13
|
-
*/
|
|
14
|
-
export declare function normalizeColor(color: string | number[]): string | null;
|
|
15
|
-
/**
|
|
16
|
-
* Get spot color definition for a given color
|
|
17
|
-
* @param color - Color to check
|
|
18
|
-
* @param spotColorConfig - Spot color configuration
|
|
19
|
-
* @returns Spot color definition or null
|
|
20
|
-
*/
|
|
21
|
-
export declare function getSpotColorForColor(color: string, spotColorConfig: SpotColorConfig): SpotColorDefinition | null;
|
|
22
|
-
/**
|
|
23
|
-
* Register a spot color using PDFKit's built-in addSpotColor method
|
|
24
|
-
* @param doc - PDFKit document
|
|
25
|
-
* @param spotName - Name of the spot color
|
|
26
|
-
* @param spotColorDef - Spot color definition with CMYK fallback
|
|
27
|
-
* @returns Reference to the registered spot color
|
|
28
|
-
*/
|
|
29
|
-
export declare function registerSpotColor(doc: any, spotName: string, spotColorDef: SpotColorDefinition): {
|
|
30
|
-
name: string;
|
|
31
|
-
cmyk: number[];
|
|
32
|
-
};
|
|
33
|
-
/**
|
|
34
|
-
* Enable spot color support on a PDFDocument by intercepting color methods
|
|
35
|
-
* @param doc - PDFKit document
|
|
36
|
-
* @param spotColorConfig - Spot color configuration mapping
|
|
37
|
-
*/
|
|
38
|
-
export declare function enableSpotColorSupport(doc: any, spotColorConfig: SpotColorConfig): void;
|
package/lib/spot-colors.js
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import { Util } from 'konva/lib/Util.js';
|
|
2
|
-
/**
|
|
3
|
-
* Normalize color to a consistent format for matching using Konva's color parser
|
|
4
|
-
* This ensures consistency with how Polotno/Konva handles colors
|
|
5
|
-
* @param color - Color in any format (string or [r, g, b, a] array)
|
|
6
|
-
* @returns Normalized color string in rgba format
|
|
7
|
-
*/
|
|
8
|
-
export function normalizeColor(color) {
|
|
9
|
-
if (!color) {
|
|
10
|
-
return null;
|
|
11
|
-
}
|
|
12
|
-
try {
|
|
13
|
-
// Handle array format [r, g, b, a]
|
|
14
|
-
if (Array.isArray(color)) {
|
|
15
|
-
const r = Math.round(color[0]);
|
|
16
|
-
const g = Math.round(color[1]);
|
|
17
|
-
const b = Math.round(color[2]);
|
|
18
|
-
const a = color[3] !== undefined ? color[3] : 1;
|
|
19
|
-
return `rgba(${r},${g},${b},${a})`;
|
|
20
|
-
}
|
|
21
|
-
// Handle string format
|
|
22
|
-
if (typeof color !== 'string') {
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
// Use Konva's colorToRGBA for consistent color parsing
|
|
26
|
-
const rgba = Util.colorToRGBA(color);
|
|
27
|
-
if (!rgba) {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
// Konva returns {r, g, b, a} object
|
|
31
|
-
// Normalize to rgba string format
|
|
32
|
-
return `rgba(${rgba.r},${rgba.g},${rgba.b},${rgba.a})`;
|
|
33
|
-
}
|
|
34
|
-
catch (error) {
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Get spot color definition for a given color
|
|
40
|
-
* @param color - Color to check
|
|
41
|
-
* @param spotColorConfig - Spot color configuration
|
|
42
|
-
* @returns Spot color definition or null
|
|
43
|
-
*/
|
|
44
|
-
export function getSpotColorForColor(color, spotColorConfig) {
|
|
45
|
-
if (!spotColorConfig || !color) {
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
const normalizedColor = normalizeColor(color);
|
|
49
|
-
if (!normalizedColor) {
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
// Check direct match first
|
|
53
|
-
if (spotColorConfig[color]) {
|
|
54
|
-
return spotColorConfig[color];
|
|
55
|
-
}
|
|
56
|
-
// Check normalized match
|
|
57
|
-
if (spotColorConfig[normalizedColor]) {
|
|
58
|
-
return spotColorConfig[normalizedColor];
|
|
59
|
-
}
|
|
60
|
-
// Check all configured colors with normalization
|
|
61
|
-
for (const [configColor, definition] of Object.entries(spotColorConfig)) {
|
|
62
|
-
const normalizedConfigColor = normalizeColor(configColor);
|
|
63
|
-
if (normalizedConfigColor === normalizedColor) {
|
|
64
|
-
return definition;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Register a spot color using PDFKit's built-in addSpotColor method
|
|
71
|
-
* @param doc - PDFKit document
|
|
72
|
-
* @param spotName - Name of the spot color
|
|
73
|
-
* @param spotColorDef - Spot color definition with CMYK fallback
|
|
74
|
-
* @returns Reference to the registered spot color
|
|
75
|
-
*/
|
|
76
|
-
export function registerSpotColor(doc, spotName, spotColorDef) {
|
|
77
|
-
const { cmyk = [0, 0, 0, 1] } = spotColorDef;
|
|
78
|
-
// Convert CMYK from 0-1 range to 0-100 percentage range for PDFKit
|
|
79
|
-
const c = Math.max(0, Math.min(100, cmyk[0] * 100));
|
|
80
|
-
const m = Math.max(0, Math.min(100, cmyk[1] * 100));
|
|
81
|
-
const y = Math.max(0, Math.min(100, cmyk[2] * 100));
|
|
82
|
-
const k = Math.max(0, Math.min(100, cmyk[3] * 100));
|
|
83
|
-
// Sanitize spot color name for PDF (no spaces or special characters)
|
|
84
|
-
const sanitizedName = spotName.replace(/[^a-zA-Z0-9-_]/g, '_');
|
|
85
|
-
// Use PDFKit's built-in spot color support
|
|
86
|
-
doc.addSpotColor(sanitizedName, c, m, y, k);
|
|
87
|
-
return {
|
|
88
|
-
name: sanitizedName,
|
|
89
|
-
cmyk: [c / 100, m / 100, y / 100, k / 100], // Store in 0-1 range
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Enable spot color support on a PDFDocument by intercepting color methods
|
|
94
|
-
* @param doc - PDFKit document
|
|
95
|
-
* @param spotColorConfig - Spot color configuration mapping
|
|
96
|
-
*/
|
|
97
|
-
export function enableSpotColorSupport(doc, spotColorConfig) {
|
|
98
|
-
if (!spotColorConfig || Object.keys(spotColorConfig).length === 0) {
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
// Store configuration and registered spot colors on the document
|
|
102
|
-
doc._spotColors = new Map();
|
|
103
|
-
doc._spotColorConfig = spotColorConfig;
|
|
104
|
-
// Register all spot colors using PDFKit's built-in addSpotColor method
|
|
105
|
-
for (const [color, definition] of Object.entries(spotColorConfig)) {
|
|
106
|
-
const spotName = definition.name || `Spot_${color}`;
|
|
107
|
-
const registered = registerSpotColor(doc, spotName, definition);
|
|
108
|
-
// Cache the registered color definition
|
|
109
|
-
const normalizedColor = normalizeColor(color);
|
|
110
|
-
doc._spotColors.set(normalizedColor, registered);
|
|
111
|
-
doc._spotColors.set(color, registered); // Also store original format
|
|
112
|
-
}
|
|
113
|
-
// Intercept fillColor method to automatically use spot colors
|
|
114
|
-
const originalFillColor = doc.fillColor.bind(doc);
|
|
115
|
-
doc.fillColor = function (color, opacity) {
|
|
116
|
-
const spotColorDef = getSpotColorForColor(color, spotColorConfig);
|
|
117
|
-
if (spotColorDef) {
|
|
118
|
-
const normalizedColor = normalizeColor(color);
|
|
119
|
-
const registered = doc._spotColors.get(normalizedColor) || doc._spotColors.get(color);
|
|
120
|
-
if (registered) {
|
|
121
|
-
// Use PDFKit's fillColor with the spot color name directly
|
|
122
|
-
return originalFillColor(registered.name, opacity);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
return originalFillColor(color, opacity);
|
|
126
|
-
};
|
|
127
|
-
// Intercept strokeColor method to automatically use spot colors
|
|
128
|
-
const originalStrokeColor = doc.strokeColor.bind(doc);
|
|
129
|
-
doc.strokeColor = function (color, opacity) {
|
|
130
|
-
const spotColorDef = getSpotColorForColor(color, spotColorConfig);
|
|
131
|
-
if (spotColorDef) {
|
|
132
|
-
const normalizedColor = normalizeColor(color);
|
|
133
|
-
const registered = doc._spotColors.get(normalizedColor) || doc._spotColors.get(color);
|
|
134
|
-
if (registered) {
|
|
135
|
-
// Use PDFKit's strokeColor with the spot color name directly
|
|
136
|
-
return originalStrokeColor(registered.name, opacity);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
return originalStrokeColor(color, opacity);
|
|
140
|
-
};
|
|
141
|
-
}
|
package/lib/svg-render.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { ImageCache } from './utils.js';
|
|
2
|
-
export interface SVGElement {
|
|
3
|
-
src: string;
|
|
4
|
-
width: number;
|
|
5
|
-
height: number;
|
|
6
|
-
opacity: number;
|
|
7
|
-
colorsReplace: Record<string, string>;
|
|
8
|
-
}
|
|
9
|
-
export declare function renderSVG(doc: any, element: SVGElement, cache?: ImageCache | null): Promise<void>;
|
package/lib/svg-render.js
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import * as svg from './svg.js';
|
|
2
|
-
import { Util } from 'konva/lib/Util.js';
|
|
3
|
-
export async function renderSVG(doc, element, cache = null) {
|
|
4
|
-
const str = await svg.urlToString(element.src, cache);
|
|
5
|
-
const replaceEntries = Object.entries(element.colorsReplace || {});
|
|
6
|
-
doc.addSVG(str, 0, 0, {
|
|
7
|
-
// Use 'none' to allow stretching to exact dimensions, 'xMinYMin meet' to preserve aspect ratio
|
|
8
|
-
preserveAspectRatio: 'none',
|
|
9
|
-
width: element.width,
|
|
10
|
-
height: element.height,
|
|
11
|
-
opacity: element.opacity,
|
|
12
|
-
colorCallback: (colors) => {
|
|
13
|
-
if (!colors) {
|
|
14
|
-
return colors;
|
|
15
|
-
}
|
|
16
|
-
const [rgb, opacity] = colors;
|
|
17
|
-
let colorString = null;
|
|
18
|
-
if (Array.isArray(rgb) && rgb.length === 3) {
|
|
19
|
-
colorString = `rgb(${rgb[0]},${rgb[1]},${rgb[2]})`;
|
|
20
|
-
}
|
|
21
|
-
else if (typeof rgb === 'string') {
|
|
22
|
-
colorString = rgb;
|
|
23
|
-
}
|
|
24
|
-
let nextColorString = colorString;
|
|
25
|
-
if (replaceEntries.length && colorString) {
|
|
26
|
-
for (const [from, to] of replaceEntries) {
|
|
27
|
-
if (svg.sameColors(from, colorString)) {
|
|
28
|
-
nextColorString = to;
|
|
29
|
-
break;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
let nextColorArray = rgb;
|
|
34
|
-
let finalOpacity = opacity * element.opacity;
|
|
35
|
-
if (nextColorString != null) {
|
|
36
|
-
const rgbaObject = Util.colorToRGBA(nextColorString);
|
|
37
|
-
if (rgbaObject) {
|
|
38
|
-
nextColorArray = [
|
|
39
|
-
Math.round(rgbaObject.r),
|
|
40
|
-
Math.round(rgbaObject.g),
|
|
41
|
-
Math.round(rgbaObject.b),
|
|
42
|
-
];
|
|
43
|
-
// Handle alpha channel from the color string
|
|
44
|
-
if (rgbaObject.a !== undefined) {
|
|
45
|
-
finalOpacity = rgbaObject.a * opacity * element.opacity;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
if (!Array.isArray(nextColorArray) &&
|
|
50
|
-
typeof nextColorArray === 'string') {
|
|
51
|
-
const rgbObject = Util.getRGB(nextColorArray);
|
|
52
|
-
if (rgbObject) {
|
|
53
|
-
nextColorArray = [
|
|
54
|
-
Math.round(rgbObject.r),
|
|
55
|
-
Math.round(rgbObject.g),
|
|
56
|
-
Math.round(rgbObject.b),
|
|
57
|
-
];
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
return [nextColorArray, finalOpacity];
|
|
61
|
-
},
|
|
62
|
-
});
|
|
63
|
-
}
|