Skip to content

Instantly share code, notes, and snippets.

@obono
Last active April 11, 2023 14:10
Show Gist options
  • Save obono/fb07c4d8bb348b1f5a98a52f9dbbe71c to your computer and use it in GitHub Desktop.
Save obono/fb07c4d8bb348b1f5a98a52f9dbbe71c to your computer and use it in GitHub Desktop.
A bespoke SVG exporter to JPEG or PNG
/*
* Bespoke SVG exporter
*
* Copyright (c) 2023 OBONO
*
* Released under the MIT license.
* see https://opensource.org/licenses/MIT
*/
const { XMLParser, XMLBuilder } = require('fast-xml-parser');
const { createCanvas, Image } = require('canvas');
const { format } = require('util');
const fs = require('fs');
const mimeTypePng = 'image/png';
const mimeTypeJpeg = 'image/jpeg';
function interpretColor(color) {
const re = /^[0-9A-Za-z]{3,20}$/;
if (color != null && re.test(color)) {
if (color === 'random') {
return '#' + Math.floor((1 + Math.random()) * 0x1000000).toString(16).slice(-6);
}
const re2 = /^[0-9A-Fa-f]+$/;
const len = color.length;
if (re2.test(color) && (len == 3 || len == 6)) {
return '#' + color;
}
return color;
}
return undefined;
}
function applyStyle(style, id, attr, params) {
const color = interpretColor(params[id + '.' + attr]);
if (color != null) {
style = style.replace(new RegExp(attr + ':[^;]+;'), attr + ':' + color + ';');
}
return style;
}
function doProcedure(filePath, params, mimeType, callback) {
// Load the base SVG file
const xmlOption = { ignoreAttributes: false };
const parser = new XMLParser(xmlOption);
const svgStr = fs.readFileSync(filePath, 'utf-8').toString();
const svgObj = parser.parse(svgStr);
// Transform
const size = Number(params.size);
let width = Number(svgObj.svg['@_width']);
let height = Number(svgObj.svg['@_height']);
if (size > 0 && size <= 512) {
if (width >= height) {
height *= size / width;
width = size;
} else {
width *= size / height;
height = size;
}
svgObj.svg['@_width'] = width;
svgObj.svg['@_height'] = height;
}
let rotate = (params.rotate === 'random') ? Math.random() * 360 : Number(params.rotate);
if (rotate) {
const centerX = width / 2;
const centerY = height / 2;
svgObj.svg['@_transform'] = format('translate(%f,%f) rotate(%f) translate(-%f,-%f)',
centerX, centerY, rotate, centerX, centerY);
}
// Change colors
for (let i = 0; i < svgObj.svg.g.length; i++) {
const g = svgObj.svg.g[i];
const id = g['@_id'];
let style = g['@_style'];
style = applyStyle(style, id, 'fill', params);
style = applyStyle(style, id, 'stroke', params);
g['@_style'] = style;
}
// Draw & Export
const canvas = createCanvas(width, height);
const ctx = canvas.getContext('2d');
const image = new Image();
image.onload = () => {
let color = interpretColor(params.bg);
if (color == null && mimeType === mimeTypeJpeg) {
color = 'white';
}
if (color != null) {
ctx.fillStyle = color;
ctx.fillRect(0, 0, width, height);
}
ctx.drawImage(image, 0, 0);
const exportOption = {
compressionLevel: 9,
filters: canvas.PNG_FILTER_NONE,
quality: 0.95
};
callback(canvas.toBuffer(mimeType, exportOption));
}
image.onerror = (err) => {
console.log(err);
callback(null);
}
const builder = new XMLBuilder(xmlOption);
image.src = Buffer.from(builder.build(svgObj));
}
exports.doProcedure = doProcedure;
exports.mimeTypePng = mimeTypePng;
exports.mimeTypeJpeg = mimeTypeJpeg;
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
const fs = require('fs');
const bse = require('./bespoke-svg-exporter');
const params = {
'size': 256,
'rotate': 'random',
'bg': 'random',
'body.fill': 'random',
'head.fill': 'random',
'line.stroke': 'random',
};
bse.doProcedure('obono.svg', params, bse.mimeTypePng, (data) => {
(async () => {
fs.writeFile('/tmp/obono.png', data, (err) => {
if (err) throw err;
});
})();
});

Bespoke SVG exporter

A bespoke SVG exporter to JPEG or PNG.

How to use

This program depends on following library.

  • canvas@2.9.3
  • fast-xml-parser@4.0.9

Therefore, you have to install these before you use this.

npm install canvas fast-xml-parser

Call doProcedure() function with 4 arguments.

  • arg 1: the file path of the source SVG file.
    • The SVG data must have <g> tags with 'id' attribute as child elements of root <svg> tag.
  • arg 2: the parameters to customize.
    • 'size': 1~512
    • 'rotate': 0~360 or 'random'
    • 'bg': color name, hexadecimal color code or 'random'
    • '{id}.fill': color name, hexadecimal color code or 'random'
    • '{id}.stroke': color name, hexadecimal color code or 'random'
  • arg 3: the format to export.
    • Use mimeTypePng or mimeTypeJpeg.
  • arg 4: the function of the post process.
    • The bytes array of the exported image is set as an argument.

License

MIT License

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment