@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.
Files changed (56) hide show
  1. package/README.md +61 -8
  2. package/lib/index.d.ts +66 -8
  3. package/lib/index.js +25 -145
  4. package/package.json +17 -18
  5. package/lib/compare-render.d.ts +0 -1
  6. package/lib/compare-render.js +0 -185
  7. package/lib/figure.d.ts +0 -10
  8. package/lib/figure.js +0 -54
  9. package/lib/filters.d.ts +0 -2
  10. package/lib/filters.js +0 -163
  11. package/lib/ghostscript.d.ts +0 -21
  12. package/lib/ghostscript.js +0 -132
  13. package/lib/group.d.ts +0 -5
  14. package/lib/group.js +0 -5
  15. package/lib/image.d.ts +0 -38
  16. package/lib/image.js +0 -279
  17. package/lib/line.d.ts +0 -10
  18. package/lib/line.js +0 -66
  19. package/lib/pdf-import/coordinate-transform.d.ts +0 -51
  20. package/lib/pdf-import/coordinate-transform.js +0 -99
  21. package/lib/pdf-import/element-builder.d.ts +0 -21
  22. package/lib/pdf-import/element-builder.js +0 -163
  23. package/lib/pdf-import/font-mapper.d.ts +0 -17
  24. package/lib/pdf-import/font-mapper.js +0 -142
  25. package/lib/pdf-import/index.d.ts +0 -35
  26. package/lib/pdf-import/index.js +0 -105
  27. package/lib/pdf-import/parser.d.ts +0 -29
  28. package/lib/pdf-import/parser.js +0 -285
  29. package/lib/pdf-import/text-analysis.d.ts +0 -17
  30. package/lib/pdf-import/text-analysis.js +0 -186
  31. package/lib/pdf-import/types.d.ts +0 -101
  32. package/lib/pdf-import/types.js +0 -1
  33. package/lib/scripts/compare-json.d.ts +0 -1
  34. package/lib/scripts/compare-json.js +0 -141
  35. package/lib/spot-colors.d.ts +0 -38
  36. package/lib/spot-colors.js +0 -141
  37. package/lib/svg-render.d.ts +0 -9
  38. package/lib/svg-render.js +0 -63
  39. package/lib/svg.d.ts +0 -12
  40. package/lib/svg.js +0 -224
  41. package/lib/text/fonts.d.ts +0 -16
  42. package/lib/text/fonts.js +0 -113
  43. package/lib/text/index.d.ts +0 -8
  44. package/lib/text/index.js +0 -42
  45. package/lib/text/layout.d.ts +0 -22
  46. package/lib/text/layout.js +0 -522
  47. package/lib/text/parser.d.ts +0 -46
  48. package/lib/text/parser.js +0 -415
  49. package/lib/text/render.d.ts +0 -8
  50. package/lib/text/render.js +0 -237
  51. package/lib/text/types.d.ts +0 -91
  52. package/lib/text/types.js +0 -1
  53. package/lib/text.d.ts +0 -39
  54. package/lib/text.js +0 -576
  55. package/lib/utils.d.ts +0 -16
  56. 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
- }
@@ -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);
@@ -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;
@@ -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
- }
@@ -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
- }