frontbacked-svg 5.0.0

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.
@@ -0,0 +1,64 @@
1
+ import { Fields, FieldsData } from "./types.ts"
2
+ import { textGenCodeParser } from "./textGenCodeParser.ts"
3
+ import { useDataForRandSeed } from "./utils.ts"
4
+ import { dateToTimestamp } from "./time.ts"
5
+
6
+
7
+ const getDefaultFieldsValue = (fieldsData?: FieldsData, fields?: Fields | null, templateId?: string | null, fieldString?: string | null, currentTemplateId?: string | null) => {
8
+ const newFieldString = fields? JSON.stringify(fields) : undefined
9
+ let defaultFieldsData
10
+ let newCurrentTemplateId
11
+ if(fieldsData && fields && fieldString != newFieldString) {
12
+ if(templateId) newCurrentTemplateId = templateId
13
+ const def: FieldsData = {} as FieldsData
14
+ for(const field of Object.values(fields)) {
15
+
16
+ if(field.type == "checkbox") {
17
+ def[field.id] = true
18
+
19
+ } else if(field.type == "date") {
20
+ def[field.id] = dateToTimestamp(new Date())
21
+
22
+ } else if(field.type == "defgen" && field.code) {
23
+ if(fieldsData) {
24
+ def[field.id] = textGenCodeParser(field.code, fieldsData, useDataForRandSeed)
25
+ }
26
+
27
+ } else if(field.type == "image_select") {
28
+ if(fieldsData && field?.options) {
29
+ def[field.id] = Object.values(field?.options)[0]?.id
30
+ }
31
+
32
+ } else if(field.type == "text_select") {
33
+ if(fieldsData && field?.selections) {
34
+ def[field.id] = Object.values(field?.selections)[0]?.value
35
+ }
36
+
37
+ } else if(!["image_upload", "faceshot", "sign"].includes(field.type) && field.placeholder && typeof field.placeholder === "string" && field.placeholder.length > 0) {
38
+ def[field.id] = field.placeholder
39
+ }
40
+ }
41
+
42
+ if(Object.keys(def).length > 0) {
43
+ defaultFieldsData = {
44
+ ...def,
45
+ is_freemium: true,
46
+ template_id: templateId || ""
47
+ }
48
+ }
49
+
50
+ } else if(currentTemplateId != templateId) {
51
+ if(templateId) newCurrentTemplateId = templateId
52
+ defaultFieldsData = {
53
+ ...(fieldsData || {} as FieldsData),
54
+ is_freemium: true,
55
+ template_id: templateId || ""
56
+ }
57
+ }
58
+
59
+ return {
60
+ newFieldString, newCurrentTemplateId, defaultFieldsData
61
+ }
62
+ }
63
+
64
+ export default getDefaultFieldsValue
package/src/getSvg.ts ADDED
@@ -0,0 +1,284 @@
1
+ import jsPDF from "jspdf"
2
+ import { getCanvas, getImageDimension, loadImageWithTimeout } from "./imageHelper.ts"
3
+ import { ImageWrapper } from "./polyfills/Image.ts";
4
+
5
+ const pngToPdf = async (image: string, filename: string, resolve: (result: string) => void, reject: (result: Error) => void) => {
6
+ // Create a new jsPDF instance
7
+ const size = await getImageDimension(image)
8
+ const pdfDoc = new jsPDF({
9
+ unit: 'px',
10
+ format: [size.width, size.height], // Set PDF dimensions to match the image
11
+ });
12
+
13
+ // Add the image to the PDF
14
+ pdfDoc.addImage(image, 'PNG', 0, 0, size.width, size.height);
15
+
16
+ pdfDoc.save(filename);
17
+ resolve(image)
18
+ }
19
+
20
+ export function entitiesToCharacters(text: string) {
21
+ // Define a mapping of HTML entities to their corresponding characters
22
+ const entityToCharMap: { [x: string]: string } = {
23
+ "'": "'",
24
+ "&": "&",
25
+ "&lt;": "<",
26
+ "&gt;": ">",
27
+ "&quot;": '"',
28
+ "&#39;": "'",
29
+ "&#039;": "'",
30
+ "&#x22;": '"',
31
+ "&#x27;": "'",
32
+ "&#60;": "<",
33
+ "&#62;": ">",
34
+ "&#x3C;": "<",
35
+ "&#x3E;": ">",
36
+ "&#x2F;": "/",
37
+ "&#x5C;": "\\",
38
+ "&#x60;": "`",
39
+ "&#x25;": "%",
40
+ "&#x3A;": ":",
41
+ "&#x3B;": ";",
42
+ "&#x5F;": "_",
43
+ "&#x40;": "@"
44
+ };
45
+
46
+ // Use a regular expression to match and replace all entities
47
+ return text.replace(/&[a-zA-Z0-9#]+;/g, (match) => {
48
+ return entityToCharMap[match] || match; // Replace with corresponding character or keep unchanged
49
+ });
50
+ }
51
+
52
+ function charactersToEntities(text: string) {
53
+ // Define a mapping of characters to their corresponding HTML entities
54
+ const charToEntityMap: { [x: string]: string } = {
55
+ "&": "&amp;",
56
+ "<": "&lt;",
57
+ ">": "&gt;",
58
+ '"': "&quot;",
59
+ "'": "&#39;",
60
+ "`": "&#x60;",
61
+ "/": "&#x2F;",
62
+ "\\": "&#x5C;",
63
+ "%": "&#x25;",
64
+ ":": "&#x3A;",
65
+ ";": "&#x3B;",
66
+ "_": "&#x5F;",
67
+ "@": "&#x40;"
68
+ };
69
+
70
+ // Use a regular expression to match and replace all characters
71
+ return text.replace(/[&<>"'`\/%:;_@]/g, (match) => {
72
+ return charToEntityMap[match] || match; // Replace with corresponding entity or keep unchanged
73
+ });
74
+ }
75
+
76
+ export const escapeHtmlEntities = (input?: string | null) => {
77
+ if(!input) return ""
78
+ return charactersToEntities(input)/*
79
+ const entities: {[x: string]: string} = {
80
+ '&': '&amp;',
81
+ '<': '&lt;',
82
+ '>': '&gt;',
83
+ '"': '&quot;',
84
+ "'": '&#39;',
85
+ '/': '&#47;', // Some scenarios might require this, but usually, it’s not necessary for XML contexts
86
+ };
87
+
88
+ return `${input}`.replace(/[&<>"'\/]/g, function(match) {
89
+ return entities[match];
90
+ });*/
91
+ }
92
+
93
+ const isNode = typeof window === 'undefined';
94
+
95
+ const getNodeImageDimensionAttr = (img: any, attr: string) => {
96
+ try {
97
+ return parseFloat(img.getAttribute(attr) || "0")
98
+
99
+ } catch(e) {
100
+ return 0
101
+ }
102
+ }
103
+
104
+ async function processBase64Image(base64Image: string, side?: "front" | "back" | "front_hr" | "back_hr", format: 'png' | 'jpeg' | 'pdf' | string = 'png'): Promise<string> {
105
+ return new Promise((resolve, reject) => {
106
+ const img = new Image();
107
+ img.onload = () => {
108
+ const canvas = getCanvas(img.width, img.height);
109
+ const ctx = canvas.getContext("2d") as any;
110
+ if (!ctx) return reject("Canvas context not supported");
111
+
112
+ let sx = 0, sy = 0, sw = img.width, sh = img.height;
113
+
114
+ switch (side) {
115
+ case "front":
116
+ sw = img.width / 2; // Left half
117
+ break;
118
+ case "back":
119
+ sx = img.width / 2; // Right half
120
+ sw = img.width / 2;
121
+ break;
122
+ case "front_hr":
123
+ sh = img.height / 2; // Top half
124
+ break;
125
+ case "back_hr":
126
+ sy = img.height / 2; // Bottom half
127
+ sh = img.height / 2;
128
+ break;
129
+ default:
130
+ return resolve(base64Image); // Return original if side is undefined
131
+ }
132
+
133
+ canvas.width = sw;
134
+ canvas.height = sh;
135
+ const realImage = ((img as any) as ImageWrapper).realImage? ((img as any) as ImageWrapper).realImage : img
136
+ ctx.drawImage(realImage, sx, sy, sw, sh, 0, 0, sw, sh);
137
+ resolve(canvas.toDataURL(`image/${format === 'pdf' ? 'png' : format}`));
138
+ };
139
+ img.onerror = () => reject("Image loading error");
140
+ img.src = base64Image;
141
+ });
142
+ }
143
+
144
+ export const downloadSvgAsImage = (
145
+ svgString: string,
146
+ format: 'png' | 'jpeg' | 'pdf' | string = 'png',
147
+ fileName: string = 'downloadedImage',
148
+ downloadSide?: 'front' | 'back' | 'front_hr' | 'back_hr',
149
+ skipBrowserDownload?: boolean | null
150
+ ): Promise<string> => {
151
+ return new Promise((resolve, reject) => {
152
+ try {
153
+ let svgUrl: string;
154
+
155
+ if (isNode) {
156
+ // Node.js: Convert SVG to base64 using Buffer
157
+ const buffer = Buffer.from(svgString, 'utf-8');
158
+ svgUrl = `data:image/svg+xml;base64,${buffer.toString('base64')}`;
159
+
160
+ } else {
161
+ const svgBlob = new Blob([svgString], { type: 'image/svg+xml' });
162
+ svgUrl = URL.createObjectURL(svgBlob);
163
+ }
164
+
165
+ //console.error("downloadSvgAsImage.call", svgUrl? svgUrl.substring(0, 30) : "no svg url")
166
+
167
+ const img = new Image();
168
+
169
+ img.onload = async function () {
170
+ //console.error("downloadSvgAsImage.onload", img.width, img.height)
171
+ // Limit canvas size to avoid crashes
172
+ const MAX_DIMENSION = 4096;
173
+ const scaleFactor = Math.min(1, MAX_DIMENSION / Math.max(img.width, img.height));
174
+
175
+ // Create canvas
176
+ const canvas = getCanvas(img.width * scaleFactor, img.height * scaleFactor);
177
+ //const canvas = document.createElement('canvas');
178
+ const context = canvas.getContext('2d') as any;
179
+
180
+ let canvasWidth = img.width * scaleFactor;
181
+ let canvasHeight = img.height * scaleFactor;
182
+
183
+ canvas.width = canvasWidth;
184
+ canvas.height = canvasHeight;
185
+
186
+ const downloadImage = (dataUrl: string, sideLabel?: string) => {
187
+ const fileFullname = sideLabel
188
+ ? `${sideLabel.toUpperCase()}_${fileName}.${format}`
189
+ : `${fileName}.${format}`;
190
+
191
+ if (format === 'pdf') {
192
+ pngToPdf(dataUrl, fileFullname, resolve, reject);
193
+ } else {
194
+ if(!isNode && !skipBrowserDownload) {
195
+ const downloadLink = document.createElement('a');
196
+ downloadLink.href = dataUrl;
197
+ downloadLink.download = fileFullname;
198
+ downloadLink.click();
199
+ }
200
+ resolve(dataUrl);
201
+ }
202
+ };
203
+
204
+ const parser = new DOMParser();//isNode? new DOMParserWrapper() : new DOMParser();
205
+ const svgDoc = parser.parseFromString(svgString, 'image/svg+xml');
206
+ const images = svgDoc.getElementsByTagName('image');
207
+ //console.log("svgDoc.images:", images, svgDoc)
208
+ //console.error("downloadSvgAsImage.parser", images, images?.length)
209
+
210
+ try {
211
+
212
+ if (context) {
213
+ //context.clearRect(0, 0, canvas.width, canvas.height);
214
+
215
+ if (images.length > 0) {
216
+ // Preload all images with timeout
217
+ await Promise.all(
218
+ Array.from(images).map((image: any) => {
219
+ return new Promise<void>(async (resolve) => {
220
+ const imageUrl = !isNode? image.getAttributeNS('http://www.w3.org/1999/xlink', 'href') : image.getAttribute("xlink:href") || image.getAttribute("href") || "";
221
+ //console.error("downloadSvgAsImage.images.imageUrl:", imageUrl)
222
+ if (!imageUrl) return resolve(); // Skip if no image URL
223
+
224
+ try {
225
+ const imageImg = await loadImageWithTimeout(imageUrl)
226
+ const x = image?.x?.baseVal?.value || getNodeImageDimensionAttr(image, "x") || 0;
227
+ const y = image?.y?.baseVal?.value || getNodeImageDimensionAttr(image, "y") || 0;
228
+ const width = Math.min(
229
+ image?.width?.baseVal?.value || getNodeImageDimensionAttr(image, "width") || canvas.width,
230
+ canvas.width
231
+ );
232
+ const height = Math.min(
233
+ image?.height?.baseVal?.value || getNodeImageDimensionAttr(image, "height") || canvas.height,
234
+ canvas.height
235
+ );
236
+ context?.drawImage(imageImg, x, y, width, height);
237
+ //console.error("downloadSvgAsImage.loadImageWithTimeout", isNode, imageImg, x, y, width, height)
238
+ try {
239
+ //console.error("downloadSvgAsImage.image.stringify", JSON.stringify(image))
240
+
241
+ } catch(e) {}
242
+ resolve();
243
+
244
+ } catch(err: any) {
245
+ //console.error("downloadSvgAsImage.loadImageWithTimeout.error", err.message); // Log the error but continue
246
+ resolve();
247
+ }
248
+ });
249
+ })
250
+ );
251
+ }
252
+
253
+ const realImage = ((img as any) as ImageWrapper).realImage? ((img as any) as ImageWrapper).realImage : img
254
+
255
+ context.drawImage(realImage, 0, 0, canvas.width, canvas.height);
256
+
257
+ // Convert canvas to Data URL and download
258
+ if(!downloadSide) {
259
+ var dataUrl = canvas.toDataURL(`image/${format === 'pdf' ? 'png' : format}`);
260
+
261
+ } else {
262
+ var dataUrl = canvas.toDataURL(`image/png`);
263
+ dataUrl = await processBase64Image(dataUrl, downloadSide, format)
264
+ }
265
+ downloadImage(dataUrl, downloadSide);
266
+ } else {
267
+ reject(new Error('Unsupported Browser. Please try to download from another browser.'));
268
+ }
269
+ } catch (err) {
270
+ reject(err);
271
+ }
272
+ };
273
+
274
+ img.onerror = function () {
275
+ reject(new Error('Error loading the SVG.'));
276
+ };
277
+
278
+ img.src = svgUrl;
279
+ } catch(error: any) {
280
+ //console.error("downloadSvgAsImage.call.catch", error)
281
+ reject(error)
282
+ }
283
+ });
284
+ };
@@ -0,0 +1,293 @@
1
+ import { ImageWrapper } from './polyfills/Image.ts';
2
+
3
+ const isNode = typeof window === 'undefined';
4
+
5
+ export function cropImage(imageUrl: string): Promise<string> {
6
+ return new Promise((resolve, reject) => {
7
+ const img = new Image();
8
+
9
+ img.onload = function () {
10
+ const canvas = getCanvas(img.width, img.height)
11
+ const ctx = canvas.getContext("2d") as any;
12
+ if (!ctx) return reject(new Error("Failed to get canvas context"));
13
+
14
+ canvas.width = img.width;
15
+ canvas.height = img.height;
16
+ const realImage = ((img as any) as ImageWrapper).realImage? ((img as any) as ImageWrapper).realImage : img
17
+ ctx.drawImage(realImage, 0, 0);
18
+
19
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
20
+ const data = imageData.data;
21
+
22
+ let minX = canvas.width;
23
+ let minY = canvas.height;
24
+ let maxX = 0;
25
+ let maxY = 0;
26
+
27
+ for (let y = 0; y < canvas.height; y++) {
28
+ for (let x = 0; x < canvas.width; x++) {
29
+ const index = (y * canvas.width + x) * 4;
30
+ if (data[index + 3] > 0) {
31
+ minX = Math.min(minX, x);
32
+ minY = Math.min(minY, y);
33
+ maxX = Math.max(maxX, x);
34
+ maxY = Math.max(maxY, y);
35
+ }
36
+ }
37
+ }
38
+
39
+ const cropWidth = maxX - minX;
40
+ const cropHeight = maxY - minY;
41
+ const croppedCanvas = getCanvas(cropWidth, cropHeight);
42
+ const croppedCtx = croppedCanvas.getContext("2d") as any;
43
+ if (!croppedCtx) return reject(new Error("Failed to get canvas context"));
44
+
45
+ croppedCanvas.width = cropWidth;
46
+ croppedCanvas.height = cropHeight;
47
+ croppedCtx.drawImage(canvas, minX, minY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight);
48
+
49
+ resolve(croppedCanvas.toDataURL("image/png"));
50
+ };
51
+
52
+ img.onerror = () => reject(new Error('Error loading image'));
53
+ img.src = imageUrl;
54
+ });
55
+ }
56
+
57
+ export function isBlankImage(imageUrl: string): Promise<boolean> {
58
+ return new Promise((resolve, reject) => {
59
+ const img = new Image();
60
+ img.onload = function () {
61
+ const canvas = getCanvas(img.width, img.height);
62
+ canvas.width = img.width;
63
+ canvas.height = img.height;
64
+ const ctx = canvas.getContext("2d") as any;
65
+ if (!ctx) return reject(new Error("Failed to get canvas context"));
66
+ const realImage = ((img as any) as ImageWrapper).realImage? ((img as any) as ImageWrapper).realImage : img
67
+ ctx.drawImage(realImage, 0, 0);
68
+
69
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
70
+ resolve(imageData.every((value: any) => value === 0));
71
+ };
72
+
73
+ img.onerror = () => reject(new Error('Error loading image'));
74
+ img.src = imageUrl;
75
+ });
76
+ }
77
+
78
+ export function getImageDimension(imageUrl: string): Promise<{ width: number; height: number }> {
79
+ return new Promise((resolve, reject) => {
80
+ const img = new Image();
81
+ img.onload = () => resolve({ width: img.width, height: img.height });
82
+ img.onerror = () => reject(new Error('Error loading image'));
83
+ img.src = imageUrl;
84
+ });
85
+ }
86
+
87
+ export function imageToStamp(base64Image: string, color: [number, number, number], holePct: number): Promise<string | null> {
88
+ return new Promise((resolve, reject) => {
89
+ if (!base64Image) return resolve(null);
90
+ const img = new Image();
91
+ img.onload = function () {
92
+ const canvas = getCanvas(img.width, img.height);
93
+ const ctx = canvas.getContext("2d") as any;
94
+ if (!ctx) return reject(new Error("Failed to get canvas context"));
95
+
96
+ canvas.width = img.width;
97
+ canvas.height = img.height;
98
+ const realImage = ((img as any) as ImageWrapper).realImage? ((img as any) as ImageWrapper).realImage : img
99
+ ctx.drawImage(realImage, 0, 0);
100
+
101
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
102
+ const data = imageData.data;
103
+
104
+ const numTransparentPixels = Math.floor((holePct / 100) * (canvas.width * canvas.height));
105
+
106
+ for (let i = 0; i < numTransparentPixels; i++) {
107
+ const x = Math.floor(Math.random() * canvas.width);
108
+ const y = Math.floor(Math.random() * canvas.height);
109
+ const index = (y * canvas.width + x) * 4;
110
+ data[index + 3] = 0;
111
+ }
112
+
113
+ ctx.putImageData(imageData, 0, 0);
114
+ resolve(canvas.toDataURL("image/png"));
115
+ };
116
+
117
+ img.onerror = () => reject(new Error('Error loading image'));
118
+ img.src = base64Image;
119
+ });
120
+ }
121
+
122
+ export function getImageColor(base64ImageUrl: string, useDominantColor = true): Promise<[number, number, number] | null> {
123
+ return new Promise((resolve, reject) => {
124
+ if (!base64ImageUrl) return resolve(null);
125
+ const img = new Image();
126
+ img.onload = function () {
127
+ const canvas = getCanvas(img.width, img.height);
128
+ const ctx = canvas.getContext("2d") as any;
129
+ if (!ctx) return reject(new Error("Failed to get canvas context"));
130
+
131
+ canvas.width = img.width;
132
+ canvas.height = img.height;
133
+ const realImage = ((img as any) as ImageWrapper).realImage? ((img as any) as ImageWrapper).realImage : img
134
+ ctx.drawImage(realImage, 0, 0);
135
+
136
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
137
+ const data = imageData.data;
138
+
139
+ let totalRed = 0, totalGreen = 0, totalBlue = 0, totalPixels = 0;
140
+ const colorCount: Record<string, number> = {};
141
+
142
+ for (let i = 0; i < data.length; i += 4) {
143
+ const red = data[i], green = data[i + 1], blue = data[i + 2];
144
+ totalRed += red;
145
+ totalGreen += green;
146
+ totalBlue += blue;
147
+ totalPixels++;
148
+ const colorKey = `${red},${green},${blue}`;
149
+ colorCount[colorKey] = (colorCount[colorKey] || 0) + 1;
150
+ }
151
+
152
+ const averageColor: [number, number, number] = [
153
+ Math.round(totalRed / totalPixels),
154
+ Math.round(totalGreen / totalPixels),
155
+ Math.round(totalBlue / totalPixels)
156
+ ];
157
+
158
+ if (useDominantColor) {
159
+ let dominantColor: [number, number, number] = averageColor;
160
+ let maxCount = 0;
161
+ for (const colorKey in colorCount) {
162
+ if (colorCount[colorKey] > maxCount) {
163
+ dominantColor = colorKey.split(",").map(Number) as [number, number, number];
164
+ maxCount = colorCount[colorKey];
165
+ }
166
+ }
167
+ resolve(dominantColor);
168
+ } else {
169
+ resolve(averageColor);
170
+ }
171
+ };
172
+
173
+ img.onerror = () => reject(new Error('Error loading image'));
174
+ img.src = base64ImageUrl;
175
+ });
176
+ }
177
+
178
+ export function reduceBase64Image(base64: string, targetWidth: number, quality=1) {
179
+ return new Promise((resolve, reject) => {
180
+ const img = new Image();
181
+ img.onload = () => {
182
+ const { width: originalWidth, height: originalHeight } = img;
183
+
184
+ // If the target width is greater than original, return original
185
+ if (targetWidth > originalWidth) {
186
+ return resolve(base64);
187
+ }
188
+
189
+ const aspectRatio = originalHeight / originalWidth;
190
+ let targetHeight = targetWidth * aspectRatio;
191
+ if(targetWidth == 0 || targetWidth == originalWidth) {
192
+ targetWidth = originalWidth
193
+ targetHeight = originalHeight;
194
+ }
195
+
196
+ const canvas = getCanvas(targetWidth, targetHeight);
197
+ //const canvas = document.createElement('canvas');
198
+ canvas.width = targetWidth;
199
+ canvas.height = targetHeight;
200
+
201
+ const ctx = canvas.getContext('2d') as any;
202
+ const realImage = ((img as any) as ImageWrapper).realImage? ((img as any) as ImageWrapper).realImage : img
203
+
204
+ if(targetWidth == 0 || targetHeight == 0 || !realImage) {
205
+ console.log("invimg:2", targetWidth, targetHeight, realImage)
206
+ }
207
+
208
+ ctx.drawImage(realImage, 0, 0, targetWidth, targetHeight);
209
+
210
+ const matches = base64.match(/^data:(.+);base64,(.+)$/);
211
+ let resizedBase64
212
+ if (matches) {
213
+ const mimeType = matches[1]; // Extract MIME type
214
+ resizedBase64 = canvas.toDataURL(mimeType, quality); // default is image/png
215
+
216
+ } else {
217
+ resizedBase64 = canvas.toDataURL(); // default is image/png
218
+ }
219
+
220
+
221
+ resolve(resizedBase64);
222
+ };
223
+ img.onerror = (err) => reject(err);
224
+ img.src = base64;
225
+ });
226
+ }
227
+
228
+ // Helper to load images with a timeout
229
+ export const loadImageWithTimeoutFallback = (src: string, timeout = 5000): Promise<any> => {
230
+ return new Promise((resolve, reject) => {
231
+ if (isNode) {
232
+ const { loadImage } = require('canvas');
233
+ if (src.startsWith('data:image/') || src.startsWith('data:img/')) {
234
+ const buffer = Buffer.from(src.split(',')[1], 'base64');
235
+ loadImage(buffer).then(resolve).catch(reject);
236
+
237
+ } else {
238
+ loadImage(src).then(resolve).catch(reject);
239
+ }
240
+
241
+ } else {
242
+ const img = new Image();
243
+ const timer = setTimeout(() => {
244
+ reject(new Error(`Image load timeout: ${src}`));
245
+ }, timeout);
246
+
247
+ img.onload = () => {
248
+ clearTimeout(timer);
249
+ const realImage = ((img as any) as ImageWrapper).realImage? ((img as any) as ImageWrapper).realImage : img
250
+ resolve(realImage);
251
+ };
252
+
253
+ img.onerror = (error: any) => {
254
+ clearTimeout(timer);
255
+ reject(new Error(`Error loading image: ${src.substring(0, 30)}`, error));
256
+ };
257
+
258
+ img.src = src;
259
+ }
260
+ });
261
+ };
262
+
263
+ export const loadImageWithTimeout = (src: string, timeout = 5000): Promise<any> => {
264
+ return new Promise((resolve, reject) => {
265
+ const img = new Image();
266
+ const timer = setTimeout(() => {
267
+ reject(new Error(`Image load timeout: ${src}`));
268
+ }, timeout);
269
+
270
+ img.onload = () => {
271
+ clearTimeout(timer);
272
+ const realImage = ((img as any) as ImageWrapper).realImage? ((img as any) as ImageWrapper).realImage : img
273
+ resolve(realImage);
274
+ };
275
+
276
+ img.onerror = (error: any) => {
277
+ clearTimeout(timer);
278
+ reject(new Error(`Error loading image: ${src.substring(0, 30)}`, error));
279
+ };
280
+
281
+ img.src = src;
282
+ });
283
+ };
284
+
285
+ export const getCanvas = (width: number, height: number) => {
286
+ if(isNode) {
287
+ const { createCanvas } = require('canvas');
288
+ return createCanvas(width, height)
289
+
290
+ } else {
291
+ return document.createElement('canvas')
292
+ }
293
+ }