UNPKG

@realfavicongenerator/generate-favicon

Version:
133 lines (110 loc) 4.4 kB
import { SVG, Svg } from "@svgdotjs/svg.js"; import { ImageAdapter } from "./adapter"; import { numberAliasToNumber } from "../icon/helper"; import { filterDoctypeOut } from "./helper"; import { cloneSvg } from "./desktop"; export const stringToSvg = (svg: string, imageAdapeter: ImageAdapter): Svg => { // We use filterDoctypeOut because of // https://github.com/RealFaviconGenerator/realfavicongenerator/issues/507 // and https://github.com/svgdotjs/svgdom/issues/130 const s = imageAdapeter.createSvg().svg(filterDoctypeOut(svg)); const subSvg: any = s.find('svg')[0]; if (subSvg) { const viewBox = subSvg.viewbox(); const innerAttrs: Record<string, any> = subSvg.attr(); // Remove outer SVG attributes that aren't on the inner SVG const outerAttrs: Record<string, any> = s.attr(); for (const key of Object.keys(outerAttrs)) { if (!(key in innerAttrs)) { s.attr(key, null); } } // Copy all attributes from inner SVG to outer SVG for (const [key, value] of Object.entries(innerAttrs)) { s.attr(key, value); } // Set width/height, falling back to viewBox dimensions if not explicit s.width(subSvg.width() ? subSvg.width() : viewBox.width); s.height(subSvg.height() ? subSvg.height() : viewBox.height); // Move children from inner SVG to outer SVG and remove the nested SVG while (subSvg.children().length > 0) { s.add(subSvg.children()[0]); } subSvg.remove(); } return s; }; export const urlToSvg = async (url: string, imageAdapter: ImageAdapter): Promise<Svg> => { const response = await fetch(url); const text = await response.text(); return stringToSvg(text, imageAdapter); }; export const blobToDataUrl = (blob: Blob): Promise<string> => { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onloadend = () => { if (typeof reader.result === 'string') { resolve(reader.result); } else { reject('Failed to read blob'); } }; reader.onerror = (e) => { reject(e); }; reader.readAsDataURL(blob); }); } export const bitmapToSvg = async (binaryData: ArrayBuffer, mimeType: string, imageAdapter: ImageAdapter): Promise<Svg> => { const blob = new Blob([ binaryData ], { type: mimeType }); const url = await blobToDataUrl(blob); return dataUrlToSvg(url, imageAdapter); } export const httpUrlToDataUrl = async (url: string, imageAdapter: ImageAdapter): Promise<string> => { const response = await fetch(url); const buffer = await response.arrayBuffer(); const contentType = response.headers.get('content-type') || 'application/octet-stream'; return `data:${contentType};base64,${Buffer.from(buffer).toString('base64')}`; } export const anyUrlToSvg = async (url: string, imageAdapter: ImageAdapter): Promise<Svg> => { const dataUrl = url.startsWith('http') ? httpUrlToDataUrl(url, imageAdapter) : url; return dataUrlToSvg(url, imageAdapter); } export const dataUrlToSvg = async (dataUrl: string, imageAdapter: ImageAdapter): Promise<Svg> => { const { width, height } = await imageAdapter.getImageSize(dataUrl); const svg = imageAdapter.createSvg().size(width, height); const i = svg.image(dataUrl); i.width(width); i.height(height); svg.viewbox(0, 0, width, height); return svg; } export const makeFluidSvg = (svg: Svg): Svg => { const viewBox = svg.viewbox(); const w = viewBox.width || 100; const h = viewBox.height || 100; svg.width('100%'); svg.height('100%'); svg.viewbox(0, 0, w, h); const children = svg.children(); if (children.length === 1) { const child = children[0]; if (child instanceof Svg) { const viewBox = child.viewbox(); const w = viewBox.width; const h = viewBox.height; child.width('100%'); child.height('100%'); child.viewbox(0, 0, w, h); } } return svg; } export const scaleSvg = (svg: Svg, widthHeight: number, imageAdapeter: ImageAdapter): Svg => { const newSvg = imageAdapeter.createSvg(); newSvg.size(widthHeight, widthHeight); const group = newSvg.group(); group.attr('transform', `scale(${widthHeight / numberAliasToNumber(svg.width())})`); group.add(cloneSvg(svg, imageAdapeter)); return newSvg; }