frontbacked-svg 5.0.0 → 5.0.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/dist/main.cjs +211 -86
- package/dist/main.d.cts +7 -4
- package/dist/main.d.ts +7 -4
- package/dist/main.mjs +211 -86
- package/package.json +2 -1
- package/src/base64Image.ts +100 -100
- package/src/cssParser.ts +58 -58
- package/src/filters.ts +225 -225
- package/src/font-utils.ts +192 -204
- package/src/get-default-fields-value.ts +63 -63
- package/src/getSvg.ts +476 -284
- package/src/imageHelper.ts +295 -293
- package/src/imagePassportUtils.ts +699 -717
- package/src/images-processor.ts +179 -186
- package/src/polyfills/DOMParser.ts +27 -27
- package/src/polyfills/File.ts +19 -19
- package/src/polyfills/Image.ts +89 -89
- package/src/svgScaler.ts +178 -178
- package/src/textGenCodeParser.ts +318 -318
- package/src/time.ts +146 -146
- package/src/toolsFunc.ts +78 -78
- package/src/types.ts +176 -176
- package/src/utils.ts +757 -757
- package/src/watermaker.ts +189 -189
package/src/getSvg.ts
CHANGED
|
@@ -1,284 +1,476 @@
|
|
|
1
|
-
import jsPDF from "jspdf"
|
|
2
|
-
import { getCanvas, getImageDimension, loadImageWithTimeout } from "./imageHelper.ts"
|
|
3
|
-
import { ImageWrapper } from "./polyfills/Image.ts";
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}
|
|
1
|
+
import jsPDF from "jspdf"
|
|
2
|
+
import { getCanvas, getImageDimension, loadImageWithTimeout } from "./imageHelper.ts"
|
|
3
|
+
import { ImageWrapper } from "./polyfills/Image.ts";
|
|
4
|
+
|
|
5
|
+
const getPdfImageFormat = (image: string): "PNG" | "JPEG" => {
|
|
6
|
+
return image.startsWith("data:image/jpeg") || image.startsWith("data:image/jpg") ? "JPEG" : "PNG";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const dataUrlToBlob = (dataUrl: string): Blob => {
|
|
10
|
+
const [header, data] = dataUrl.split(",");
|
|
11
|
+
const mimeType = header.match(/:(.*?);/)?.[1] || "application/octet-stream";
|
|
12
|
+
const binary = atob(data);
|
|
13
|
+
const bytes = new Uint8Array(binary.length);
|
|
14
|
+
|
|
15
|
+
for (let index = 0; index < binary.length; index += 1) {
|
|
16
|
+
bytes[index] = binary.charCodeAt(index);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return new Blob([bytes], { type: mimeType });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const isAppleMobileDevice = (): boolean => {
|
|
23
|
+
if (isNode || typeof navigator === "undefined") return false;
|
|
24
|
+
|
|
25
|
+
const userAgent = navigator.userAgent || "";
|
|
26
|
+
const platform = navigator.platform || "";
|
|
27
|
+
const maxTouchPoints = navigator.maxTouchPoints || 0;
|
|
28
|
+
|
|
29
|
+
return /iPad|iPhone|iPod/i.test(userAgent) || (platform === "MacIntel" && maxTouchPoints > 1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const openBlobInBrowser = (blob: Blob, fileName: string, openInNewTab = false): void => {
|
|
33
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
34
|
+
const downloadLink = document.createElement("a");
|
|
35
|
+
downloadLink.href = objectUrl;
|
|
36
|
+
downloadLink.rel = "noopener";
|
|
37
|
+
downloadLink.style.display = "none";
|
|
38
|
+
|
|
39
|
+
if (openInNewTab) {
|
|
40
|
+
downloadLink.target = "_blank";
|
|
41
|
+
} else {
|
|
42
|
+
downloadLink.download = fileName;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
document.body.appendChild(downloadLink);
|
|
46
|
+
downloadLink.click();
|
|
47
|
+
document.body.removeChild(downloadLink);
|
|
48
|
+
|
|
49
|
+
setTimeout(() => URL.revokeObjectURL(objectUrl), 30000);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const shareBlob = async (blob: Blob, fileName: string): Promise<boolean> => {
|
|
53
|
+
const shareApi = typeof navigator !== "undefined" ? (navigator as any).share : null;
|
|
54
|
+
const canShare = typeof navigator !== "undefined" ? (navigator as any).canShare : null;
|
|
55
|
+
|
|
56
|
+
if (!shareApi || typeof File === "undefined") return false;
|
|
57
|
+
|
|
58
|
+
const file = new File([blob], fileName, { type: blob.type || "application/octet-stream" });
|
|
59
|
+
const shareData = { files: [file], title: fileName };
|
|
60
|
+
|
|
61
|
+
if (canShare && !canShare.call(navigator, shareData)) return false;
|
|
62
|
+
|
|
63
|
+
await shareApi.call(navigator, shareData);
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const promptAppleMobileSave = (blob: Blob, fileName: string): Promise<void> => {
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
const overlay = document.createElement("div");
|
|
70
|
+
overlay.setAttribute("role", "dialog");
|
|
71
|
+
overlay.setAttribute("aria-modal", "true");
|
|
72
|
+
overlay.style.cssText = [
|
|
73
|
+
"position:fixed",
|
|
74
|
+
"inset:0",
|
|
75
|
+
"z-index:2147483647",
|
|
76
|
+
"display:flex",
|
|
77
|
+
"align-items:center",
|
|
78
|
+
"justify-content:center",
|
|
79
|
+
"padding:24px",
|
|
80
|
+
"background:rgba(15,23,42,0.58)",
|
|
81
|
+
"font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif",
|
|
82
|
+
].join(";");
|
|
83
|
+
|
|
84
|
+
const dialog = document.createElement("div");
|
|
85
|
+
dialog.style.cssText = [
|
|
86
|
+
"width:min(360px,100%)",
|
|
87
|
+
"border-radius:12px",
|
|
88
|
+
"background:#fff",
|
|
89
|
+
"box-shadow:0 20px 60px rgba(15,23,42,0.3)",
|
|
90
|
+
"padding:18px",
|
|
91
|
+
"color:#111827",
|
|
92
|
+
"text-align:left",
|
|
93
|
+
].join(";");
|
|
94
|
+
|
|
95
|
+
const title = document.createElement("div");
|
|
96
|
+
title.textContent = "File ready";
|
|
97
|
+
title.style.cssText = "font-size:17px;font-weight:700;margin-bottom:8px;";
|
|
98
|
+
|
|
99
|
+
const body = document.createElement("div");
|
|
100
|
+
body.textContent = "Choose where to save this file.";
|
|
101
|
+
body.style.cssText = "font-size:14px;line-height:1.45;color:#4b5563;margin-bottom:16px;";
|
|
102
|
+
|
|
103
|
+
const actions = document.createElement("div");
|
|
104
|
+
actions.style.cssText = "display:flex;gap:10px;justify-content:flex-end;";
|
|
105
|
+
|
|
106
|
+
const cancelButton = document.createElement("button");
|
|
107
|
+
cancelButton.type = "button";
|
|
108
|
+
cancelButton.textContent = "Cancel";
|
|
109
|
+
cancelButton.style.cssText = [
|
|
110
|
+
"border:0",
|
|
111
|
+
"border-radius:8px",
|
|
112
|
+
"padding:10px 14px",
|
|
113
|
+
"background:#f3f4f6",
|
|
114
|
+
"color:#111827",
|
|
115
|
+
"font-size:14px",
|
|
116
|
+
"font-weight:600",
|
|
117
|
+
].join(";");
|
|
118
|
+
|
|
119
|
+
const saveButton = document.createElement("button");
|
|
120
|
+
saveButton.type = "button";
|
|
121
|
+
saveButton.textContent = "Save File";
|
|
122
|
+
saveButton.style.cssText = [
|
|
123
|
+
"border:0",
|
|
124
|
+
"border-radius:8px",
|
|
125
|
+
"padding:10px 14px",
|
|
126
|
+
"background:#2563eb",
|
|
127
|
+
"color:#fff",
|
|
128
|
+
"font-size:14px",
|
|
129
|
+
"font-weight:700",
|
|
130
|
+
].join(";");
|
|
131
|
+
|
|
132
|
+
const cleanup = () => {
|
|
133
|
+
document.body.removeChild(overlay);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
cancelButton.onclick = () => {
|
|
137
|
+
cleanup();
|
|
138
|
+
reject(new Error("File save was cancelled."));
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
saveButton.onclick = async () => {
|
|
142
|
+
saveButton.setAttribute("disabled", "true");
|
|
143
|
+
try {
|
|
144
|
+
const shared = await shareBlob(blob, fileName);
|
|
145
|
+
if (!shared) openBlobInBrowser(blob, fileName, true);
|
|
146
|
+
cleanup();
|
|
147
|
+
resolve();
|
|
148
|
+
} catch (error: any) {
|
|
149
|
+
cleanup();
|
|
150
|
+
if (error?.name === "AbortError") {
|
|
151
|
+
reject(new Error("File save was cancelled."));
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
openBlobInBrowser(blob, fileName, true);
|
|
155
|
+
resolve();
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
actions.appendChild(cancelButton);
|
|
160
|
+
actions.appendChild(saveButton);
|
|
161
|
+
dialog.appendChild(title);
|
|
162
|
+
dialog.appendChild(body);
|
|
163
|
+
dialog.appendChild(actions);
|
|
164
|
+
overlay.appendChild(dialog);
|
|
165
|
+
document.body.appendChild(overlay);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const downloadBlobInBrowser = async (blob: Blob, fileName: string): Promise<void> => {
|
|
170
|
+
if (isNode) return;
|
|
171
|
+
|
|
172
|
+
if (isAppleMobileDevice()) {
|
|
173
|
+
await promptAppleMobileSave(blob, fileName);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
openBlobInBrowser(blob, fileName);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const downloadDataUrlInBrowser = async (dataUrl: string, fileName: string): Promise<void> => {
|
|
181
|
+
await downloadBlobInBrowser(dataUrlToBlob(dataUrl), fileName);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const pngToPdf = async (
|
|
185
|
+
image: string,
|
|
186
|
+
filename: string,
|
|
187
|
+
resolve: (result: string) => void,
|
|
188
|
+
reject: (result: Error) => void,
|
|
189
|
+
skipBrowserDownload?: boolean | null
|
|
190
|
+
) => {
|
|
191
|
+
try {
|
|
192
|
+
const size = await getImageDimension(image)
|
|
193
|
+
const pdfDoc = new jsPDF({
|
|
194
|
+
orientation: size.width >= size.height ? "landscape" : "portrait",
|
|
195
|
+
unit: 'pt',
|
|
196
|
+
format: [size.width, size.height],
|
|
197
|
+
compress: true,
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
pdfDoc.addImage(image, getPdfImageFormat(image), 0, 0, size.width, size.height, undefined, "FAST");
|
|
201
|
+
|
|
202
|
+
const pdfDataUrl = pdfDoc.output("datauristring");
|
|
203
|
+
if (!isNode && !skipBrowserDownload) {
|
|
204
|
+
const pdfBlob = new Blob([pdfDoc.output("arraybuffer")], { type: "application/pdf" });
|
|
205
|
+
await downloadBlobInBrowser(pdfBlob, filename);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
resolve(pdfDataUrl)
|
|
209
|
+
} catch (error: any) {
|
|
210
|
+
reject(error)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function entitiesToCharacters(text: string) {
|
|
215
|
+
// Define a mapping of HTML entities to their corresponding characters
|
|
216
|
+
const entityToCharMap: { [x: string]: string } = {
|
|
217
|
+
"'": "'",
|
|
218
|
+
"&": "&",
|
|
219
|
+
"<": "<",
|
|
220
|
+
">": ">",
|
|
221
|
+
""": '"',
|
|
222
|
+
"'": "'",
|
|
223
|
+
"'": "'",
|
|
224
|
+
""": '"',
|
|
225
|
+
"'": "'",
|
|
226
|
+
"<": "<",
|
|
227
|
+
">": ">",
|
|
228
|
+
"<": "<",
|
|
229
|
+
">": ">",
|
|
230
|
+
"/": "/",
|
|
231
|
+
"\": "\\",
|
|
232
|
+
"`": "`",
|
|
233
|
+
"%": "%",
|
|
234
|
+
":": ":",
|
|
235
|
+
";": ";",
|
|
236
|
+
"_": "_",
|
|
237
|
+
"@": "@"
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
// Use a regular expression to match and replace all entities
|
|
241
|
+
return text.replace(/&[a-zA-Z0-9#]+;/g, (match) => {
|
|
242
|
+
return entityToCharMap[match] || match; // Replace with corresponding character or keep unchanged
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function charactersToEntities(text: string) {
|
|
247
|
+
// Define a mapping of characters to their corresponding HTML entities
|
|
248
|
+
const charToEntityMap: { [x: string]: string } = {
|
|
249
|
+
"&": "&",
|
|
250
|
+
"<": "<",
|
|
251
|
+
">": ">",
|
|
252
|
+
'"': """,
|
|
253
|
+
"'": "'",
|
|
254
|
+
"`": "`",
|
|
255
|
+
"/": "/",
|
|
256
|
+
"\\": "\",
|
|
257
|
+
"%": "%",
|
|
258
|
+
":": ":",
|
|
259
|
+
";": ";",
|
|
260
|
+
"_": "_",
|
|
261
|
+
"@": "@"
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
// Use a regular expression to match and replace all characters
|
|
265
|
+
return text.replace(/[&<>"'`\/%:;_@]/g, (match) => {
|
|
266
|
+
return charToEntityMap[match] || match; // Replace with corresponding entity or keep unchanged
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export const escapeHtmlEntities = (input?: string | null) => {
|
|
271
|
+
if(!input) return ""
|
|
272
|
+
return charactersToEntities(input)/*
|
|
273
|
+
const entities: {[x: string]: string} = {
|
|
274
|
+
'&': '&',
|
|
275
|
+
'<': '<',
|
|
276
|
+
'>': '>',
|
|
277
|
+
'"': '"',
|
|
278
|
+
"'": ''',
|
|
279
|
+
'/': '/', // Some scenarios might require this, but usually, it’s not necessary for XML contexts
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
return `${input}`.replace(/[&<>"'\/]/g, function(match) {
|
|
283
|
+
return entities[match];
|
|
284
|
+
});*/
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const isNode = typeof window === 'undefined';
|
|
288
|
+
|
|
289
|
+
const getNodeImageDimensionAttr = (img: any, attr: string) => {
|
|
290
|
+
try {
|
|
291
|
+
return parseFloat(img.getAttribute(attr) || "0")
|
|
292
|
+
|
|
293
|
+
} catch(e) {
|
|
294
|
+
return 0
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async function processBase64Image(base64Image: string, side?: "front" | "back" | "front_hr" | "back_hr", format: 'png' | 'jpeg' | 'pdf' | string = 'png'): Promise<string> {
|
|
299
|
+
return new Promise((resolve, reject) => {
|
|
300
|
+
const img = new Image();
|
|
301
|
+
if (!isNode) img.crossOrigin = "anonymous";
|
|
302
|
+
img.onload = () => {
|
|
303
|
+
const canvas = getCanvas(img.width, img.height);
|
|
304
|
+
const ctx = canvas.getContext("2d") as any;
|
|
305
|
+
if (!ctx) return reject("Canvas context not supported");
|
|
306
|
+
|
|
307
|
+
let sx = 0, sy = 0, sw = img.width, sh = img.height;
|
|
308
|
+
|
|
309
|
+
switch (side) {
|
|
310
|
+
case "front":
|
|
311
|
+
sw = img.width / 2; // Left half
|
|
312
|
+
break;
|
|
313
|
+
case "back":
|
|
314
|
+
sx = img.width / 2; // Right half
|
|
315
|
+
sw = img.width / 2;
|
|
316
|
+
break;
|
|
317
|
+
case "front_hr":
|
|
318
|
+
sh = img.height / 2; // Top half
|
|
319
|
+
break;
|
|
320
|
+
case "back_hr":
|
|
321
|
+
sy = img.height / 2; // Bottom half
|
|
322
|
+
sh = img.height / 2;
|
|
323
|
+
break;
|
|
324
|
+
default:
|
|
325
|
+
return resolve(base64Image); // Return original if side is undefined
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
canvas.width = sw;
|
|
329
|
+
canvas.height = sh;
|
|
330
|
+
const realImage = ((img as any) as ImageWrapper).realImage? ((img as any) as ImageWrapper).realImage : img
|
|
331
|
+
ctx.drawImage(realImage, sx, sy, sw, sh, 0, 0, sw, sh);
|
|
332
|
+
resolve(canvas.toDataURL(`image/${format === 'pdf' ? 'png' : format}`));
|
|
333
|
+
};
|
|
334
|
+
img.onerror = () => reject("Image loading error");
|
|
335
|
+
img.src = base64Image;
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export const downloadSvgAsImage = (
|
|
340
|
+
svgString: string,
|
|
341
|
+
format: 'png' | 'jpeg' | 'pdf' | string = 'png',
|
|
342
|
+
fileName: string = 'downloadedImage',
|
|
343
|
+
downloadSide?: 'front' | 'back' | 'front_hr' | 'back_hr',
|
|
344
|
+
skipBrowserDownload?: boolean | null
|
|
345
|
+
): Promise<string> => {
|
|
346
|
+
return new Promise((resolve, reject) => {
|
|
347
|
+
try {
|
|
348
|
+
let svgUrl: string;
|
|
349
|
+
|
|
350
|
+
if (isNode) {
|
|
351
|
+
// Node.js: Convert SVG to base64 using Buffer
|
|
352
|
+
const buffer = Buffer.from(svgString, 'utf-8');
|
|
353
|
+
svgUrl = `data:image/svg+xml;base64,${buffer.toString('base64')}`;
|
|
354
|
+
|
|
355
|
+
} else {
|
|
356
|
+
const svgBlob = new Blob([svgString], { type: 'image/svg+xml' });
|
|
357
|
+
svgUrl = URL.createObjectURL(svgBlob);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
//console.error("downloadSvgAsImage.call", svgUrl? svgUrl.substring(0, 30) : "no svg url")
|
|
361
|
+
|
|
362
|
+
const img = new Image();
|
|
363
|
+
|
|
364
|
+
img.onload = async function () {
|
|
365
|
+
//console.error("downloadSvgAsImage.onload", img.width, img.height)
|
|
366
|
+
// Limit canvas size to avoid crashes
|
|
367
|
+
const MAX_DIMENSION = 4096;
|
|
368
|
+
const scaleFactor = Math.min(1, MAX_DIMENSION / Math.max(img.width, img.height));
|
|
369
|
+
|
|
370
|
+
// Create canvas
|
|
371
|
+
const canvas = getCanvas(img.width * scaleFactor, img.height * scaleFactor);
|
|
372
|
+
//const canvas = document.createElement('canvas');
|
|
373
|
+
const context = canvas.getContext('2d') as any;
|
|
374
|
+
|
|
375
|
+
let canvasWidth = img.width * scaleFactor;
|
|
376
|
+
let canvasHeight = img.height * scaleFactor;
|
|
377
|
+
|
|
378
|
+
canvas.width = canvasWidth;
|
|
379
|
+
canvas.height = canvasHeight;
|
|
380
|
+
|
|
381
|
+
const downloadImage = async (dataUrl: string, sideLabel?: string) => {
|
|
382
|
+
const fileFullname = sideLabel
|
|
383
|
+
? `${sideLabel.toUpperCase()}_${fileName}.${format}`
|
|
384
|
+
: `${fileName}.${format}`;
|
|
385
|
+
|
|
386
|
+
if (format === 'pdf') {
|
|
387
|
+
await pngToPdf(dataUrl, fileFullname, resolve, reject, skipBrowserDownload);
|
|
388
|
+
} else {
|
|
389
|
+
if(!isNode && !skipBrowserDownload) {
|
|
390
|
+
await downloadDataUrlInBrowser(dataUrl, fileFullname);
|
|
391
|
+
}
|
|
392
|
+
resolve(dataUrl);
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
const parser = new DOMParser();//isNode? new DOMParserWrapper() : new DOMParser();
|
|
397
|
+
const svgDoc = parser.parseFromString(svgString, 'image/svg+xml');
|
|
398
|
+
const images = svgDoc.getElementsByTagName('image');
|
|
399
|
+
//console.log("svgDoc.images:", images, svgDoc)
|
|
400
|
+
//console.error("downloadSvgAsImage.parser", images, images?.length)
|
|
401
|
+
|
|
402
|
+
try {
|
|
403
|
+
|
|
404
|
+
if (context) {
|
|
405
|
+
//context.clearRect(0, 0, canvas.width, canvas.height);
|
|
406
|
+
|
|
407
|
+
if (images.length > 0) {
|
|
408
|
+
// Preload all images with timeout
|
|
409
|
+
await Promise.all(
|
|
410
|
+
Array.from(images).map((image: any) => {
|
|
411
|
+
return new Promise<void>(async (resolve) => {
|
|
412
|
+
const imageUrl = !isNode? image.getAttributeNS('http://www.w3.org/1999/xlink', 'href') : image.getAttribute("xlink:href") || image.getAttribute("href") || "";
|
|
413
|
+
//console.error("downloadSvgAsImage.images.imageUrl:", imageUrl)
|
|
414
|
+
if (!imageUrl) return resolve(); // Skip if no image URL
|
|
415
|
+
|
|
416
|
+
try {
|
|
417
|
+
const imageImg = await loadImageWithTimeout(imageUrl)
|
|
418
|
+
const x = image?.x?.baseVal?.value || getNodeImageDimensionAttr(image, "x") || 0;
|
|
419
|
+
const y = image?.y?.baseVal?.value || getNodeImageDimensionAttr(image, "y") || 0;
|
|
420
|
+
const width = Math.min(
|
|
421
|
+
image?.width?.baseVal?.value || getNodeImageDimensionAttr(image, "width") || canvas.width,
|
|
422
|
+
canvas.width
|
|
423
|
+
);
|
|
424
|
+
const height = Math.min(
|
|
425
|
+
image?.height?.baseVal?.value || getNodeImageDimensionAttr(image, "height") || canvas.height,
|
|
426
|
+
canvas.height
|
|
427
|
+
);
|
|
428
|
+
context?.drawImage(imageImg, x, y, width, height);
|
|
429
|
+
//console.error("downloadSvgAsImage.loadImageWithTimeout", isNode, imageImg, x, y, width, height)
|
|
430
|
+
try {
|
|
431
|
+
//console.error("downloadSvgAsImage.image.stringify", JSON.stringify(image))
|
|
432
|
+
|
|
433
|
+
} catch(e) {}
|
|
434
|
+
resolve();
|
|
435
|
+
|
|
436
|
+
} catch(err: any) {
|
|
437
|
+
//console.error("downloadSvgAsImage.loadImageWithTimeout.error", err.message); // Log the error but continue
|
|
438
|
+
resolve();
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
})
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const realImage = ((img as any) as ImageWrapper).realImage? ((img as any) as ImageWrapper).realImage : img
|
|
446
|
+
|
|
447
|
+
context.drawImage(realImage, 0, 0, canvas.width, canvas.height);
|
|
448
|
+
|
|
449
|
+
// Convert canvas to Data URL and download
|
|
450
|
+
if(!downloadSide) {
|
|
451
|
+
var dataUrl = canvas.toDataURL(`image/${format === 'pdf' ? 'png' : format}`);
|
|
452
|
+
|
|
453
|
+
} else {
|
|
454
|
+
var dataUrl = canvas.toDataURL(`image/png`);
|
|
455
|
+
dataUrl = await processBase64Image(dataUrl, downloadSide, format)
|
|
456
|
+
}
|
|
457
|
+
await downloadImage(dataUrl, downloadSide);
|
|
458
|
+
} else {
|
|
459
|
+
reject(new Error('Unsupported Browser. Please try to download from another browser.'));
|
|
460
|
+
}
|
|
461
|
+
} catch (err) {
|
|
462
|
+
reject(err);
|
|
463
|
+
}
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
img.onerror = function () {
|
|
467
|
+
reject(new Error('Error loading the SVG.'));
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
img.src = svgUrl;
|
|
471
|
+
} catch(error: any) {
|
|
472
|
+
//console.error("downloadSvgAsImage.call.catch", error)
|
|
473
|
+
reject(error)
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
};
|