UNPKG

@svgd/core

Version:

An SVG optimization tool that converts SVG files into a single path 'd' attribute string for efficient storage and rendering.

374 lines (367 loc) 10.1 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { defaultConfig: () => defaultConfig, getPaths: () => getPaths, getSvg: () => getSvg, getSvgoConfig: () => getSvgoConfig }); module.exports = __toCommonJS(index_exports); // src/commands.ts var commands = [ { code: "o", attribute: "opacity", regexp: "[\\d.]+", toAttribute: (codeValue) => codeValue, toCommand: (attributeValue) => attributeValue }, { code: "of", attribute: "fill-opacity", regexp: "[\\d.]+", toAttribute: (codeValue) => codeValue, toCommand: (attributeValue) => attributeValue }, { code: "os", attribute: "stroke-opacity", regexp: "[\\d.]+", toAttribute: (codeValue) => codeValue, toCommand: (attributeValue) => attributeValue }, { code: "f", attribute: "stroke", regexp: "[#0-9a-zA-Z]+", toAttribute: (codeValue) => { switch (codeValue) { case "c": return "currentColor"; case "n": return "none"; default: return codeValue; } }, toCommand: (attributeValue) => { switch (attributeValue) { case "currentColor": return "c"; case "none": return "n"; default: return attributeValue; } } }, { code: "F", attribute: "fill", regexp: "[#0-9a-zA-Z]+", toAttribute: (codeValue) => { switch (codeValue) { case "c": return "currentColor"; case "n": return "none"; default: return codeValue; } }, toCommand: (attributeValue) => { switch (attributeValue) { case "currentColor": return null; case "none": return "n"; default: return attributeValue; } } }, { code: "w", attribute: "stroke-width", regexp: "[\\d.]+", toAttribute: (codeValue) => codeValue, toCommand: (attributeValue) => attributeValue }, { code: "e", attribute: "fill-rule", regexp: "", toAttribute: () => "evenodd", toCommand: (attributeValue) => attributeValue === "evenodd" ? "" : null } ]; // src/getPaths.ts function getPaths(d) { const paths = []; let attributes = {}; const pathCommands = d.split(new RegExp( `(${commands.map((cmd) => `${cmd.code}${cmd.regexp}`).join("|")})` )); pathCommands.forEach((text, i) => { const isCommand = i % 2 === 1; if (isCommand) { commands.forEach(({ code, attribute, regexp, toAttribute }) => { const match = text.match(new RegExp(`^${code}(${regexp})$`)); if (match) { attributes[attribute] = toAttribute(match[1]); } }); return; } const d2 = text.trim(); if (d2) { paths.push({ ...attributes, d: d2 }); attributes = {}; } }); return paths; } // src/getSvg.ts function getSvg(d, viewbox) { const svgParts = getPaths(d).map((attributes) => `<path ${attributes ? Object.entries(attributes).map(([k, v]) => `${k}="${v}"`).join(" ") : ""} />`); const { minX = 0, minY = 0, width = 24, height = 24 } = viewbox ?? {}; const content = svgParts.length ? ` ${svgParts.join(` `)} ` : ""; return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${minX} ${minY} ${width} ${height}" width="${width}" height="${height}">${content}</svg>`; } // src/defaultConfig.ts var defaultConfig = { resize: { targetViewBox: { minX: 0, minY: 0, width: 24, height: 24 } }, colors: false, svgo: { plugins: [ { name: "preset-default", params: { overrides: { convertShapeToPath: false, convertColors: false, mergePaths: false, moveElemsAttrsToGroup: false, moveGroupAttrsToElems: false } } }, { name: "inlineStyles", params: { onlyMatchedOnce: false } }, { name: "convertStyleToAttrs" }, { name: "removeUselessStrokeAndFill", params: { stroke: true, fill: true, removeNone: true } }, { name: "convertColors", params: { currentColor: false, names2hex: true, rgb2hex: true, shorthex: true, shortname: false } }, { name: "convertShapeToPath", params: { convertArcs: true } }, { name: "mergePaths", params: { force: true } }, { name: "moveGroupAttrsToElems" }, { name: "collapseGroups" }, { name: "convertPathData" }, { name: "removeHiddenElems" }, { name: "removeUselessDefs" } ] } }; // src/resizePlugin.ts function resizePlugin(params) { return { name: "resizePlugin", fn: (ast) => { const svgNode = getSvgNode(ast); if (!svgNode) return null; const originalDims = getOriginalDimensions(svgNode); const transform = computeTransformations(originalDims, params); wrapChildrenInGroup(svgNode, transform); overrideSvgAttributesIfNeeded(svgNode, params); return null; } }; } function getSvgNode(ast) { return ast.children.find( (node) => node.type === "element" && node.name === "svg" ); } function getOriginalDimensions(svgNode) { const viewBox = svgNode.attributes.viewBox; if (viewBox) { const [minX, minY, width, height] = viewBox.split(/[\s,]+/).map(parseFloat); return { minX, minY, width, height }; } return { minX: 0, minY: 0, width: parseFloat(svgNode.attributes.width ?? "100"), height: parseFloat(svgNode.attributes.height ?? "100") }; } function computeTransformations(originalDims, params) { const { targetViewBox, preserveAspectRatio = true } = params; const { minX: origMinX, minY: origMinY, width: origWidth, height: origHeight } = originalDims; const { minX, minY, width, height } = targetViewBox; const scaleX = width / origWidth; const scaleY = height / origHeight; const scale = preserveAspectRatio ? Math.min(scaleX, scaleY) : NaN; const translateX = minX - origMinX * (preserveAspectRatio ? scale : scaleX) + (preserveAspectRatio ? (width - origWidth * scale) / 2 : 0); const translateY = minY - origMinY * (preserveAspectRatio ? scale : scaleY) + (preserveAspectRatio ? (height - origHeight * scale) / 2 : 0); if (preserveAspectRatio) { return `translate(${translateX}, ${translateY}) scale(${scale}, ${scale})`; } return `translate(${translateX}, ${translateY}) scale(${scaleX}, ${scaleY})`; } function wrapChildrenInGroup(svgNode, transform) { const groupNode = { type: "element", name: "g", attributes: { transform }, children: [] }; groupNode.children = svgNode.children.splice(0, svgNode.children.length); svgNode.children.push(groupNode); } function overrideSvgAttributesIfNeeded(svgNode, params) { const { overrideSvgAttributes = true, targetViewBox } = params; if (!overrideSvgAttributes) return; const { minX, minY, width, height } = targetViewBox; svgNode.attributes.viewBox = `${minX} ${minY} ${width} ${height}`; delete svgNode.attributes.width; delete svgNode.attributes.height; } // src/getSvgoConfig.ts var getSvgoConfig = (config = defaultConfig) => { const plugins = config.svgo.plugins ?? []; const pluginsByColor = config.colors ? plugins : plugins.map((plugin) => typeof plugin === "object" && plugin.name === "convertColors" ? { ...plugin, params: { currentColor: true } } : plugin); return { ...config.svgo, plugins: [ resizePlugin(config.resize), ...pluginsByColor, extractPathDPlugin() ] }; }; var extractPathDPlugin = () => ({ name: "extractPathD", fn: (ast) => { const collectPathsContext = { paths: [], wasCommand: false }; collectPaths(ast, collectPathsContext); ast.children = [{ type: "text", value: collectPathsContext.paths.join(" ") }]; return null; } }); var collectPaths = (node, context) => { if (node.type === "element" && !["path", "g", "svg", "title"].includes(node.name)) { throw new Error(`[SVGD ERROR] svg has other tag "${node.name}"`); } if (node.type === "element" && node.name === "path" && node.attributes.d) { const { attributes } = node; const d = attributes.d; const commandsArray = []; commands.forEach(({ code, toCommand, attribute }) => { if (attribute in attributes) { const commandValue = toCommand(attributes[attribute]); if (commandValue !== null) { commandsArray.push(`${code}${commandValue}`); } } }); if (commandsArray.length) { context.wasCommand = true; context.paths.push(...commandsArray); } else if (context.wasCommand) { context.paths.push("o1"); } context.paths.push(d); } if ("children" in node) { node.children.forEach((node2) => collectPaths(node2, context)); } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { defaultConfig, getPaths, getSvg, getSvgoConfig }); //# sourceMappingURL=index.cjs.map