var SVGParser = require('./core'); SVGParser.implement({ findLinkedAttributes: function(element, callback){ var self = this; var cb = function(result){ var attributes = element.attributes; for (var i = 0, l = attributes.length; i < l; i++){ var attribute = attributes[i]; result[attribute.nodeName] = attribute.nodeValue; } if (element.childNodes.length > 0) result.container = element; callback.call(self, result); }; var href = element.getAttribute('xlink:href') || element.getAttribute('href'); if (!href){ cb.call(this, {}); return; } this.findByURL(element.ownerDocument, href, function(parent){ if (!parent) cb.call(this, {}); else this.findLinkedAttributes(parent, function(parentResult){ cb.call(this, parentResult); }); }); }, getBBox: function(graphic){ var path = graphic.getPath && graphic.getPath(); return path ? path.measure() : graphic; }, parseBBLength: function(value, bbox, dimension){ value = /%/.test(value) ? parseFloat(value) / 100 : parseFloat(value); if (dimension == 'x') return (bbox.left || 0) + (bbox.width || 0) * value; if (dimension == 'y') return (bbox.top || 0) + (bbox.height || 0) * value; }, getGradientStops: function(element, styles){ var stops = null, node = element.firstChild; while (node){ if (node.nodeName == 'stop'){ var stopStyles = this.parseStyles(node, styles); var color = this.parseColor(stopStyles['stop-color'] || 'black', stopStyles['stop-opacity'], stopStyles), offset = node.getAttribute('offset'); if (color && offset){ offset = /%/.test(offset) ? parseFloat(offset) / 100 : parseFloat(offset); if (offset < 0) offset = 0; if (offset > 1) offset = 1; if (!stops) stops = {}; stops[offset.toFixed(4)] = color; } } node = node.nextSibling; } return stops; }, radialGradientFill: function(element, styles, target, x, y){ this.findLinkedAttributes(element, function(attrs){ if (!attrs.container) return; var stops = this.getGradientStops(attrs.container, styles); if (!stops) return; // TODO: Transform var cx = attrs.cx || '50%', cy = attrs.cy || '50%', rx = attrs.r || '50%', ry, fx = attrs.fx || cx, fy = attrs.fy || cy; if (attrs['gradientUnits'] == 'userSpaceOnUse'){ cx = this.parseLength(cx, styles, 'x') - x; cy = this.parseLength(cy, styles, 'y') - y; rx = ry = this.parseLength(rx, styles); fx = this.parseLength(fx, styles, 'x') - x; fy = this.parseLength(fy, styles, 'y') - y; } else { var bb = this.getBBox(target); cx = this.parseBBLength(cx, bb, 'x'); cy = this.parseBBLength(cy, bb, 'y'); rx = this.parseBBLength(rx, bb, 'x'); ry = rx * (bb.height / bb.width); fx = this.parseBBLength(fx, bb, 'x'); fy = this.parseBBLength(fy, bb, 'y'); } target.fillRadial(stops, fx, fy, rx, ry, cx, cy); }); }, linearGradientFill: function(element, styles, target, x, y){ this.findLinkedAttributes(element, function(attrs){ if (!attrs.container) return; var stops = this.getGradientStops(attrs.container, styles); if (!stops) return; var x1 = attrs.x1 || 0, y1 = attrs.y1 || 0, x2 = attrs.x2 || '100%', y2 = attrs.y2 || 0; // TODO: Transform if (attrs['gradientUnits'] == 'userSpaceOnUse'){ x1 = this.parseLength(x1, styles, 'x') - x; y1 = this.parseLength(y1, styles, 'y') - y; x2 = this.parseLength(x2, styles, 'x') - x; y2 = this.parseLength(y2, styles, 'y') - y; } else { x1 = /%/.test(x1) ? parseFloat(x1) / 100 : parseFloat(x1); y1 = /%/.test(y1) ? parseFloat(y1) / 100 : parseFloat(y1); x2 = /%/.test(x2) ? parseFloat(x2) / 100 : parseFloat(x2); y2 = /%/.test(y2) ? parseFloat(y2) / 100 : parseFloat(y2); // If both points are close to the opposite edges, use rotation angle instead var closeToEdge = (x1 > -0.01 && x1 < 0.01 && x2 > 0.99 && x2 < 1.01) || (x1 > 0.99 && x1 < 1.01 && x2 > -0.01 && x2 < 0.01) || (y1 > -0.01 && y1 < 0.01 && y2 > 0.99 && y2 < 1.01) || (y1 > 0.99 && y1 < 1.01 && y2 > -0.01 && y2 < 0.01); if (closeToEdge){ var angle = Math.atan2(y1 - y2, x2 - x1) * 180 / Math.PI; target.fillLinear(stops, angle); return; } // TODO: Always use rotation angle, but offset stops instead to adjust // TODO2: Never use rotation angle because measuring is deprecated var bb = this.getBBox(target); x1 = this.parseBBLength(x1, bb, 'x'); y1 = this.parseBBLength(y1, bb, 'y'); x2 = this.parseBBLength(x2, bb, 'x'); y2 = this.parseBBLength(y2, bb, 'y'); } target.fillLinear(stops, x1, y1, x2, y2); }); }, patternFill: function(element, styles, target, x, y){ // TODO: If the pattern is an image, fillImage } });