/* * Raphael SVG Import Classic Copyright (c) 2015 Chris Chang, Ingvar Stepanyan * Original Raphael SVG Import Copyright (c) 2009 Wout Fierens * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license. * */ if (!Raphael && require){ var Raphael = require('raphael'); } Raphael.fn.importSVG = function (svgXML, options) { "use strict"; var myNewSet = this.set(); var groupSet = {}; var defaultTextAttr = { // stroke: "none" "text-anchor": "start" // raphael defaults to "middle" }; // minimal polyfill for String.trim() // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trim#Polyfill var trim = function(string){ return string.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); }; // polyfill for Array.forEach var forEach = Function.prototype.bind && Array.prototype.forEach ? Function.prototype.call.bind(Array.prototype.forEach) : function (arr, callback) { for (var i = 0, length = arr.length; i < length; i++) { callback(arr[i], i, arr); } }; this.parseElement = function(elShape) { // skip text nodes if (elShape.nodeType === 3) { return; } var attr = {"stroke": "none", "stroke-width": 1, "fill":"black"}, shapeName = elShape.nodeName, i, n, key, shape; if (elShape.attributes) { for (i = 0, n = elShape.attributes.length; i < n; i++) { attr[elShape.attributes[i].name] = elShape.attributes[i].value; } } switch(shapeName) { case "svg": case "g": var groupId = elShape.getAttribute('id'); var groupClass = elShape.getAttribute('class'); if (groupId || groupClass) { var elShapeChildren = elShape.childNodes; for (i = 0, n = elShapeChildren.length; i < n; i++) { var elShapeChild = elShapeChildren[i]; if (elShapeChild.nodeType === 3) { // skip text nodes continue; } if (groupId) { // FIXME data-* attrs are not part of the SVG spec elShapeChild.setAttribute('data-svg-group', groupId); } if (groupClass) { elShapeChild.setAttribute('class', (elShapeChild.getAttribute('class') || '') + ' ' + groupClass); } } } var thisGroup = this.set(); for (i = 0, n = elShape.childNodes.length; i < n; i++) { thisGroup.push(this.parseElement(elShape.childNodes.item(i))); } // handle transform attribute if (attr.transform){ var match = /translate\(([^,]+),([^,]+)\)/.exec(attr.transform); if (match.length == 3){ thisGroup.translate(match[1], match[2]); } } // handle display=none if (attr.display === "none") { thisGroup.hide(); } // hold onto thisGroup just in case if (groupId && elShape.childNodes.length) { groupSet[groupId] = thisGroup; } return; case "rect": if (attr.rx && attr.ry) { attr.r = (+(attr.rx || 0) + (+(attr.ry || 0))) / 2; delete attr.rx; delete attr.ry; } else { attr.r = attr.rx || attr.ry || 0; delete attr.rx; delete attr.ry; } /* falls through */ case "circle": case "ellipse": shape = this[shapeName](); break; case "path": shape = this.path(attr.d); delete attr.d; break; case "polygon": shape = this.polygon(attr); break; case "polyline": shape = this.polyline(attr); break; case "line": shape = this.line(attr); break; case "image": shape = this.image(); break; case "text": for (key in defaultTextAttr){ if (!attr[key] && defaultTextAttr.hasOwnProperty(key)) { attr[key] = defaultTextAttr[key]; } } shape = this.text(attr.x, attr.y, elShape.text || elShape.textContent || elShape.innerText || elShape.outerText); break; default: var elSVG = elShape.getElementsByTagName("svg"); if (elSVG.length){ elSVG[0].normalize(); this.parseElement(elSVG[0]); } return; } // apply matrix transformation var matrix = attr.transform; if (matrix) { // strip `matrix(...)` text and then tokenize matrix = matrix.substring(7, matrix.length-1).split(' '); // cast matrix elements, parseFloat don't care if there's commas or not for (var idx in matrix) { matrix[idx] = parseFloat(matrix[idx]); } var m = shape.matrix; m.add.apply(m, matrix); // this seems like a very odd step: shape.transform(m.toTransformString()); delete attr.transform; } // Raphael throws away the `style` attribute; re-interpret it. if (attr.style) { var styleBits = attr.style.split(';'), styleBitBits; for (i = 0; i < styleBits.length; i++) { styleBitBits = styleBits[i].split(':'); key = trim(styleBitBits[0]); if (key) { attr[key] = trim(styleBitBits[1]); } } } shape.attr(attr); // Adds dataattributes to raphael for (var attributeIndex = 0; attributeIndex < elShape.attributes.length; attributeIndex++) { var attribute = elShape.attributes[attributeIndex]; // Checks if attribute is an data-attribute if(attribute.name.indexOf('data-') === 0) { shape.data(attribute.name.replace(/data-/, ''), attribute.value); } } // copy group id var shapeClass = elShape.getAttribute('class'); if (shapeClass) { shape.node.setAttribute('class', (shape.node.getAttribute('class') || '') + ' ' + shapeClass); } // FIXME data-* attrs are not part of the SVG spec shape.node.setAttribute('data-svg', shapeName); var group = elShape.getAttribute('data-svg-group'); if (group) { // FIXME data-* attrs are not part of the SVG spec shape.node.setAttribute('data-svg-group', group); } var nodeID = elShape.getAttribute("id"); if (nodeID) { shape.node.id = nodeID; } if (options && options.parseElement) { shape = options.parseElement(shape, elShape); } if (shape) { myNewSet.push(shape); } return shape; }; this.parseElement(svgXML); // TODO add tests for svg style attr functionality var paper = this; forEach(svgXML.getElementsByTagName('style'), function (xmlStyle) { var domStyle = document.createElement('style'), css = xmlStyle.textContent || xmlStyle.text; domStyle.type = 'text/css'; document.head.appendChild(domStyle); var rules; if (domStyle.styleSheet) { domStyle.styleSheet.cssText = css; rules = domStyle.styleSheet.rules; } else { domStyle.appendChild(document.createTextNode(css)); rules = domStyle.sheet.cssRules; } forEach(rules, function (rule) { var style = rule.style, elements = document.querySelectorAll(rule.selectorText), attrs = {}; for (var name in Raphael._availableAttrs) { var value = style[name]; if (!value) continue; // fix for Chrome attrs[name] = typeof Raphael._availableAttrs[name] === 'number' ? parseFloat(value) : value; } forEach(elements, function (element) { paper.getById(element.raphaelid).attr(attrs); }); }); }); var groupsExist = false, x; for (x in groupSet){ groupsExist = true; break; } if (groupsExist) { myNewSet.groups = groupSet; } return myNewSet; }; Raphael.fn.line = function(attr){ var pathString = ["M", attr.x1, attr.y1, "L", attr.x2, attr.y2, "Z"]; delete attr.x1; delete attr.y1; delete attr.x2; delete attr.y2; return this.path(pathString); }; // extending raphael with a polygon function Raphael.fn.polygon = function(attr) { var pointString = attr.points; var poly = ['M'], point = pointString.split(' '); for(var i=0; i < point.length; i++) { var c = point[i].split(','); for(var j=0; j < c.length; j++) { var d = parseFloat(c[j]); if (!isNaN(d)) poly.push(d); } if (i === 0) poly.push('L'); } poly.push('Z'); delete attr.points; return this.path(poly); }; Raphael.fn.polyline = function(attr) { var pointString = attr.points; var poly = ['M'], point = pointString.split(' '); for(var i=0; i < point.length; i++) { var c = point[i].split(','); for(var j=0; j < c.length; j++) { var d = parseFloat(c[j]); if (!isNaN(d)) poly.push(d); } if (i === 0) poly.push('L'); } delete attr.points; return this.path(poly); };