
Research
Security News
Lazarus Strikes npm Again with New Wave of Malicious Packages
The Socket Research Team has discovered six new malicious npm packages linked to North Korea’s Lazarus Group, designed to steal credentials and deploy backdoors.
@iconify/tools
Advanced tools
Collection of functions for cleaning up and parsing SVG for Iconify.design project
This library is a collection of tools for importing, exporting and processing SVG images.
Its main purpose is to convert icon sets and fonts to Iconify JSON collections, but it can be used for other purposes.
First install it by running this command:
npm install @iconify/tools --save
Then you can use it in your Node.js files:
const tools = require('@iconify/tools');
Core of tools are SVG and Collections classes. All tools create or manipulate instances of SVG or Collection classes.
All tools are based on Promises. If you are not familiar with JavaScript Promises, do read up. There are many tutorials available online. Make sure you are reading something from 2016 or newer, not tutorials for old implementations.
SVG class represents one SVG image. It is a simple class, it does not manipulate anything, except for cleaning up junk code that could otherwise cause XML parser to fail.
You can find code in src/svg.js
Creating SVG instance is easy:
let svg = new tools.SVG('<svg ...></svg>');
That code will load SVG from string, extract image dimensions and clean up a bit, removing all junk image editors left behind.
SVG instance has multiple methods to get SVG as string:
<svg>
element.Then you can get dimensions:
Last method replaces content of SVG instance:
You can see usage examples in unit tests: tests/core/svg_test.js
Collection is a set of SVG instances.
You can find code in src/collection.js
To create Collection instance use this:
let collection = new tools.Collection();
That will create empty collection.
To clone another collection add collection as parameter to constructor:
let newCollection = new tools.Collection(oldCollection);
To add/remove items there are several methods:
where "svg" is instance of SVG class
To access any item use this:
There are other helpful functions:
Then there are main functions that are used to manipulate SVG instances in collection:
How to use forEach():
collection.forEach((svg, name) {
console.log('Found icon ' + name + ': ' + svg.toString());
});
How to use promiseAll() and promiseEach():
collection.promiseAll((svg, name) => {
return new Promise((fulfill, reject) {
// do stuff to "svg" variable
fulfill('Result for icon ' + name);
});
}).then(results => {
// Results of all promises as object. Key = icon name, value = result for that icon
Object.keys(results).forEach(name => {
console.log('Result for icon ' + name + ':', results[name]);
});
}).catch(err => {
console.error('Promise failed:', err);
});
promiseAll() and promiseEach() are almost identical. First argument is callback that returns Promise for one icon.
The only difference is promiseAll() runs all promises at the same time using Promise.all() function, promiseEach() runs promises one after another. It is better to use promiseEach() when dealing with large collections.
You can find examples throughout this library and unit tests. Everything in this library is based on promises.
There are several importers available. Some import one file, some import collections.
tools.ImportSVG('path-to-file.svg').then(svg => {
// SVG was imported
// Variable 'svg' is instance of SVG class
console.log(svg.toString());
}).catch(err => {
console.error(err);
});
tools.ImportDir('directory').then(collection => {
// Collection was imported
// Variable 'collection' is instance of Collection class
console.log('Found icons: ' + collection.keys().join(', '));
}).catch(err => {
console.error(err);
});
There are several options available for importing directory, options should be passed as second parameter to ImportDir function:
tools.ImportDir('directory', {
'include-subdirs': false,
ignoreFiles: ['bad-icon'],
keywordCallback: (file, filename) => 'prefix-' + file
}).then(collection => {
// Collection was imported
// Variable 'collection' is instance of Collection class
console.log('Found icons: ' + collection.keys().join(', '));
}).catch(err => {
console.error(err);
});
WebIcon format is one big SVG image that contains multiple images.
tools.ImportWebIcon('path-to-file.svg').then(collection => {
// Collection was imported
// Variable 'collection' is instance of Collection class
console.log('Found icons: ' + collection.keys().join(', '));
}).catch(err => {
console.error(err);
});
There are many popular glyph fonts, such as FontAwesome, that are not available as individual files. This importer will import SVG font as collection. It will not import keywords though for each icon - that is different for every collection and should be done separately.
tools.ImportFont('path-to-file.svg').then(collection => {
// Collection was imported
// Variable 'collection' is instance of Collection class
console.log('Found icons: ' + collection.keys().join(', '));
}).catch(err => {
console.error(err);
});
There are several options available for importing SVG font, options should be passed as second parameter to ImportFont function:
Keys for characterChanges and fontChanges objects are similar:
Each value can be a number or a function(oldValue) that should return new value.
Example:
tools.ImportFont('path-to-file.svg', {
fontChanges: {
height = height => Math.ceil(height / 16) * 16 // Round up height to 16px grid
},
characterChanges: {
f19c: { width: 1920 },
f0fc: { left: 64, width: 1600 },
}
}).then(collection => {
// Collection was imported
// Variable 'collection' is instance of Collection class
console.log('Found icons: ' + collection.keys().join(', '));
}).catch(err => {
console.error(err);
});
If you are going to crop images after import, there is no point in worrying about fixing characters. Crop will fix it (unless font is really badly messed up).
There are several exporters available that work with Collection or SVG instances:
Exports one SVG instance.
tools.ExportSVG(svg, 'filename.svg').then(() => {
console.log('Exported!');
}).catch(err => {
console.error(err);
});
tools.ExportDir(collection, 'directory').then(count => {
console.log('Exported ' + count + ' files');
}).catch(err => {
console.error(err);
});
This is main export function, it exports collection to JSON format used by Iconify.
tools.ExportJSON(collection, 'filename.json').then(json => {
console.log('Exported collection. JSON data: ' + JSON.stringify(json));
}).catch(err => {
console.error(err);
});
You can also export images to PNG.
Important: this function requires PhantomJS to be installed and accessible from command line!
tools.ExportPNG(svg, 'filename.png', {
// options object here
}).then(() => {
console.log('Exported filename.png');
}).catch(err => {
console.error(err);
});
Options object has following properties:
Most functions require SVG to be optimized. Therefore before doing anything else, you should optimize icon.
This module optimizes SVG images, removing any unnecessary code and cleaning up stuff. It does not merge shapes to avoid potential problems with images that rely on specific order of shapes.
tools.SVGO(svg).then(svg => {
// svg variable is the same as in argument. Optimizer does not create new instance of SVG class
console.log('Optimized SVG');
}).catch(err => {
console.error(err);
});
Want to optimize entire collection? Use collection's promiseAll function:
collection.promiseAll(svg => tools.SVGO(svg)).then(results => {
console.log('Optimized entire collection');
}).catch(err => {
console.error(err);
});
This module crops SVG icons imported from fonts.
Important! This function requires PhantomJS to be installed. See http://phantomjs.org/
How to crop one image:
tools.Crop(svg).then(svg => {
console.log('Cropped SVG: ' + svg.toString());
}).catch(err => {
console.error(err);
});
How to crop entire collection:
tools.Crop(collection).then(collection => {
console.log('Cropped ' + svg.length() + ' images');
}).catch(err => {
console.error(err);
});
How does crop work? By drawing SVG on canvas and checking all 4 sides for transparent pixels. First it appends space on all sides of SVG, until all sides have nothing but empty pixels. Then it slowly cuts all sides until it reaches pixels that aren't empty. Process doesn't take long because script checks pixels in bulk by zooming in.
When it crops SVG or Collection instance (like in examples above), each SVG instance receives additional properties that you might want to use later: _cropData. _cropData is an object with list of cropped edges: left, top, right, bottom.
Node.js is not a browser, it does not support canvas and cannot render SVG images. Because of that calculations are done in PhantomJS and script requires PhantomJS to be installed.
This module checks elements used in SVG image, changes stylesheet into inline style and removes useless attributes.
Stylesheet parser is very simple, it parses only basic stylesheets that are generated by some image editors. If SVG contains fishy elements that aren't vector images, such as bitmaps or scripts, it will reject with error message.
Important! Before running this function you need to optimize SVG image using tools.SVGO()
Usage:
tools.Tags(svg).then(svg => {
console.log('Cleaned up tags in SVG: ' + svg.toString());
}).catch(err => {
console.error(err);
});
This function is used for optimizing SVG images that contain a lot of junk code.
If you don't know if SVG is colored or monotone or maybe doesn't have any colors at all, this tool will find all colors used in SVG and return you array of used colors.
Result is object with 2 properties:
Usage:
tools.GetPalette(svg).then(result => {
console.log('Colors used in SVG: ' + result.colors.join(', ');
if (result.notices.length) {
result.notices.forEach(notice => console.warn(notice));
}
}).catch(err => {
console.error(err);
});
Using it with collection:
collection.promiseAll(svg => tools.GetPalette(svg)).then(result => {
Object.keys(results).forEach(key => {
console.log('Colors found in image ' + key + ': ' + result[key].colors.join(', ');
});
}).catch(err => {
console.error(err);
});
This tool can be used to change colors in SVG or add colors to shapes that are missing color values.
tools.ChangePalette(svg, {
'#ff8000': '#0080ff',
'#123': '#234'
}).then(svg => {
// Variable "svg" in result is the same as in first parameter of ChangePalette.
// Function changes existing SVG instance
console.log('Changed palette');
}).catch(err => {
console.error(err);
});
Code above will change #ff8000 to #0080ff and #123 to #234.
But there are more options:
tools.ChangePalette(svg, 'red').then(svg => {
console.log('Added palette to elements without palette');
}).catch(err => {
console.error(err);
});
Code above will add color "red" to all elements that do not have color. It is identical to this:
tools.ChangePalette(svg, {
add: 'red'
}).then(svg => {
console.log('Added palette to elements without palette');
}).catch(err => {
console.error(err);
});
You can also change all colors by using keyword "default":
tools.ChangePalette(svg, {
default: 'green'
}).then(svg => {
console.log('Changed all colors to green');
}).catch(err => {
console.error(err);
});
Or you can combine all those attributes:
tools.ChangePalette(svg, {
default: 'green',
add: '#000',
'#123': '#234
}).then(svg => {
console.log('Changed all colors to green, except for #123 that was changed to #234, added black color to elements without color');
}).catch(err => {
console.error(err);
});
Primary use of this tool in Iconify.design is to replace all colors in monotone with "currentColor" keyword and add it to shapes that are missing color values:
collection.promiseAll(svg => tools.ChangePalette(svg, {
default: 'primaryColor',
add: 'primaryColor'
})).then(() => {
console.log('Changed color to primaryColor');
}).catch(err => {
console.error(err);
});
If you are doing stroke animations, you might need to index shapes (add unique class names for them to apply different animations to different shapes) and get length of those shapes.
tools.IndexShapes adds custom attribute to all shapes:
tools.IndexShapes(svg, {
// Attribute to set
shapeAttribute: 'data-shape-index',
// Attribute value. {index} is replaced with index, incrementing with each shape
shapeAttributeValue: '{index}',
}).then(shapesCount => {
console.log('Added data-shape-index attribute to ' + shapesCount + ' shapes.');
}).catch(err => {
console.error(err);
});
You can also get those shapes by setting option returnNodes to true. Instead of number of shapes Promise will return array of nodes. Each node is cheerio node object. See cheerio documentation for details.
tools.ShapeLengths counts lengths of all shapes. It also returns all nodes, so you can change shapes (add/remove attributes or class name):
tools.ShapeLength(svg).then(results => {
console.log('Found ' + results.length + ' shapes:');
results.forEach(result => {
console.log('Shape ' + result.$node.get(0).tagName + ' has length of ' + result.length);
});
}).catch(err => {
console.error(err);
});
In directory "sample" you will find a sample script that parses the directory of SVG images. It optimizes images, exports them as optimized SVG images and as JSON collection.
If you need any other tool to help with custom SVG icon sets development, suggest it by opening an issue on GitHub repository.
Library is released with MIT license.
© 2016 - 2019 Vjacheslav Trushkin
FAQs
Collection of functions for cleaning up and parsing SVG for Iconify project
We found that @iconify/tools demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
The Socket Research Team has discovered six new malicious npm packages linked to North Korea’s Lazarus Group, designed to steal credentials and deploy backdoors.
Security News
Socket CEO Feross Aboukhadijeh discusses the open web, open source security, and how Socket tackles software supply chain attacks on The Pair Program podcast.
Security News
Opengrep continues building momentum with the alpha release of its Playground tool, demonstrating the project's rapid evolution just two months after its initial launch.