Compare commits

...
Author SHA1 Message Date
Prashant-7718 0fa868821b removed unwanted console 2025-05-12 18:00:54 +05:30
Prashant-7718 8b3962c5e5 fix: Font Awesome brand and regular icons 2025-05-12 17:47:29 +05:30
Sidharth Vinod 9626f6b5df fix: PNG export icons 2025-05-09 21:58:33 +05:30
+102 -11
View File
@@ -19,7 +19,7 @@
import ExternalLinkIcon from '~icons/material-symbols/open-in-new-rounded'; import ExternalLinkIcon from '~icons/material-symbols/open-in-new-rounded';
import WidthIcon from '~icons/material-symbols/width-rounded'; import WidthIcon from '~icons/material-symbols/width-rounded';
const FONT_AWESOME_URL = `https://cdnjs.cloudflare.com/ajax/libs/font-awesome/${FAVersion}/css/all.min.css`; const FONT_AWESOME_URL = `https://cdnjs.cloudflare.com/ajax/libs/font-awesome/${FAVersion}`;
type Exporter = (context: CanvasRenderingContext2D, image: HTMLImageElement) => () => void; type Exporter = (context: CanvasRenderingContext2D, image: HTMLImageElement) => () => void;
@@ -32,7 +32,23 @@
return svgElement; return svgElement;
}; };
const getBase64SVG = (svg?: HTMLElement, width?: number, height?: number): string => { /**
* Converts a Blob to base64 string
*/
export function blobToBase64(blob: Blob): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.addEventListener('load', () => resolve(reader.result as string));
reader.addEventListener('error', () => reject(new Error('Failed to convert blob to base64')));
reader.readAsDataURL(blob);
});
}
const getBase64SVG = async (
svg?: HTMLElement,
width?: number,
height?: number
): Promise<string> => {
if (svg) { if (svg) {
// Prevents the SVG size of the interface from being changed // Prevents the SVG size of the interface from being changed
svg = svg.cloneNode(true) as HTMLElement; svg = svg.cloneNode(true) as HTMLElement;
@@ -44,13 +60,88 @@
svg = getSvgElement(); svg = getSvgElement();
} }
const svgString = svg.outerHTML try {
.replaceAll('<br>', '<br/>') // Fetch both regular and brands Font Awesome CSS and font files
.replaceAll(/<img([^>]*)>/g, (m, g: string) => `<img ${g} />`); const [fontAwesomeCSS, solidFontBlob, brandsFontBlob, regularFontBlob] = await Promise.all([
fetch(`${FONT_AWESOME_URL}/css/all.min.css`).then((response) => response.text()),
fetch(`${FONT_AWESOME_URL}/webfonts/fa-solid-900.woff2`)
.then((res) => res.blob())
.catch(() => new Blob()),
fetch(`${FONT_AWESOME_URL}/webfonts/fa-brands-400.woff2`)
.then((res) => res.blob())
.catch(() => new Blob()),
fetch(`${FONT_AWESOME_URL}/webfonts/fa-regular-400.woff2`)
.then((res) => res.blob())
.catch(() => new Blob())
]);
return toBase64(`<?xml version="1.0" encoding="UTF-8"?> // Convert font blobs to base64
<?xml-stylesheet href="${FONT_AWESOME_URL}" type="text/css"?> const [solidFontBase64, brandsFontBase64, regularFontBase64] = await Promise.all([
${svgString}`); blobToBase64(solidFontBlob).then((data) => data.split(',')[1]),
blobToBase64(brandsFontBlob).then((data) => data.split(',')[1]),
blobToBase64(regularFontBlob).then((data) => data.split(',')[1])
]);
// Create font-face definitions for all Font Awesome types
const fontFaceCSS = `
@font-face {
font-family: 'Font Awesome 6 Free';
font-style: normal;
font-weight: 900;
src: url(data:font/woff2;base64,${solidFontBase64}) format('woff2');
}
@font-face {
font-family: 'Font Awesome 6 Free';
font-style: normal;
font-weight: 400;
src: url(data:font/woff2;base64,${regularFontBase64}) format('woff2');
}
@font-face {
font-family: 'Font Awesome 6 Brands';
font-style: normal;
font-weight: 400;
src: url(data:font/woff2;base64,${brandsFontBase64}) format('woff2');
}
.fa, .fas {
font-family: 'Font Awesome 6 Free';
font-weight: 900;
}
.far {
font-family: 'Font Awesome 6 Free';
font-weight: 400;
}
.fab {
font-family: 'Font Awesome 6 Brands';
font-weight: 400;
}
.fa, .fas, .far, .fab {
font-size: 16px;
width: 16px;
fill: currentColor;
}
`;
// Fix self-closing tags for XML compatibility
const svgString = svg.outerHTML
.replaceAll('<br>', '<br/>')
.replaceAll(/<img([^>]*)>/g, (m, g: string) => `<img ${g} />`)
.replace('<style>', `<style>${fontFaceCSS} ${fontAwesomeCSS}`);
return toBase64(svgString);
} catch {
// Fallback to simple SVG if font loading fails
const svgString = svg.outerHTML
.replaceAll('<br>', '<br/>')
.replaceAll(/<img([^>]*)>/g, (m, g: string) => `<img ${g} />`);
return toBase64(svgString);
}
}; };
const simulateDownload = (download: string, href: string): void => { const simulateDownload = (download: string, href: string): void => {
@@ -100,7 +191,7 @@ ${svgString}`);
exporter(context, image)(); exporter(context, image)();
$inputStateStore.panZoom = true; $inputStateStore.panZoom = true;
}); });
image.src = `data:image/svg+xml;base64,${getBase64SVG(svg, canvas.width, canvas.height)}`; image.src = `data:image/svg+xml;base64,${await getBase64SVG(svg, canvas.width, canvas.height)}`;
// Fallback to set panZoom to true after 2 seconds // Fallback to set panZoom to true after 2 seconds
// This is a workaround for the case when the image is not loaded // This is a workaround for the case when the image is not loaded
setTimeout(() => { setTimeout(() => {
@@ -160,8 +251,8 @@ ${svgString}`);
}); });
}; };
const onDownloadSVG = () => { const onDownloadSVG = async () => {
simulateDownload(getFileName('svg'), `data:image/svg+xml;base64,${getBase64SVG()}`); simulateDownload(getFileName('svg'), `data:image/svg+xml;base64,${await getBase64SVG()}`);
logEvent('download', { logEvent('download', {
type: 'svg' type: 'svg'
}); });