@polotno/pdf-export 0.1.18 → 0.1.20
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 +52 -0
- package/lib/compare-render.d.ts +1 -0
- package/lib/compare-render.js +185 -0
- package/lib/figure.d.ts +10 -0
- package/lib/figure.js +53 -48
- package/lib/ghostscript.d.ts +21 -0
- package/lib/ghostscript.js +98 -128
- package/lib/group.d.ts +5 -0
- package/lib/group.js +4 -8
- package/lib/image.d.ts +21 -0
- package/lib/image.js +147 -89
- package/lib/index.d.ts +26 -0
- package/lib/index.js +133 -0
- package/lib/line.d.ts +10 -0
- package/lib/line.js +41 -57
- package/lib/spot-colors.d.ts +38 -0
- package/lib/spot-colors.js +141 -0
- package/lib/svg-render.d.ts +9 -0
- package/lib/svg-render.js +19 -28
- package/lib/svg.d.ts +11 -0
- package/lib/svg.js +212 -233
- package/lib/text.d.ts +39 -0
- package/lib/text.js +558 -165
- package/lib/utils.d.ts +16 -0
- package/lib/utils.js +118 -72
- package/package.json +35 -15
- package/index.js +0 -130
|
@@ -0,0 +1,141 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
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
CHANGED
|
@@ -1,29 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
const [color, opacity] = colors;
|
|
22
|
-
return [color, opacity * element.opacity];
|
|
23
|
-
},
|
|
24
|
-
});
|
|
1
|
+
import * as svg from './svg.js';
|
|
2
|
+
export async function renderSVG(doc, element, cache = null) {
|
|
3
|
+
const svgStr = await svg.urlToString(element.src, cache);
|
|
4
|
+
const src = svg.replaceColors(svgStr, new Map(Object.entries(element.colorsReplace)));
|
|
5
|
+
const str = await svg.urlToString(src, cache);
|
|
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 [color, opacity] = colors;
|
|
17
|
+
return [color, opacity * element.opacity];
|
|
18
|
+
},
|
|
19
|
+
});
|
|
25
20
|
}
|
|
26
|
-
|
|
27
|
-
module.exports = {
|
|
28
|
-
renderSVG,
|
|
29
|
-
};
|
package/lib/svg.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ImageCache } from './utils.js';
|
|
2
|
+
export declare function urlToBase64(url: string, cache?: ImageCache | null): Promise<string>;
|
|
3
|
+
export declare function urlToString(url: string, cache?: ImageCache | null): Promise<string>;
|
|
4
|
+
export declare function getColors(svgString: string): string[];
|
|
5
|
+
export declare function svgToURL(s: string): string;
|
|
6
|
+
export declare function getSvgSize(url: string): Promise<{
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
}>;
|
|
10
|
+
export declare function fixSize(svgString: string): string;
|
|
11
|
+
export declare function replaceColors(svgString: string, replaceMap: Map<string, string>): string;
|
package/lib/svg.js
CHANGED
|
@@ -1,245 +1,224 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const DOMParser = xmldom.DOMParser;
|
|
6
|
-
const XMLSerializer = xmldom.XMLSerializer;
|
|
7
|
-
|
|
8
|
-
const originalFetch = nodeFetch;
|
|
9
|
-
|
|
10
|
-
const fetch = (url) => {
|
|
11
|
-
if (url.indexOf('base64') >= 0) {
|
|
12
|
-
return {
|
|
13
|
-
text: async () => {
|
|
14
|
-
let buff = Buffer.from(url.split('base64,')[1], 'base64');
|
|
15
|
-
return buff.toString('ascii');
|
|
16
|
-
},
|
|
17
|
-
buffer: async () => {
|
|
18
|
-
return Buffer.from(url.split('base64,')[1], 'base64');
|
|
19
|
-
},
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
return originalFetch(url);
|
|
23
|
-
};
|
|
24
|
-
|
|
1
|
+
import { Util } from 'konva/lib/Util.js';
|
|
2
|
+
import { DOMParser, XMLSerializer } from 'xmldom';
|
|
3
|
+
import { fetchWithTimeout } from './utils.js';
|
|
25
4
|
function isInsideDef(element) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
5
|
+
while (element.parentNode) {
|
|
6
|
+
if (element.nodeName === 'defs') {
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
element = element.parentNode;
|
|
29
10
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
function parseStyleAttribute(style) {
|
|
14
|
+
if (!style) {
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
17
|
+
const styles = style.split(';');
|
|
18
|
+
const result = {};
|
|
19
|
+
styles.forEach((style) => {
|
|
20
|
+
const [key, value] = style.split(':');
|
|
21
|
+
if (key && value) {
|
|
22
|
+
result[key] = value;
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
function buildStyleAttribute(styleObject) {
|
|
28
|
+
return Object.keys(styleObject)
|
|
29
|
+
.map((key) => `${key}:${styleObject[key]}`)
|
|
30
|
+
.join(';');
|
|
33
31
|
}
|
|
34
|
-
|
|
35
32
|
function getElementColors(e) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return colors;
|
|
33
|
+
const style = parseStyleAttribute(e.getAttribute('style'));
|
|
34
|
+
const colors = {
|
|
35
|
+
fill: '',
|
|
36
|
+
stroke: '',
|
|
37
|
+
};
|
|
38
|
+
if (e.getAttribute('fill') && e.getAttribute('fill') !== 'none') {
|
|
39
|
+
colors.fill = e.getAttribute('fill');
|
|
40
|
+
}
|
|
41
|
+
if (!colors.fill && style && style.fill && style.fill !== 'none') {
|
|
42
|
+
colors.fill = style.fill;
|
|
43
|
+
}
|
|
44
|
+
if (e.getAttribute('stroke')) {
|
|
45
|
+
colors.stroke = e.getAttribute('stroke');
|
|
46
|
+
}
|
|
47
|
+
if (!colors.stroke && style && style.stroke) {
|
|
48
|
+
colors.stroke = style.stroke;
|
|
49
|
+
}
|
|
50
|
+
if (!colors.stroke && !colors.fill) {
|
|
51
|
+
colors.fill = 'black';
|
|
52
|
+
}
|
|
53
|
+
return colors;
|
|
58
54
|
}
|
|
59
|
-
|
|
60
55
|
const SVG_SHAPES = ['path', 'rect', 'circle'];
|
|
61
|
-
|
|
62
56
|
function getAllElementsWithColor(doc) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return matchingElements;
|
|
57
|
+
var matchingElements = [];
|
|
58
|
+
var allElements = doc.getElementsByTagName('*');
|
|
59
|
+
for (var i = 0, n = allElements.length; i < n; i++) {
|
|
60
|
+
const element = allElements[i];
|
|
61
|
+
if (isInsideDef(element)) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (element.getAttribute('fill') !== null) {
|
|
65
|
+
matchingElements.push(element);
|
|
66
|
+
}
|
|
67
|
+
const style = element.getAttribute('style');
|
|
68
|
+
if (style != null && style.indexOf('fill') >= 0) {
|
|
69
|
+
matchingElements.push(element);
|
|
70
|
+
}
|
|
71
|
+
if (element.getAttribute('stroke') !== null) {
|
|
72
|
+
matchingElements.push(element);
|
|
73
|
+
}
|
|
74
|
+
else if (element.style && element.style['fill']) {
|
|
75
|
+
matchingElements.push(element);
|
|
76
|
+
}
|
|
77
|
+
else if (SVG_SHAPES.indexOf(element.nodeName) >= 0) {
|
|
78
|
+
matchingElements.push(element);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return matchingElements;
|
|
89
82
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const colors = [];
|
|
119
|
-
|
|
120
|
-
elements.forEach((e) => {
|
|
121
|
-
const { fill, stroke } = getElementColors(e);
|
|
122
|
-
const results = [fill, stroke];
|
|
123
|
-
results.forEach((color) => {
|
|
124
|
-
if (!color) {
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
const rgba = Konva.Util.colorToRGBA(color);
|
|
128
|
-
if (!rgba) {
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
if (colors.indexOf(color) === -1) {
|
|
132
|
-
colors.push(color);
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
return colors;
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
module.exports.svgToURL = function svgToURL(s) {
|
|
140
|
-
const uri = Buffer.from(unescape(encodeURIComponent(s))).toString('base64');
|
|
141
|
-
return 'data:image/svg+xml;base64,' + uri;
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
module.exports.getSvgSize = async function getSvgSize(url) {
|
|
145
|
-
const svgString = await urlToString(url);
|
|
146
|
-
var parser = new DOMParser();
|
|
147
|
-
var doc = parser.parseFromString(svgString, 'image/svg+xml');
|
|
148
|
-
const viewBox = doc.documentElement.getAttribute('viewBox');
|
|
149
|
-
const [x, y, width, height] = viewBox?.split(' ') || [];
|
|
150
|
-
return { width: parseFloat(width), height: parseFloat(height) };
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
module.exports.fixSize = function fixSize(svgString) {
|
|
154
|
-
var parser = new DOMParser();
|
|
155
|
-
var doc = parser.parseFromString(svgString, 'image/svg+xml');
|
|
156
|
-
const viewBox = doc.documentElement.getAttribute('viewBox');
|
|
157
|
-
const [x, y, width, height] = viewBox?.split(' ') || [];
|
|
158
|
-
if (!doc.documentElement.getAttribute('width')) {
|
|
159
|
-
doc.documentElement.setAttribute('width', width + 'px');
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (!doc.documentElement.getAttribute('height')) {
|
|
163
|
-
doc.documentElement.setAttribute('height', height + 'px');
|
|
164
|
-
}
|
|
165
|
-
var xmlSerializer = new XMLSerializer();
|
|
166
|
-
const str = xmlSerializer.serializeToString(doc);
|
|
167
|
-
return str;
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
const sameColors = (c1, c2) => {
|
|
171
|
-
if (!c2 || !c2) {
|
|
172
|
-
return false;
|
|
173
|
-
}
|
|
174
|
-
return c1.r === c2.r && c1.g === c2.g && c1.b === c2.b && c1.a === c2.a;
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
module.exports.replaceColors = function replaceColors(svgString, replaceMap) {
|
|
178
|
-
var parser = new DOMParser();
|
|
179
|
-
var doc = parser.parseFromString(svgString, 'text/xml');
|
|
180
|
-
|
|
181
|
-
const elements = getAllElementsWithColor(doc);
|
|
182
|
-
|
|
183
|
-
const oldColors = Array.from(replaceMap.keys());
|
|
184
|
-
|
|
185
|
-
elements.forEach((el) => {
|
|
186
|
-
const { fill, stroke } = getElementColors(el);
|
|
187
|
-
const colors = [
|
|
188
|
-
{ prop: 'fill', color: fill },
|
|
189
|
-
{ prop: 'stroke', color: stroke },
|
|
190
|
-
];
|
|
191
|
-
colors.forEach(({ prop, color }) => {
|
|
192
|
-
// find matched oldColor
|
|
193
|
-
const marchedOldValue = oldColors.find((oldColor) => {
|
|
194
|
-
return sameColors(
|
|
195
|
-
Konva.Util.colorToRGBA(oldColor),
|
|
196
|
-
Konva.Util.colorToRGBA(color)
|
|
197
|
-
);
|
|
198
|
-
});
|
|
199
|
-
if (!marchedOldValue) {
|
|
200
|
-
return;
|
|
201
|
-
} else {
|
|
202
|
-
el.setAttribute(prop, replaceMap.get(marchedOldValue));
|
|
203
|
-
|
|
204
|
-
const style = parseStyleAttribute(el.getAttribute('style'));
|
|
205
|
-
|
|
206
|
-
if (style && style[prop]) {
|
|
207
|
-
style[prop] = replaceMap.get(marchedOldValue);
|
|
208
|
-
el.setAttribute('style', buildStyleAttribute(style));
|
|
83
|
+
export async function urlToBase64(url, cache = null) {
|
|
84
|
+
// Check cache first
|
|
85
|
+
if (cache && cache.buffers.has(url)) {
|
|
86
|
+
return cache.buffers.get(url);
|
|
87
|
+
}
|
|
88
|
+
const req = await fetchWithTimeout(url);
|
|
89
|
+
let result;
|
|
90
|
+
if (req.buffer) {
|
|
91
|
+
const buffer = await req.buffer();
|
|
92
|
+
result = `data:image/svg+xml;base64,${buffer.toString('base64')}`;
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
const svgString = await req.text();
|
|
96
|
+
result = svgToURL(svgString);
|
|
97
|
+
}
|
|
98
|
+
// Store in cache
|
|
99
|
+
if (cache) {
|
|
100
|
+
cache.buffers.set(url, result);
|
|
101
|
+
}
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
export async function urlToString(url, cache = null) {
|
|
105
|
+
// Check cache first
|
|
106
|
+
if (cache && cache.buffers.has(url)) {
|
|
107
|
+
const cached = cache.buffers.get(url);
|
|
108
|
+
// If cached value is a base64 data URL, decode it
|
|
109
|
+
if (cached.startsWith('data:')) {
|
|
110
|
+
return Buffer.from(cached.split('base64,')[1], 'base64').toString();
|
|
209
111
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
112
|
+
return cached;
|
|
113
|
+
}
|
|
114
|
+
let svgString;
|
|
115
|
+
if (url.startsWith('data:')) {
|
|
116
|
+
svgString = Buffer.from(url.split('base64,')[1], 'base64').toString();
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
const req = await fetchWithTimeout(url);
|
|
120
|
+
svgString = await req.text();
|
|
121
|
+
}
|
|
122
|
+
// Store in cache
|
|
123
|
+
if (cache) {
|
|
124
|
+
cache.buffers.set(url, svgString);
|
|
125
|
+
}
|
|
126
|
+
return svgString;
|
|
127
|
+
}
|
|
128
|
+
export function getColors(svgString) {
|
|
129
|
+
var parser = new DOMParser();
|
|
130
|
+
var doc = parser.parseFromString(svgString, 'text/xml');
|
|
131
|
+
const elements = getAllElementsWithColor(doc);
|
|
132
|
+
const colors = [];
|
|
133
|
+
elements.forEach((e) => {
|
|
134
|
+
const { fill, stroke } = getElementColors(e);
|
|
135
|
+
const results = [fill, stroke];
|
|
136
|
+
results.forEach((color) => {
|
|
137
|
+
if (!color) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const rgba = Util.colorToRGBA(color);
|
|
141
|
+
if (!rgba) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (colors.indexOf(color) === -1) {
|
|
145
|
+
colors.push(color);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
213
148
|
});
|
|
214
|
-
|
|
215
|
-
var xmlSerializer = new XMLSerializer();
|
|
216
|
-
const str = xmlSerializer.serializeToString(doc);
|
|
217
|
-
|
|
218
|
-
// console.log(str);
|
|
219
|
-
// Array.from(replaceMap.keys()).forEach((oldColor) => {
|
|
220
|
-
// svgString = svgString.replace(
|
|
221
|
-
// new RegExp(oldColor, 'g'),
|
|
222
|
-
// replaceMap.get(oldColor) as string
|
|
223
|
-
// );
|
|
224
|
-
// });
|
|
225
|
-
return module.exports.svgToURL(str);
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
function parseStyleAttribute(style) {
|
|
229
|
-
if (!style) {
|
|
230
|
-
return {};
|
|
231
|
-
}
|
|
232
|
-
const styles = style.split(';');
|
|
233
|
-
const result = {};
|
|
234
|
-
styles.forEach((style) => {
|
|
235
|
-
const [key, value] = style.split(':');
|
|
236
|
-
result[key] = value;
|
|
237
|
-
});
|
|
238
|
-
return result;
|
|
149
|
+
return colors;
|
|
239
150
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
151
|
+
export function svgToURL(s) {
|
|
152
|
+
const uri = Buffer.from(unescape(encodeURIComponent(s))).toString('base64');
|
|
153
|
+
return 'data:image/svg+xml;base64,' + uri;
|
|
154
|
+
}
|
|
155
|
+
export async function getSvgSize(url) {
|
|
156
|
+
const svgString = await urlToString(url);
|
|
157
|
+
var parser = new DOMParser();
|
|
158
|
+
var doc = parser.parseFromString(svgString, 'image/svg+xml');
|
|
159
|
+
const viewBox = doc.documentElement.getAttribute('viewBox');
|
|
160
|
+
const [x, y, width, height] = viewBox?.split(' ') || [];
|
|
161
|
+
return { width: parseFloat(width), height: parseFloat(height) };
|
|
162
|
+
}
|
|
163
|
+
export function fixSize(svgString) {
|
|
164
|
+
var parser = new DOMParser();
|
|
165
|
+
var doc = parser.parseFromString(svgString, 'image/svg+xml');
|
|
166
|
+
const viewBox = doc.documentElement.getAttribute('viewBox');
|
|
167
|
+
const [x, y, width, height] = viewBox?.split(' ') || [];
|
|
168
|
+
if (!doc.documentElement.getAttribute('width')) {
|
|
169
|
+
doc.documentElement.setAttribute('width', width + 'px');
|
|
170
|
+
}
|
|
171
|
+
if (!doc.documentElement.getAttribute('height')) {
|
|
172
|
+
doc.documentElement.setAttribute('height', height + 'px');
|
|
173
|
+
}
|
|
174
|
+
var xmlSerializer = new XMLSerializer();
|
|
175
|
+
const str = xmlSerializer.serializeToString(doc);
|
|
176
|
+
return str;
|
|
177
|
+
}
|
|
178
|
+
const sameColors = (color1, color2) => {
|
|
179
|
+
if (!color1 || !color2) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
if (color2 === 'currentColor' && color1 === 'black') {
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
const c1 = Util.colorToRGBA(color1);
|
|
186
|
+
const c2 = Util.colorToRGBA(color2);
|
|
187
|
+
if (!c1 || !c2) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
return c1.r === c2.r && c1.g === c2.g && c1.b === c2.b && c1.a === c2.a;
|
|
191
|
+
};
|
|
192
|
+
export function replaceColors(svgString, replaceMap) {
|
|
193
|
+
var parser = new DOMParser();
|
|
194
|
+
var doc = parser.parseFromString(svgString, 'text/xml');
|
|
195
|
+
const elements = getAllElementsWithColor(doc);
|
|
196
|
+
const oldColors = Array.from(replaceMap.keys());
|
|
197
|
+
elements.forEach((el) => {
|
|
198
|
+
const { fill, stroke } = getElementColors(el);
|
|
199
|
+
const colors = [
|
|
200
|
+
{ prop: 'fill', color: fill },
|
|
201
|
+
{ prop: 'stroke', color: stroke },
|
|
202
|
+
];
|
|
203
|
+
colors.forEach(({ prop, color }) => {
|
|
204
|
+
// find matched oldColor
|
|
205
|
+
const marchedOldValue = oldColors.find((oldColor) => {
|
|
206
|
+
return sameColors(oldColor, color);
|
|
207
|
+
});
|
|
208
|
+
if (!marchedOldValue) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
el.setAttribute(prop, replaceMap.get(marchedOldValue));
|
|
213
|
+
const style = parseStyleAttribute(el.getAttribute('style'));
|
|
214
|
+
if (style && style[prop]) {
|
|
215
|
+
style[prop] = replaceMap.get(marchedOldValue);
|
|
216
|
+
el.setAttribute('style', buildStyleAttribute(style));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
var xmlSerializer = new XMLSerializer();
|
|
222
|
+
const str = xmlSerializer.serializeToString(doc);
|
|
223
|
+
return svgToURL(str);
|
|
245
224
|
}
|