UNPKG

fontello-batch-cli

Version:
337 lines (303 loc) 8.41 kB
// Cleanup svg to be useful for font generation: apply transforms, merge paths and clear everything else // 'use strict'; var XMLDOMParser = require('xmldom').DOMParser; var _ = require('lodash'); var SvgPath = require('svgpath'); var quietTags = {}; var notQuietAtts = {}; // Ok to skip this attrs ['desc', 'title', 'metadata', 'defs'].forEach(function(key) { quietTags[key] = true; }); // Should warn on this attrs skip [ 'requiredFeatures', 'requiredExtensions', 'systemLanguage', 'xml:base', 'xml:lang', 'xml:space', 'onfocusin', 'onfocusout', 'onactivate', 'onclick', 'onmousedown', 'onmouseup', 'onmouseover', 'onmousemove', 'onmouseout', 'onload', 'alignment-baseline', 'baseline-shift', 'clip', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cursor', 'direction', 'display', 'dominant-baseline', 'enable-background', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'glyph-orientation-horizontal', 'glyph-orientation-vertical', 'image-rendering', 'kerning', 'letter-spacing', 'lighting-color', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'overflow', 'pointer-events', 'shape-rendering', 'stop-color', 'stop-opacity', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'text-anchor', 'text-decoration', 'text-rendering', 'unicode-bidi', 'visibility', 'word-spacing', 'writing-mode', 'class', 'style', 'externalResourcesRequired', 'pathLength', 'externalResourcesRequired' ].forEach(function(key) { notQuietAtts[key] = true; }); /** * Removing tags which can't be converted. * * @param node * @param ignoredTags Hash with ignored tags * @param ignoredAttrs Hash with ignored attributes * @param parentTransforms Text with parent transforms * @param path * @returns {{path: String with contains merged path, ignoredTags: *, ignoredAttrs: *, guaranteed: boolean}} */ function processTree(node, ignoredTags, ignoredAttrs, parentTransforms, path) { var guaranteed = true; _.each(node.childNodes, function(item) { // If item not Node - skip node . Example: #text - text node, comments if (item.nodeType !== 1) { return; } // Quiet ignored tags if (quietTags[item.nodeName]) { return; } var transforms = item.getAttribute('transform') ? parentTransforms + ' ' + item.getAttribute('transform') : parentTransforms; // Parse nested tags if (item.nodeName === 'g') { var result = processTree( item, ignoredTags, ignoredAttrs, transforms, path ); path = result.path; guaranteed = guaranteed && result.guaranteed; } // Get d from supported tag, else return var d = ''; switch (item.nodeName) { case 'path': d = item.getAttribute('d'); break; case 'g': break; default: ignoredTags[item.nodeName] = true; return; } var transformedPath = new SvgPath(d).transform(transforms).toString(); if (path !== '' && transformedPath !== '') { guaranteed = false; } path += transformedPath; // Check not supported attributes _.each(item.attributes, function(item) { if (notQuietAtts[item.nodeName]) { guaranteed = false; ignoredAttrs[item.nodeName] = true; } }); }); return { path, ignoredTags, ignoredAttrs, guaranteed }; } /** * Returns coordinates for svg * * @param svg * @returns {{x: (string|number), * y: (string|number), * width: (string), * height: (string)}} */ function getCoordinates(svg) { // getting viewBox values array var viewBoxAttr = svg.getAttribute('viewBox'); var viewBox = _.map((viewBoxAttr || '').split(/(?: *, *| +)/g), function( val ) { return parseFloat(val); }); // If viewBox attr has less than 4 digits it's incorrect if (viewBoxAttr && viewBox.length < 4) { return { error: new Error('Svg viewbox attr has less than 4 params') }; } // getting base parameters var attr = {}; _.forEach(['x', 'y', 'width', 'height'], function(key) { var val = svg.getAttribute(key); // TODO: remove and do properly // Quick hack - ignore values in %. There can be strange cases like // `width="100%" height="100%" viewbox="0 0 1000 1000"` if (val.length && val[val.length - 1] !== '%') { attr[key] = parseFloat(svg.getAttribute(key)); } }); if (viewBox[2] < 0 || viewBox[3] < 0 || attr.width < 0 || attr.height < 0) { return { error: new Error('Svg sizes can`t be negative') }; } var result = { x: attr.x || 0, y: attr.y || 0, width: attr.width, height: attr.height, error: null }; if (!viewBoxAttr) { // Only svg width & height attrs are set if (result.width && result.height) { return result; } // viewBox not set and attrs not set result.error = new Error( 'Not implemented yet. There is no width or height' ); // TODO: Need calculate BBox return result; } /*if ( (result.x !== 0 && viewBox[0] !== 0 && result.x !== viewBox[0]) ) { result.error = new Error('Not implemented yet. Svg attr x not equals viewbox x'); // TODO: Need transform return result; } if ( (result.y !== 0 && viewBox[1] !== 0 && result.y !== viewBox[1]) ) { result.error = new Error('Not implemented yet. Svg attr y not equals viewbox y'); // TODO: Need transform return result; }*/ /*if (viewBox[0]) { result.x = viewBox[0]; } if (viewBox[1]) { result.y = viewBox[1]; }*/ // viewBox is set and attrs not set if (!result.width && !result.height) { result.width = viewBox[2]; result.height = viewBox[3]; return result; } return result; /*// viewBox and attrs are set and values on width and height are equals if (viewBox[2] === result.width && viewBox[3] === result.height) { return result; } // viewBox is set and one attr not set if (!result.width || !result.height) { result.error = new Error('Not implemented yet. Width and height must be set'); // TODO: Implement BBox. If width or height is setthan implement transform return result; } // viewBox and attrs are set, but have different sizes. Need to transform image result.error = new Error('Not implemented yet. Svg viewbox sizes are different with svg sizes'); // TODO: Implement transform return result;*/ } /** * * * @param xml * @returns {{d: "", width: number, height: number, x: number, y: number, ignoredTagsTags: Array, * ignoredAttrs: Array, error: null, guaranteed: boolean}} */ module.exports = function convert(sourceXml) { var error = null; var result = { d: '', width: 0, height: 0, x: 0, y: 0, ignoredTags: [], ignoredAttrs: [], error, guaranteed: false }; var xmlDoc = new XMLDOMParser({ errorHandler: { error(err) { error = err; }, fatalError(err) { error = err; } } }).parseFromString(sourceXml, 'application/xml'); if (error) { result.error = error; return result; } var svg = xmlDoc.getElementsByTagName('svg')[0]; var processed = processTree(svg, {}, {}, '', ''); var coords = getCoordinates(svg); if (coords.error) { result.error = coords.error; return result; } result.d = processed.path; result.width = coords.width; result.height = coords.height; result.x = coords.x; result.y = coords.y; result.guaranteed = processed.guaranteed; result.ignoredTags = _.keys(processed.ignoredTags); result.ignoredAttrs = _.keys(processed.ignoredAttrs); return result; };