svgfusion-core
Version:
Core engine and utilities for SVGFusion - the foundation library that powers SVG to component conversion.
2 lines • 16 kB
JavaScript
'use strict';require('svgfusion-utils');var W=Object.defineProperty;var A=(a,t,r)=>t in a?W(a,t,{enumerable:true,configurable:true,writable:true,value:r}):a[t]=r;var G=(a=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(a,{get:(t,r)=>(typeof require<"u"?require:t)[r]}):a)(function(a){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+a+'" is not supported')});var c=(a,t,r)=>A(a,typeof t!="symbol"?t+"":t,r);var f=class{parse(t){let r=this.cleanSvgContent(t),e=this.parseElement(r);return {root:e,viewBox:e.attributes.viewBox,width:e.attributes.width,height:e.attributes.height,namespace:e.attributes.xmlns}}extractColors(t){let r=[];return this.traverseElements(t.root,e=>{e.attributes.fill&&this.isValidColor(e.attributes.fill)&&r.push({value:e.attributes.fill,type:"fill",element:e,attribute:"fill"}),e.attributes.stroke&&this.isValidColor(e.attributes.stroke)&&r.push({value:e.attributes.stroke,type:"stroke",element:e,attribute:"stroke"}),e.attributes["stop-color"]&&this.isValidColor(e.attributes["stop-color"])&&r.push({value:e.attributes["stop-color"],type:"stop-color",element:e,attribute:"stop-color"});}),r}cleanSvgContent(t){return t.replace(/<\?xml[^>]*\?>/gi,"").replace(/<!--[\s\S]*?-->/g,"").trim()}parseElement(t){if(typeof globalThis.DOMParser<"u"){let e=new globalThis.DOMParser().parseFromString(t,"image/svg+xml");if(e.querySelector("parsererror"))throw new Error("Invalid SVG: XML parsing failed");let i=e.documentElement;if(i.tagName.toLowerCase()!=="svg")throw new Error("Invalid SVG: No svg element found");return this.convertDOMToSVGElement(i)}try{let e=(typeof G<"u"?G:function(){throw new Error("require not available")}())("jsdom"),{JSDOM:s}=e,o=new s(t,{contentType:"image/svg+xml"}).window.document.documentElement;if(o.tagName.toLowerCase()!=="svg")throw new Error("Invalid SVG: No svg element found");return this.convertDOMToSVGElement(o)}catch{return this.parseElementWithRegex(t)}}convertDOMToSVGElement(t){let r={tag:this.preserveSvgTagCase(t.tagName),attributes:{},children:[]};for(let e=0;e<t.attributes.length;e++){let s=t.attributes[e];r.attributes[s.name]=s.value;}for(let e=0;e<t.children.length;e++){let s=t.children[e];r.children.push(this.convertDOMToSVGElement(s));}return t.children.length===0&&t.textContent?.trim()&&(r.content=t.textContent.trim()),r}parseElementWithRegex(t){let r=t.match(/<svg([^>]*)>/i);if(!r)throw new Error("Invalid SVG: No svg element found");let e=this.parseAttributes(r[1]),s=this.parseChildren(t);return {tag:"svg",attributes:e,children:s}}parseAttributes(t){let r={},e=/(\w+(?:-\w+)*)=["']([^"']*)["']/g,s;for(;(s=e.exec(t))!==null;)r[s[1]]=s[2];return r}parseChildren(t){let r=[],e=t.match(/<svg[^>]*>(.*)<\/svg>/is);if(!e||!e[1])return r;let s=e[1],i=/<(\w+(?::\w+)?)([^>]*?)(?:\s*\/\s*>|>(.*?)<\/\1\s*>)/gs,o;for(;(o=i.exec(s))!==null;){let[,n,d,l]=o,p=this.parseAttributes(d),g={tag:n,attributes:p,children:[]};l!==void 0&&(l.includes("<")?g.children=this.parseNestedElements(l):l.trim()&&(g.content=l.trim())),r.push(g);}return r}parseNestedElements(t){let r=[],e=/<(\w+(?::\w+)?)([^>]*?)(?:\s*\/\s*>|>(.*?)<\/\1\s*>)/gs,s;for(;(s=e.exec(t))!==null;){let[,i,o,n]=s,d=this.parseAttributes(o),l={tag:i,attributes:d,children:[]};n!==void 0&&(n.includes("<")?l.children=this.parseNestedElements(n):n.trim()&&(l.content=n.trim())),r.push(l);}return r}traverseElements(t,r){r(t),t.children.forEach(e=>this.traverseElements(e,r));}preserveSvgTagCase(t){let r=t.toLowerCase();return {clippath:"clipPath",defs:"defs",foreignobject:"foreignObject",lineargradient:"linearGradient",radialgradient:"radialGradient",textpath:"textPath",use:"use"}[r]||r}isValidColor(t){return !t||t==="none"||t==="transparent"||t==="currentColor"?false:/^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/.test(t)||/^rgba?\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*(?:,\s*[\d.]+\s*)?\)$/.test(t)||/^hsla?\(\s*\d+\s*,\s*\d+%\s*,\s*\d+%\s*(?:,\s*[\d.]+\s*)?\)$/.test(t)?true:["red","green","blue","black","white","yellow","cyan","magenta"].includes(t.toLowerCase())}};var S=class{constructor(t={}){c(this,"options");this.options={preserveOriginalNames:t.preserveOriginalNames??false,generateClasses:t.generateClasses??true,colorPrefix:t.colorPrefix??"color"};}extractColors(t){let r=[];this.traverseElement(t,r);let e=new Map;return r.forEach(s=>{this.isValidColor(s.value)&&e.set(s.value,s);}),Array.from(e.values()).sort((s,i)=>s.value.localeCompare(i.value))}apply(t){let r=this.extractColors(t),e=this.generateColorMappings(r),s=this.replaceColorsWithVariables(t,e);return {mappings:e,processedElement:s,originalColors:r.map(i=>i.value)}}generateColorMappings(t){return t.map((r,e)=>({originalColor:r.value,variableName:e===0?this.options.colorPrefix:`${this.options.colorPrefix}${e+1}`,type:r.type}))}replaceColorsWithVariables(t,r){let e=new Map(r.map(s=>[s.originalColor,s.variableName]));return this.transformElement(t,s=>{let i={...s.attributes};if(i.fill&&e.has(i.fill)&&(i.fill=`{${e.get(i.fill)}}`),i.stroke&&e.has(i.stroke)&&(i.stroke=`{${e.get(i.stroke)}}`),i["stop-color"]&&e.has(i["stop-color"])&&(i["stop-color"]=`{${e.get(i["stop-color"])}}`),i.style){let o=i.style,n=o.match(/fill:\s*([^;]+)/);if(n){let p=n[1].trim();e.has(p)&&(o=o.replace(/fill:\s*[^;]+/,`fill: {${e.get(p)}}`));}let d=o.match(/stroke:\s*([^;]+)/);if(d){let p=d[1].trim();e.has(p)&&(o=o.replace(/stroke:\s*[^;]+/,`stroke: {${e.get(p)}}`));}let l=o.match(/stop-color:\s*([^;]+)/);if(l){let p=l[1].trim();e.has(p)&&(o=o.replace(/stop-color:\s*[^;]+/,`stop-color: {${e.get(p)}}`));}i.style=o;}if(this.isDrawableElement(s.tag)){let o=s.attributes.fill!==void 0&&s.attributes.fill!=="",n=s.attributes.stroke!==void 0&&s.attributes.stroke!=="";o&&!n&&!i.stroke&&(i.stroke="none"),n&&!o&&!i.fill&&(i.fill="none");}return {...s,attributes:i}})}traverseElement(t,r){t.attributes.fill&&r.push({value:t.attributes.fill,type:"fill",element:t,attribute:"fill"}),t.attributes.stroke&&r.push({value:t.attributes.stroke,type:"stroke",element:t,attribute:"stroke"}),t.attributes["stop-color"]&&r.push({value:t.attributes["stop-color"],type:"stop-color",element:t,attribute:"stop-color"}),t.attributes.style&&this.extractColorsFromStyle(t.attributes.style).forEach(s=>{r.push({value:s.value,type:s.type,element:t,attribute:"style"});}),t.children.forEach(e=>this.traverseElement(e,r));}transformElement(t,r){let e=r(t);return {...e,children:e.children.map(s=>this.transformElement(s,r))}}isDrawableElement(t){return ["path","circle","ellipse","line","rect","polygon","polyline","text","tspan","use"].includes(t)}isValidColor(t){return !t||t==="none"||t==="transparent"||t==="currentColor"?false:/^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/.test(t)||/^rgba?\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*(?:,\s*[\d.]+\s*)?\)$/.test(t)||/^hsla?\(\s*\d+\s*,\s*\d+%\s*,\s*\d+%\s*(?:,\s*[\d.]+\s*)?\)$/.test(t)?true:["red","green","blue","yellow","orange","purple","pink","brown","black","white","gray","grey","cyan","magenta","lime","navy"].includes(t.toLowerCase())}extractColorsFromStyle(t){let r=[],e=t.match(/fill:\s*([^;]+)/);if(e){let o=e[1].trim();this.isValidColor(o)&&r.push({value:o,type:"fill"});}let s=t.match(/stroke:\s*([^;]+)/);if(s){let o=s[1].trim();this.isValidColor(o)&&r.push({value:o,type:"stroke"});}let i=t.match(/stop-color:\s*([^;]+)/);if(i){let o=i[1].trim();this.isValidColor(o)&&r.push({value:o,type:"stop-color"});}return r}};var b=class{constructor(t={}){c(this,"options");this.options={preserveExisting:t.preserveExisting??true,onlyIfStrokePresent:t.onlyIfStrokePresent??true};}apply(t){let r=0;return {processedElement:this.transformElement(t,s=>this.shouldAddVectorEffect(s)?(r++,{...s,attributes:{...s.attributes,"vector-effect":"non-scaling-stroke"}}):s),elementsModified:r}}shouldAddVectorEffect(t){return this.options.preserveExisting&&t.attributes["vector-effect"]?false:["path","line","polyline","polygon","rect","circle","ellipse"].includes(t.tag)}transformElement(t,r){let e=r(t);return {...e,children:e.children.map(s=>this.transformElement(s,r))}}};var v=class{constructor(t={}){c(this,"options");this.options={preserveOriginalNames:t.preserveOriginalNames??false,strokeWidthPrefix:t.strokeWidthPrefix??"strokeWidth",generateClasses:t.generateClasses??true};}extractStrokeWidths(t){let r=[];this.traverseElement(t,r);let e=new Map;return r.forEach(s=>{this.isValidStrokeWidth(s.value)&&e.set(s.value,s);}),Array.from(e.values()).sort((s,i)=>{let o=parseFloat(s.value),n=parseFloat(i.value);return isNaN(o)&&isNaN(n)?s.value.localeCompare(i.value):isNaN(o)?1:isNaN(n)?-1:o-n})}apply(t){let r=this.extractStrokeWidths(t),e=this.generateStrokeWidthMappings(r),s=this.replaceStrokeWidthsWithVariables(t,e);return {mappings:e,processedElement:s,originalStrokeWidths:r.map(i=>i.value)}}traverseElement(t,r){if(t.attributes["stroke-width"]&&r.push({value:t.attributes["stroke-width"],element:t,attribute:"stroke-width"}),t.attributes.style){let e=this.extractStrokeWidthFromStyle(t.attributes.style);e&&r.push({value:e,element:t,attribute:"style",styleProperty:"stroke-width"});}t.children.forEach(e=>this.traverseElement(e,r));}extractStrokeWidthFromStyle(t){let r=t.match(/stroke-width\s*:\s*([^;]+)/);return r?r[1].trim():null}isValidStrokeWidth(t){return t==="inherit"||t==="none"||t==="initial"||t==="unset"||t.includes("var(")||t.includes("calc(")?false:/^[\d.]+(?:px|pt|pc|in|cm|mm|em|rem|ex|ch|vw|vh|vmin|vmax|%)?$/.test(t.trim())}generateStrokeWidthMappings(t){return t.map((r,e)=>({originalStrokeWidth:r.value,variableName:e===0?this.options.strokeWidthPrefix:`${this.options.strokeWidthPrefix}${e+1}`}))}replaceStrokeWidthsWithVariables(t,r){let e=new Map(r.map(s=>[s.originalStrokeWidth,s.variableName]));return this.transformElement(t,s=>{let i={...s.attributes};if(i["stroke-width"]&&e.has(i["stroke-width"])&&(i["stroke-width"]=`{${e.get(i["stroke-width"])}}`),i.style){let o=i.style;e.forEach((n,d)=>{let l=new RegExp(`stroke-width\\s*:\\s*${this.escapeRegExp(d)}`,"g");o=o.replace(l,`stroke-width: {${n}}`);}),i.style=o;}return {...s,attributes:i}})}transformElement(t,r){let e=r(t);return {...e,children:e.children.map(s=>this.transformElement(s,r))}}escapeRegExp(t){return t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}};var E=class{constructor(t={}){c(this,"options");this.options={addRole:t.addRole??true,addAriaHidden:t.addAriaHidden??false,addTitle:t.addTitle??true,addDesc:t.addDesc??true,defaultRole:t.defaultRole??"img",preserveExisting:t.preserveExisting??true};}apply(t){let r=[];if(t.tag!=="svg")return {processedElement:t,attributesAdded:r};let e={...t.attributes};return this.options.addRole&&(!this.options.preserveExisting||!e.role)&&(e.role=this.options.defaultRole,r.push("role")),this.options.addAriaHidden&&(!this.options.preserveExisting||!e["aria-hidden"])&&(e["aria-hidden"]="true",r.push("aria-hidden")),this.options.addTitle&&r.push("title-support"),this.options.addDesc&&r.push("desc-support"),this.options.addTitle&&this.options.addDesc&&(!this.options.preserveExisting||!e["aria-labelledby"])?(e["aria-labelledby"]="{titleId} {descId}",r.push("aria-labelledby")):this.options.addTitle&&(!this.options.preserveExisting||!e["aria-labelledby"])&&(e["aria-labelledby"]="{titleId}",r.push("aria-labelledby")),{processedElement:{...t,attributes:e},attributesAdded:r}}generateProps(){let t=[];return this.options.addTitle&&t.push("title?: string","titleId?: string"),this.options.addDesc&&t.push("desc?: string","descId?: string"),t}generateJSXElements(t){let r=[];return t==="react"?(this.options.addDesc&&r.push("{desc ? <desc id={descId}>{desc}</desc> : null}"),this.options.addTitle&&r.push("{title ? <title id={titleId}>{title}</title> : null}")):(this.options.addDesc&&r.push('<desc v-if="desc" :id="descId">{{ desc }}</desc>'),this.options.addTitle&&r.push('<title v-if="title" :id="titleId">{{ title }}</title>')),r.join(`
`)}};var y=class{constructor(t){this.options=t;}apply(t){return this.options.enabled?this.normalizeElements(t):t}normalizeElements(t){return t.map(r=>this.transformElement(r,e=>{let s={...e.attributes};if(this.isDrawableElement(e.tag)){let i=e.attributes.fill!==void 0&&e.attributes.fill!=="",o=e.attributes.stroke!==void 0&&e.attributes.stroke!=="";i&&!o&&(s.stroke="none"),o&&!i&&(s.fill="none");}return {...e,attributes:s}}))}transformElement(t,r){let e=r(t);return e.children&&(e.children=e.children.map(s=>this.transformElement(s,r))),e}isDrawableElement(t){return ["path","circle","ellipse","line","rect","polygon","polyline","text","tspan","use"].includes(t)}};var C=class{transform(t,r={}){let{optimize:e=true,splitColors:s=false,splitStrokeWidths:i=false,fixedStrokeWidth:o=false,normalizeFillStroke:n=false,accessibility:d=true,removeComments:l=true,removeDuplicates:p=true,minifyPaths:g=false}=r,m=this.deepCloneAst(t),h=[],k=[],x=[];return e&&(this.applyOptimizations(m,{removeComments:l,removeDuplicates:p,minifyPaths:g&&!s}),h.push("optimization")),s&&(k=this.applySplitColors(m),h.push("split-colors")),i&&(x=this.applySplitStrokeWidths(m),h.push("split-stroke-widths")),o&&(this.applyFixedStrokeWidth(m),h.push("fixed-stroke-width")),n&&(this.applyFillStrokeNormalization(m),h.push("normalize-fill-stroke")),d&&(this.applyAccessibility(m),h.push("accessibility")),{ast:m,colorMappings:k,strokeWidthMappings:x,metadata:{originalColors:k.map(V=>V.originalColor),originalStrokeWidths:x.map(V=>V.originalStrokeWidth),optimizationApplied:e,features:h,hasClassAttributes:this.hasClassAttributes(m)}}}applySplitColors(t){let e=new S({generateClasses:true,colorPrefix:"color"}).apply(t.root);return t.root=e.processedElement,e.mappings}applyFixedStrokeWidth(t){let e=new b({onlyIfStrokePresent:false,preserveExisting:true}).apply(t.root);t.root=e.processedElement;}applyAccessibility(t){let e=new E({addRole:true,addTitle:true,addDesc:true,defaultRole:"img",preserveExisting:true}).apply(t.root);t.root=e.processedElement;}applyFillStrokeNormalization(t){let r=new y({enabled:true});t.root.children=r.apply(t.root.children);}applyOptimizations(t,r){r.removeDuplicates&&this.removeDuplicateElements(t),r.minifyPaths&&this.minifyPaths(t),this.removeEmptyGroups(t);}removeDuplicateElements(t){}minifyPaths(t){this.traverseElements(t.root,r=>{r.tag==="path"&&r.attributes.d&&(r.attributes.d=r.attributes.d.replace(/\s+/g," ").replace(/([MLHVCSQTAZ])\s+/gi,"$1").trim());});}removeEmptyGroups(t){let r=e=>(e.children=e.children.filter(r),!(e.tag==="g"&&e.children.length===0&&!e.content));t.root.children=t.root.children.filter(r);}traverseElements(t,r){r(t),t.children.forEach(e=>this.traverseElements(e,r));}isValidColor(t){return !t||t==="none"||t==="transparent"||t==="currentColor"?false:/^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/.test(t)||/^rgba?\(.+\)$/.test(t)||/^hsla?\(.+\)$/.test(t)}deepCloneAst(t){return {...t,root:this.deepCloneElement(t.root)}}deepCloneElement(t){return {...t,attributes:{...t.attributes},children:t.children.map(r=>this.deepCloneElement(r))}}hasClassAttributes(t){let r=e=>e.attributes.class||e.attributes.className?true:e.children.some(s=>r(s));return r(t.root)}applySplitStrokeWidths(t){let e=new v({generateClasses:true,strokeWidthPrefix:"strokeWidth"}).apply(t.root);return t.root=e.processedElement,e.mappings}};var w=class{constructor(){c(this,"parser");c(this,"transformer");this.parser=new f,this.transformer=new C;}extractColors(t){let r=this.parser.parse(t),e=this.parser.extractColors(r);return Array.from(new Set(e.map(s=>s.value))).sort()}validate(t){let r=[];try{let e=this.parser.parse(t);e.root||r.push("No root SVG element found"),!e.viewBox&&!e.width&&!e.height&&r.push("SVG should have viewBox or width/height attributes");}catch(e){r.push(e instanceof Error?e.message:String(e));}return {valid:r.length===0,errors:r}}async convert(t,r,e){try{let s=this.parser.parse(t),i=this.transformer.transform(s,r.transformation);return {...await new e(r.generator).generate(i),metadata:i.metadata}}catch(s){throw new Error(`SVG conversion failed: ${s instanceof Error?s.message:String(s)}`)}}};exports.SVGFusion=w;