Skip to content

Instantly share code, notes, and snippets.

@MrPaXe
Last active January 25, 2026 15:27
Show Gist options
  • Select an option

  • Save MrPaXe/232c9db06bcbcb9b85eaf10a23364b2d to your computer and use it in GitHub Desktop.

Select an option

Save MrPaXe/232c9db06bcbcb9b85eaf10a23364b2d to your computer and use it in GitHub Desktop.
SVG Converter to make svgs compatible with PixiJs
const fs = require("fs");
const path = require("path");
/**
* Converts CSS declarations to SVG attributes
* @param {string} decls
* @returns {string}
*/
function declsToAttrs(decls) {
return decls
.split(";")
.map((d) => {
const [prop, val] = d.split(":").map((s) => s?.trim());
if (prop && val) {
// Remove px from values as Pixi prefers raw numbers
const cleanVal = val.replace(/px/g, "");
return `${prop}="${cleanVal}"`;
}
return "";
})
.filter((s) => s !== "")
.join(" ");
}
/**
* Processes a single SVG file to make it compatible with PixiJS v8
* @param {string} filePath
*/
function fixSVGFile(filePath) {
if (!filePath.endsWith(".svg")) {
return;
}
const fileName = path.basename(filePath);
console.log(`Processing ${fileName}...`);
let content;
try {
content = fs.readFileSync(filePath, "utf8");
} catch (err) {
console.error(` Error reading file: ${err.message}`);
return;
}
const originalSize = content.length;
// 1. Extract styles from <style> blocks
const styleMatch = content.match(/<style[^>]*>([\s\S]*?)<\/style>/);
const styles = {};
if (styleMatch) {
const styleContent = styleMatch[1];
// Simple parser for Illustrator-style CSS
const rules = styleContent.split("}");
rules.forEach((rule) => {
const parts = rule.split("{");
if (parts.length === 2) {
const selectors = parts[0].trim().split(",");
const decls = parts[1].trim();
selectors.forEach((sel) => {
const className = sel.trim().replace(/^\./, "");
if (className) {
if (!styles[className]) {
styles[className] = "";
}
styles[className] += decls + ";";
}
});
}
});
}
// 2. Inline styles by replacing class="..." with actual attributes
content = content.replace(/class="([^"]+)"/g, (match, className) => {
const classes = className.split(/\s+/);
let attrs = "";
classes.forEach((c) => {
if (styles[c]) {
attrs += " " + declsToAttrs(styles[c]);
}
});
return attrs || match;
});
// 3. Clean up style blocks and other junk
content = content.replace(/<style[^>]*>[\s\S]*?<\/style>/g, "");
content = content.replace(/<defs>\s*<\/defs>/g, ""); // Remove empty defs
// 4. Remove Adobe Illustrator metadata and junk
content = content.replace(/xmlns:i="[^"]+"/g, "");
content = content.replace(/<i:pgf>[\s\S]*?<\/i:pgf>/g, "");
content = content.replace(/<i:view[\s\S]*?\/>/g, "");
content = content.replace(/<!-- Generator: Adobe Illustrator.*? -->/g, "");
content = content.replace(/i:[a-z]+="[^"]*"/g, "");
content = content.replace(/version="2\.1\.1"/g, 'version="1.1"');
// 5. Ensure viewBox exists (critical for PixiJS)
if (!content.includes("viewBox")) {
const widthMatch = content.match(/width="([^"]+)"/);
const heightMatch = content.match(/height="([^"]+)"/);
if (widthMatch && heightMatch) {
const width = widthMatch[1].replace("px", "");
const height = heightMatch[1].replace("px", "");
content = content.replace("<svg", `<svg viewBox="0 0 ${width} ${height}"`);
}
}
// 6. Fix dasharray (sometimes it's semicolon separated or has "px")
content = content.replace(/stroke-dasharray="([^"]+)"/g, (match, val) => {
const cleanVal = val.replace(/px/g, "").replace(/;/g, " ").trim();
return `stroke-dasharray="${cleanVal}"`;
});
// 7. Handle special case: isolation: isolate which Pixi doesn't support
content = content.replace(/isolation="isolate"/g, "");
if (content.length !== originalSize || content !== fs.readFileSync(filePath, "utf8")) {
fs.writeFileSync(filePath, content);
console.log(` Fixed ${fileName} (Size: ${originalSize} -> ${content.length})`);
} else {
console.log(` No changes needed for ${fileName}`);
}
}
// Main execution
const target = process.argv[2];
let filesToProcess = [];
if (target) {
const fullPath = path.resolve(target);
if (!fs.existsSync(fullPath)) {
console.error(`Error: Path not found: ${fullPath}`);
process.exit(1);
}
const stats = fs.statSync(fullPath);
if (stats.isFile()) {
filesToProcess.push(fullPath);
} else if (stats.isDirectory()) {
filesToProcess = fs.readdirSync(fullPath)
.filter((f) => f.endsWith(".svg"))
.map((f) => path.join(fullPath, f));
if (filesToProcess.length === 0) {
console.log(`No SVG files found in directory: ${fullPath}`);
}
} else {
console.error(`Error: Path is neither a file nor a directory: ${fullPath}`);
process.exit(1);
}
} else {
// Default behavior: process the maps directory
const mapsDir = __dirname;
if (fs.existsSync(mapsDir)) {
filesToProcess = fs.readdirSync(mapsDir)
.filter((f) => f.endsWith(".svg"))
.map((f) => path.join(mapsDir, f));
if (filesToProcess.length === 0) {
console.log(`No SVG files found in default maps directory: ${mapsDir}`);
}
} else {
console.error(`Error: Default maps directory not found: ${mapsDir}`);
console.log("Usage: node fixsvg.cjs [file-or-directory]");
process.exit(1);
}
}
filesToProcess.forEach(fixSVGFile);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment