Skip to content

Instantly share code, notes, and snippets.

@scottmas
Created February 26, 2019 16:11
Show Gist options
  • Save scottmas/1f662871ed60043bb14a9af4a3fcd3b0 to your computer and use it in GitHub Desktop.
Save scottmas/1f662871ed60043bb14a9af4a3fcd3b0 to your computer and use it in GitHub Desktop.
Hack to support vector-effect: non-scaling-stroke
/****
*
* A super clunky svg file to react component converter very similar in spirit to the somewhat popular svgr library (https://www.npmjs.com/package/svgr/v/1.4.0)
* But we have the additional requirement of needing to scale the svg while __preserving__ the stroke width. And unfortunately
* react-native-svg does not support the only config value that would enable this. Namely, the property `vector-effect="non-scaling-stroke"`.
* Follow the issue here: https://github.com/react-native-community/react-native-svg/issues/885
* Eventually, once the issue is resolved, we should move to svgr and simply use the svg `scale` property
*/
const path = require("path");
const cheerio = require("cheerio");
const _ = require("lodash");
const fs = require("fs");
const filename = process.argv[2];
if (!path.isAbsolute(filename)) {
throw new Error("Must pass absolute path");
}
if (path.extname(filename).toLowerCase() !== ".svg") {
throw new Error("File must be an svg file");
}
const svgString = String(fs.readFileSync(filename));
const START_DELIM = "STARRRRRRTTTT";
const END_DELIM = "ENDDDDDDDDD";
const Y_SCALE = "yScaleFactor";
const X_SCALE = "xScaleFactor";
const $ = cheerio.load(svgString, {
xmlMode: true,
recognizeSelfClosing: true,
lowerCaseTags: false,
lowerCaseAttributeNames: false
});
$("*").each(function(i, elem) {
elem.tagName = elem.tagName[0].toUpperCase() + elem.tagName.slice(1);
elem.attribs = Object.keys(elem.attribs).reduce((acc, attr) => {
let val = elem.attribs[attr];
if (attr === "width" || attr === "x") {
val = `\${${X_SCALE} * ${val}}`;
}
if (attr === "height" || attr === "y") {
val = `\${${Y_SCALE} * ${val}}`;
}
if (attr === "viewBox") {
val = val
.split(" ")
.map((d, i) => {
const scale = i % 2 === 0 ? X_SCALE : Y_SCALE;
return `\${${d} * ${scale}}`;
})
.join(" ");
}
if (attr === "transform") {
val = val
.split(/(\(.+?\))/)
.filter(a => a)
.map((d, i, arr) => {
if (i % 2 === 0) {
return d;
}
const transformType = arr[i - 1];
if (transformType === "matrix") {
console.error("matrix transforms are not supported. Too tricky for my blood...");
}
if (transformType === "rotate") {
const points = d.substr(1, d.length - 2).split(" ");
return `(${points[0]} \${${points[1]} * ${X_SCALE}} \${${points[2] || 0} * ${Y_SCALE}})`;
}
if (transformType === "scale") {
const points = d.substr(1, d.length - 2).split(" ");
return `(\${${points[0]} * ${X_SCALE}} \${${points[1] || points[0]} * ${Y_SCALE}})`;
}
if (transformType === "skewX") {
const points = d.substr(1, d.length - 2).split(" ");
return `(\${${points[0]} * ${X_SCALE}})`;
}
if (transformType === "skewY") {
const points = d.substr(1, d.length - 2).split(" ");
return `(\${${points[0]} * ${Y_SCALE}})`;
}
return d;
})
.join("");
}
if (attr === "d") {
const strings = val.split(/([a-zA-Z])/).filter(a => a);
const newDirectives = strings
.map((str, i) => {
if (i % 2 === 0) {
return str;
}
const directive = strings[i - 1];
if (directive.toLowerCase() === "v") {
return `\${${Y_SCALE} * ${str}}`;
}
if (directive.toLowerCase() === "h") {
return `\${${X_SCALE} * ${str}}`;
}
const newStrVal = str
.split(" ")
.map((num, i) => {
const scale = i % 2 === 0 ? X_SCALE : Y_SCALE;
return `\${${scale} * ${num}}`;
})
.join(" ");
return newStrVal;
})
.join("");
val = newDirectives;
}
acc[_.camelCase(attr)] = `${START_DELIM}${val}${END_DELIM}`;
return acc;
}, {});
});
const reactComponentStr = $.html()
.replace(new RegExp(`"${START_DELIM}`, "g"), "{`")
.replace(new RegExp(`${END_DELIM}"`, "g"), "`}");
const basename = path.basename(filename).replace(/\.svg/i, "");
const componentStr = `
/** STOP! Do not edit this file. Instead generate it by running
* \`node generate-svg-component-from-file.js ${filename}\`
*/
import React from "react";
import Svg,{ Circle, Ellipse, G, Text, TSpan, TextPath, Path, Polygon, Polyline, Line, Rect, Use, Image, Symbol, Defs, LinearGradient, RadialGradient, Stop, ClipPath, Pattern, Mask } from "react-native-svg";
export function ${basename}({${X_SCALE}, ${Y_SCALE}}){
return (
${reactComponentStr}
)
}
`;
fs.writeFileSync(`${path.dirname(filename)}/${basename}.js`, componentStr);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment