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.
package/package.json ADDED
@@ -0,0 +1,103 @@
1
+ {
2
+ "name": "frontbacked-svg",
3
+ "version": "5.0.0",
4
+ "description": "frontbacked svg renderer",
5
+ "types": "dist/main.d.ts",
6
+ "type": "module",
7
+ "bin.disable": {
8
+ "frontbacked-svg": "dist/bin/cli.cjs"
9
+ },
10
+ "exports": {
11
+ ".": {
12
+ "import": {
13
+ "types": "./dist/main.d.ts",
14
+ "default": "./dist/main.mjs"
15
+ },
16
+ "require": {
17
+ "types": "./dist/main.d.cts",
18
+ "default": "./dist/main.cjs"
19
+ },
20
+ "default": "./dist/main.mjs"
21
+ },
22
+ "./dist/*": {
23
+ "types": "./dist/*.d.ts",
24
+ "import": "./dist/*.mjs",
25
+ "require": "./dist/*.cjs"
26
+ }
27
+ },
28
+ "engines": {
29
+ "node": ">=22.0.0"
30
+ },
31
+ "packageManager": "npm@8.4.0",
32
+ "files": [
33
+ "dist",
34
+ "src",
35
+ "bin"
36
+ ],
37
+ "scripts": {
38
+ "build": "tsc && tsup",
39
+ "s": "node dist/bin/cli.cjs",
40
+ "t": "npm run build && npm run s",
41
+ "lint": "eslint . && npm run lint:lockfile",
42
+ "lint:fix": "eslint . --fix",
43
+ "lint:lockfile": "lockfile-lint --path package-lock.json --validate-https --allowed-hosts npm yarn",
44
+ "test": "c8 node --loader ts-node/esm --test __tests__/**",
45
+ "test:watch": "c8 node --loader ts-node/esm --test --watch __tests__/**",
46
+ "coverage:view": "open coverage/lcov-report/index.html",
47
+ "version": "changeset version",
48
+ "release": "changeset publish"
49
+ },
50
+ "author": {
51
+ "name": "feyi-tech",
52
+ "email": "jinminetics@gmail.com",
53
+ "url": "https://github.com/feyi-tech"
54
+ },
55
+ "publishConfig": {
56
+ "provenance": false,
57
+ "access": "public"
58
+ },
59
+ "license": "Apache-2.0",
60
+ "keywords": [
61
+ ""
62
+ ],
63
+ "homepage": "https://github.com/feyi-tech/frontbacked-svg",
64
+ "bugs": {
65
+ "url": "https://github.com/feyi-tech/frontbacked-svg/issues"
66
+ },
67
+ "repository": {
68
+ "type": "git",
69
+ "url": "git+https://github.com/feyi-tech/frontbacked-svg.git"
70
+ },
71
+ "devDependencies": {
72
+ "@changesets/changelog-github": "^0.5.0",
73
+ "@changesets/cli": "^2.27.7",
74
+ "@types/jsdom": "^21.1.7",
75
+ "@types/node": "^20.14.10",
76
+ "c8": "^10.1.2",
77
+ "eslint": "^9.6.0",
78
+ "eslint-plugin-security": "^3.0.1",
79
+ "husky": "^9.0.11",
80
+ "lint-staged": "^15.2.7",
81
+ "lockfile-lint": "^4.14.0",
82
+ "neostandard": "^0.11.0",
83
+ "ts-node": "^10.9.2",
84
+ "tsup": "^8.1.0",
85
+ "typescript": "^5.5.3",
86
+ "validate-conventional-commit": "^1.0.4"
87
+ },
88
+ "peerDependencies": {
89
+ "firebase": ">=8.0.0"
90
+ },
91
+ "lint-staged": {
92
+ "**/*.{js,json}": [
93
+ "npm run lint:fix"
94
+ ]
95
+ },
96
+ "dependencies": {
97
+ "@types/firebase": "^3.2.1",
98
+ "canvas": "^3.1.0",
99
+ "jsdom": "^26.0.0",
100
+ "jspdf": "^2.5.1",
101
+ "svgson": "^5.3.1"
102
+ }
103
+ }
@@ -0,0 +1,101 @@
1
+ import { ImageWrapper } from "./polyfills/Image.ts";
2
+
3
+ const isNode = typeof window === 'undefined';
4
+
5
+ /**
6
+ * Gets the dimensions (width and height) of an image from a base64 string.
7
+ *
8
+ * @param base64Image - The base64 string of the image.
9
+ * @returns A Promise that resolves to an object containing the base64 string, width, and height of the image.
10
+ */
11
+ export function getImageDimensions(base64Image: string): Promise<{ width: number, height: number }> {
12
+ return new Promise<{ width: number, height: number }>((resolve, reject) => {
13
+ const img = new Image();
14
+
15
+ img.onload = () => {
16
+ resolve({
17
+ width: img.width,
18
+ height: img.height,
19
+ });
20
+ };
21
+
22
+ img.onerror = (error) => {
23
+ reject(new Error(`Failed to load image: ${error}`));
24
+ };
25
+
26
+ img.src = base64Image;
27
+ });
28
+ }
29
+
30
+ /**
31
+ * Resizes a base64 image string to the given width while maintaining the aspect ratio.
32
+ *
33
+ * @param base64Image - The base64 string of the image.
34
+ * @param targetWidth - The desired width for the resized image.
35
+ * @returns A Promise that resolves to the resized image as a base64 string.
36
+ */
37
+ export async function resizeBase64Image(base64Image: string, targetWidth: number): Promise<string> {
38
+ return new Promise<string>((resolve, reject) => {
39
+ const img = new Image();
40
+
41
+ // Set up the onload handler to resize the image once it's loaded
42
+ img.onload = () => {
43
+ const aspectRatio = img.width / img.height;
44
+ const targetHeight = targetWidth / aspectRatio;
45
+
46
+ // Create a canvas and draw the resized image on it
47
+ const canvas = document.createElement('canvas');
48
+ canvas.width = targetWidth;
49
+ canvas.height = targetHeight;
50
+ const ctx = canvas.getContext('2d') as any;
51
+
52
+ if (ctx) {
53
+ const realImage = ((img as any) as ImageWrapper).realImage? ((img as any) as ImageWrapper).realImage : img
54
+ ctx.drawImage(realImage, 0, 0, targetWidth, targetHeight);
55
+
56
+ // Convert the canvas to a base64 string and resolve the promise
57
+ const resizedBase64Image = canvas.toDataURL('image/png');
58
+ resolve(resizedBase64Image);
59
+ } else {
60
+ reject(new Error('Failed to get canvas context'));
61
+ }
62
+ };
63
+
64
+ // Set up the onerror handler to reject the promise if the image fails to load
65
+ img.onerror = (error) => {
66
+ reject(new Error(`Failed to load image: ${error}`));
67
+ };
68
+
69
+ // Set the src attribute to the base64 image string to start loading the image
70
+ img.src = base64Image;
71
+ });
72
+ }
73
+
74
+ export function base64UrlToFile(base64Url: string, fileName?: string, fileType?: string): File {
75
+ // Extract base64 data and the MIME type from the header
76
+ const [header, base64Data] = base64Url.split(',');
77
+
78
+ // Infer the file type from the header if not provided
79
+ fileType = fileType || header.match(/:(.*?);/)?.[1];
80
+
81
+ // If the fileName is not provided, generate a random one with an appropriate extension
82
+ if (!fileName) {
83
+ const extension = fileType?.split('/')[1] || 'bin'; // Default to 'bin' if no type is inferred
84
+ fileName = `file_${Date.now()}.${extension}`;
85
+ }
86
+
87
+ // Decode the base64 string to a binary string
88
+ const binaryString = window.atob(base64Data);
89
+ const len = binaryString.length;
90
+ const bytes = new Uint8Array(len);
91
+
92
+ for (let i = 0; i < len; i++) {
93
+ bytes[i] = binaryString.charCodeAt(i);
94
+ }
95
+
96
+ // Create a Blob from the decoded data
97
+ const blob = new Blob([bytes], { type: fileType });
98
+
99
+ // Create and return a File from the Blob
100
+ return new File([blob], fileName, { type: fileType });
101
+ }
@@ -0,0 +1,59 @@
1
+ import { Declaration } from "./types.ts";
2
+
3
+
4
+ export const getIdentifier = (selectorOrProperty: string, propertyValue?: string) => {
5
+ return `${selectorOrProperty.replace(/\s/g, "")}${propertyValue? `:${propertyValue.replace(/\s/g, "")}` : ""}`
6
+ }
7
+
8
+ export function parseValueUnit(input: string) {
9
+ const match = input.match(/^([-+]?\d*\.?\d+)([a-zA-Z]+)$/);
10
+ if (match) {
11
+ return {
12
+ value: match[1],
13
+ unit: match[2]
14
+ };
15
+ } else {
16
+ throw new Error(`Invalid input format: ${input}`);
17
+ }
18
+ }
19
+
20
+ export function parseAndModifyCSS(cssString: string, callback: (selector: string, declarations: Declaration[]) => Declaration[]) {
21
+ const cssObject: {[x: string]: Declaration[]} = {};
22
+
23
+ // Regular expression to match selectors and their declarations
24
+ const regex = /([^{]+)\{([^}]+)\}/g;
25
+ let match;
26
+
27
+ // Parse the CSS content into an object
28
+ while ((match = regex.exec(cssString)) !== null) {
29
+ const selector = match[1].trim();
30
+ const declarations = match[2].trim().split(';').filter(Boolean).map(decl => {
31
+ const [property, value] = decl.split(':').map(item => item.trim());
32
+ return { property, value };
33
+ });
34
+
35
+ cssObject[selector] = declarations;
36
+ }
37
+
38
+ // Function to process each selector and its declarations using the callback
39
+ Object.keys(cssObject).forEach(selector => {
40
+ const declarations = cssObject[selector];
41
+
42
+ // Call the callback, passing the selector and its declarations
43
+ const updatedDeclarations = callback(selector, declarations);
44
+
45
+ // Replace the original declarations with the returned value
46
+ cssObject[selector] = updatedDeclarations;
47
+ });
48
+
49
+ // Convert the object back to a CSS string
50
+ let newCssString = '';
51
+ Object.keys(cssObject).forEach(selector => {
52
+ const declarations = cssObject[selector]
53
+ .map(({ property, value }) => `${property}: ${value};`)
54
+ .join(' ');
55
+ newCssString += `${selector} { ${declarations} }\n`;
56
+ });
57
+
58
+ return newCssString;
59
+ }
package/src/filters.ts ADDED
@@ -0,0 +1,226 @@
1
+ import { FilterArgs, Filters } from "./types.ts";
2
+ import { AnalyzeResult, transformImageByTemplate } from "./imagePassportUtils.ts";
3
+ import { ImageWrapper } from './polyfills/Image.ts';
4
+ import { getCanvas } from "./imageHelper.ts";
5
+
6
+ const isNode = typeof window === 'undefined';
7
+ // Helper function to apply the filter
8
+ const applyFilter = (base64ImageString: string, filter: (data: ImageData, args?: FilterArgs | null) => void, args?: FilterArgs | null): Promise<string> => {
9
+ //alert(`Filter will apply`)
10
+ return new Promise((resolve, reject) => {
11
+ const img = new Image();
12
+
13
+ img.onload = () => {
14
+
15
+ const canvas = getCanvas(img.width, img.width);
16
+ //const canvas = document.createElement('canvas');
17
+ const ctx = canvas.getContext('2d') as any;
18
+ if (!ctx) {
19
+ return reject(new Error("Canvas not supported"));
20
+ }
21
+
22
+ canvas.width = img.width;
23
+ canvas.height = img.height;
24
+ const realImage = ((img as any) as ImageWrapper).realImage? ((img as any) as ImageWrapper).realImage : img
25
+ ctx.drawImage(realImage, 0, 0);
26
+
27
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
28
+ filter(imageData, args);
29
+ ctx.putImageData(imageData, 0, 0);
30
+
31
+ resolve(canvas.toDataURL());
32
+ };
33
+
34
+ img.src = base64ImageString;
35
+
36
+ img.onerror = reject;
37
+ });
38
+ };
39
+
40
+ const BLUR_RADII = ["0.1", "0.2", "0.3", "0.4", "0.5", "0.6", "0.7",/* "0.8", "0.9", "1"*/]
41
+ // Define the FILTERS object with filter functions and corresponding React components
42
+ const FILTERS: Filters = {
43
+ Whitescale: {
44
+ id: "Whitescale",
45
+ filter: (base64ImageString) => applyFilter(base64ImageString, (data) => {
46
+ const pixels = data.data;
47
+ for (let i = 0; i < pixels.length; i += 4) {
48
+ pixels[i] = 255; // R
49
+ pixels[i + 1] = 255; // G
50
+ pixels[i + 2] = 255; // B
51
+ }
52
+ })
53
+ },
54
+ Blackscale: {
55
+ id: "Blackscale",
56
+ filter: (base64ImageString) => applyFilter(base64ImageString, (data) => {
57
+ const pixels = data.data;
58
+ for (let i = 0; i < pixels.length; i += 4) {
59
+ pixels[i] = 0; // R
60
+ pixels[i + 1] = 0; // G
61
+ pixels[i + 2] = 0; // B
62
+ }
63
+ })
64
+ },
65
+ Greyscale: {
66
+ id: "Greyscale",
67
+ filter: (base64ImageString) => applyFilter(base64ImageString, (data) => {
68
+ const pixels = data.data;
69
+ for (let i = 0; i < pixels.length; i += 4) {
70
+ const avg = (pixels[i] + pixels[i + 1] + pixels[i + 2]) / 3;
71
+ pixels[i] = avg; // R
72
+ pixels[i + 1] = avg; // G
73
+ pixels[i + 2] = avg; // B
74
+ }
75
+ })
76
+ },
77
+ Redscale: {
78
+ id: "Redscale",
79
+ filter: (base64ImageString) => applyFilter(base64ImageString, (data) => {
80
+ const pixels = data.data;
81
+ for (let i = 0; i < pixels.length; i += 4) {
82
+ pixels[i + 1] = 0; // G
83
+ pixels[i + 2] = 0; // B
84
+ }
85
+ })
86
+ },
87
+ Greenscale: {
88
+ id: "Greenscale",
89
+ filter: (base64ImageString) => applyFilter(base64ImageString, (data) => {
90
+ const pixels = data.data;
91
+ for (let i = 0; i < pixels.length; i += 4) {
92
+ pixels[i] = 0; // R
93
+ pixels[i + 2] = 0; // B
94
+ }
95
+ })
96
+ },
97
+ Bluescale: {
98
+ id: "Bluescale",
99
+ filter: (base64ImageString) => applyFilter(base64ImageString, (data) => {
100
+ const pixels = data.data;
101
+ for (let i = 0; i < pixels.length; i += 4) {
102
+ pixels[i] = 0; // R
103
+ pixels[i + 1] = 0; // G
104
+ }
105
+ })
106
+ },
107
+ Sepia: {
108
+ id: "Sepia",
109
+ filter: (base64ImageString, args) => applyFilter(base64ImageString, (data) => {
110
+ // Set default values for sepia transformation or use provided args
111
+ const rMult = args?.rMult ?? 0.393;
112
+ const gMult = args?.gMult ?? 0.769;
113
+ const bMult = args?.bMult ?? 0.189;
114
+ const rAdd = args?.rAdd ?? 0.349;
115
+ const gAdd = args?.gAdd ?? 0.686;
116
+ const bAdd = args?.bAdd ?? 0.272;
117
+
118
+ const pixels = data.data;
119
+ for (let i = 0; i < pixels.length; i += 4) {
120
+ const r = pixels[i]; // Red
121
+ const g = pixels[i + 1]; // Green
122
+ const b = pixels[i + 2]; // Blue
123
+
124
+ // Apply customizable sepia transformation
125
+ const tr = rMult * r + gMult * g + bMult * b;
126
+ const tg = rAdd * r + gAdd * g + bAdd * b;
127
+ const tb = bAdd * r + bAdd * g + bAdd * b;
128
+
129
+ pixels[i] = tr > 255 ? 255 : tr; // Red
130
+ pixels[i + 1] = tg > 255 ? 255 : tg; // Green
131
+ pixels[i + 2] = tb > 255 ? 255 : tb; // Blue
132
+ }
133
+ })
134
+ },
135
+ Guassianblur: {
136
+ id: "Guassianblur",
137
+ filter: (base64ImageString, args) => applyFilter(base64ImageString, (data, args) => {
138
+ const minRadius = Number(BLUR_RADII[0])
139
+ const maxRadius = Number(BLUR_RADII[BLUR_RADII.length - 1])
140
+ var radius = args?.radius && !isNaN(args.radius) ? Number(args.radius) : minRadius;// Default radius
141
+ if(radius > maxRadius) radius = minRadius;
142
+ radius *= 10;
143
+
144
+ const pixels = data.data;
145
+ const width = data.width;
146
+ const height = data.height;
147
+
148
+ // Create a copy of the original pixel data
149
+ const copyData = new Uint8ClampedArray(pixels);
150
+
151
+ // Apply separable Gaussian blur: first horizontal, then vertical
152
+ // Horizontal pass
153
+ function blurHorizontal() {
154
+ for (let y = 0; y < height; y++) {
155
+ for (let x = 0; x < width; x++) {
156
+ let r = 0, g = 0, b = 0, a = 0, count = 0;
157
+
158
+ for (let dx = -radius; dx <= radius; dx++) {
159
+ const nx = x + dx;
160
+
161
+ if (nx >= 0 && nx < width) {
162
+ const i = (y * width + nx) * 4;
163
+ r += copyData[i];
164
+ g += copyData[i + 1];
165
+ b += copyData[i + 2];
166
+ a += copyData[i + 3];
167
+ count++;
168
+ }
169
+ }
170
+
171
+ const idx = (y * width + x) * 4;
172
+ pixels[idx] = r / count;
173
+ pixels[idx + 1] = g / count;
174
+ pixels[idx + 2] = b / count;
175
+ pixels[idx + 3] = a / count;
176
+ }
177
+ }
178
+ }
179
+
180
+ // Vertical pass
181
+ function blurVertical() {
182
+ for (let x = 0; x < width; x++) {
183
+ for (let y = 0; y < height; y++) {
184
+ let r = 0, g = 0, b = 0, a = 0, count = 0;
185
+
186
+ for (let dy = -radius; dy <= radius; dy++) {
187
+ const ny = y + dy;
188
+
189
+ if (ny >= 0 && ny < height) {
190
+ const i = (ny * width + x) * 4;
191
+ r += copyData[i];
192
+ g += copyData[i + 1];
193
+ b += copyData[i + 2];
194
+ a += copyData[i + 3];
195
+ count++;
196
+ }
197
+ }
198
+
199
+ const idx = (y * width + x) * 4;
200
+ pixels[idx] = r / count;
201
+ pixels[idx + 1] = g / count;
202
+ pixels[idx + 2] = b / count;
203
+ pixels[idx + 3] = a / count;
204
+ }
205
+ }
206
+ }
207
+
208
+ // Apply the separable blur
209
+ blurHorizontal(); // First pass
210
+ blurVertical(); // Second pass
211
+ }, args)
212
+ },/*
213
+ Rotate: {
214
+ id: "Rotate",
215
+ filter: (base64ImageString, args) => new Promise((resolve, reject) => {
216
+ resolve(base64ImageString)
217
+ }),
218
+ render: null
219
+ },*/
220
+ ImageTransform: {
221
+ id: "ImageTransform",
222
+ filter: (base64ImageString, args) => transformImageByTemplate(args as AnalyzeResult, base64ImageString, 2)
223
+ },
224
+ }
225
+
226
+ export default FILTERS
@@ -0,0 +1,204 @@
1
+ import { parse, INode } from 'svgson';
2
+ import { Font, FontsMap } from './types.ts';
3
+ import { cleanFilename, isBrowser } from './utils.ts';
4
+ import { getCanvas } from './imageHelper.ts';
5
+
6
+ const isNode = typeof window === 'undefined';
7
+
8
+ // Function to recursively traverse the parsed SVG and collect font-family values
9
+ export function collectFontFamilies(node: INode, fontFamilies: Set<string> = new Set()): Set<string> {
10
+ // Check for direct font-family attribute
11
+ if (node.attributes && node.attributes['font-family']) {
12
+ fontFamilies.add(node.attributes['font-family']);
13
+ }
14
+
15
+ // Check for <defs> and <style> elements
16
+ if (node.name === 'defs' && node.children) {
17
+ node.children.forEach(child => {
18
+ if (child.name === 'style' && child.children && child.children.length > 0) {
19
+ const cssContent = child.children[0].value;
20
+ extractFontFamiliesFromCSS(cssContent, fontFamilies);
21
+ }
22
+ });
23
+ }
24
+
25
+ // Recurse into children
26
+ if (node.children && node.children.length > 0) {
27
+ node.children.forEach(child => collectFontFamilies(child, fontFamilies));
28
+ }
29
+
30
+ return fontFamilies;
31
+ }
32
+
33
+ // Function to extract font-family values from CSS content
34
+ export function extractFontFamiliesFromCSS(cssContent: string, fontFamilies: Set<string>) {
35
+ const fontFamilyRegex = /font-family:\s*([^;]+);/g;
36
+ let match;
37
+ while ((match = fontFamilyRegex.exec(cssContent)) !== null) {
38
+ fontFamilies.add(match[1].trim());
39
+ }
40
+ }
41
+
42
+ // Main export function to parse the SVG and return a list of font-family values
43
+ export async function getFontFamiliesFromSVG(svgString: string): Promise<string[]> {
44
+ const parsedSVG = await parse(svgString);
45
+ const fontFamilies = collectFontFamilies(parsedSVG as INode);
46
+ return Array.from(fontFamilies);
47
+ }
48
+
49
+
50
+ async function fetchFontDataAsDataUrl(url: string): Promise<string> {
51
+ try {
52
+ const response = await fetch(url);
53
+ if (!response.ok) {
54
+ throw new Error(`HTTP status ${response.status}`);
55
+ }
56
+ const blob = await response.blob();
57
+
58
+ if (typeof window !== "undefined" && typeof FileReader !== "undefined") {
59
+ // Browser Environment: Use FileReader
60
+ return new Promise<string>((resolve, reject) => {
61
+ const reader = new FileReader();
62
+ reader.onloadend = () => resolve(reader.result as string);
63
+ reader.onerror = reject;
64
+ reader.readAsDataURL(blob);
65
+ });
66
+ } else if (typeof Buffer !== "undefined") {
67
+ // Node.js Environment: Use Buffer
68
+ const arrayBuffer = await blob.arrayBuffer();
69
+ const base64String = Buffer.from(arrayBuffer).toString("base64");
70
+ const mimeType = blob.type || "application/octet-stream";
71
+ return `data:${mimeType};base64,${base64String}`;
72
+ } else {
73
+ throw new Error("Base64 conversion not supported in this environment");
74
+ }
75
+ } catch (error: any) {
76
+ throw new Error(`Failed to fetch font data from ${url}: ${error.message}`);
77
+ }
78
+ }
79
+
80
+
81
+ export function getFontId(fontName: string): string {
82
+ // Convert to lowercase
83
+ let fontId = fontName.toLowerCase();
84
+ fontId = fontId.replace(/["']/g, "")
85
+
86
+ // Replace spaces and special characters with hyphens
87
+ fontId = fontId.replace(/[\s]+/g, '-');
88
+
89
+ // Remove any characters that are not alphanumeric or hyphens
90
+ fontId = fontId.replace(/[^a-z0-9-]+/g, '');
91
+
92
+ // Ensure no multiple consecutive hyphens
93
+ fontId = fontId.replace(/-+/g, '-');
94
+
95
+ // Trim hyphens from start and end
96
+ fontId = fontId.replace(/^-|-$/g, '');
97
+
98
+ return fontId;
99
+ }
100
+
101
+ export function generateFontMap(
102
+ fontsLocationDomain: string,
103
+ fontNames: string[],
104
+ fontExtensions = ["ttf", "otf", "woff", "woff2", "eot", "svg"]
105
+ ): Promise<FontsMap> {
106
+ return new Promise(async (resolve, reject) => {
107
+ const fontMap: FontsMap = {};
108
+
109
+ for (const fontName of fontNames) {
110
+ const fontInfo: Font = {
111
+ name: fontName.replace(/["']/g, ""),
112
+ id: getFontId(fontName)
113
+ };
114
+
115
+ let fontFound = false;
116
+
117
+ for (const ext of fontExtensions) {
118
+ const url = `https://${fontsLocationDomain}/fonts/${cleanFilename(fontInfo.id)}.${ext}?v=1`;
119
+ try {
120
+ const dataUrl = await fetchFontDataAsDataUrl(url);
121
+ fontInfo.url = url;
122
+ fontInfo.dataUrl = dataUrl;
123
+ fontInfo.ext = ext;
124
+ fontFound = true;
125
+ break;
126
+ } catch (error: any) {
127
+ fontInfo.readError = error.message;
128
+ }
129
+ }
130
+
131
+ if (!fontFound) {
132
+ fontInfo.readError = `No valid font found for ${fontName} with extensions: ${fontExtensions.join(
133
+ ', '
134
+ )}`;
135
+ }
136
+
137
+ fontMap[fontInfo.id] = fontInfo;
138
+ }
139
+
140
+ resolve(fontMap)
141
+ })
142
+ }
143
+
144
+ export function getFontFormat(extension: string) {
145
+ const formatMap: {[x: string]: string} = {
146
+ 'ttf': 'truetype',
147
+ 'otf': 'opentype',
148
+ 'woff': 'woff',
149
+ 'woff2': 'woff2',
150
+ 'eot': 'embedded-opentype',
151
+ 'svg': 'svg'
152
+ };
153
+
154
+ return formatMap[extension.toLowerCase()] || 'unknown';
155
+ }
156
+
157
+ export function createFontThumbnail(fontBase64: string, fontFormat: string): Promise<string> {
158
+ const mimeTypeMap: {[x: string]: string} = {
159
+ 'ttf': 'font/ttf',
160
+ 'otf': 'font/otf',
161
+ 'woff': 'font/woff',
162
+ 'woff2': 'font/woff2',
163
+ 'eot': 'font/eot',
164
+ 'svg': 'svg'
165
+ };
166
+
167
+ const mimeType = mimeTypeMap[fontFormat.toLowerCase()];
168
+ if (!mimeType) {
169
+ return Promise.reject(new Error('Unsupported font format'));
170
+ }
171
+
172
+ return new Promise((resolve, reject) => {
173
+ //return resolve("")
174
+ //const fontFace = new FontFace('CustomFont', `url(data:${mimeType};base64,${fontBase64})`);
175
+
176
+ if(!isBrowser() || !document?.fonts) return resolve("")
177
+ const fontFace = new FontFace('CustomFont', `url(${fontBase64})`);
178
+
179
+ fontFace.load().then((loadedFontFace) => {
180
+ (document.fonts as any).add(loadedFontFace);
181
+
182
+ const canvas = getCanvas(100, 100);
183
+ //const canvas = document.createElement('canvas');
184
+ canvas.width = 100; // Set canvas width
185
+ canvas.height = 100; // Set canvas height
186
+
187
+ const ctx = canvas.getContext('2d') as any;
188
+ if(!ctx) return
189
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
190
+ ctx.fillStyle = '#FFFFFF'; // Set background color
191
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
192
+ ctx.fillStyle = '#000000'; // Set text color
193
+ ctx.font = '50px CustomFont'; // Use the loaded font
194
+ ctx.textAlign = 'center';
195
+ ctx.textBaseline = 'middle';
196
+ ctx.fillText('Aa', canvas.width / 2, canvas.height / 2); // Draw the text
197
+
198
+ const dataUrl = canvas.toDataURL('image/png');
199
+ resolve(dataUrl);
200
+ }).catch(error => {
201
+ reject(error);
202
+ });
203
+ });
204
+ }