var Class = require('../../core/class'); var Color = require('../../core/color'); var Node = require('./node'); var DOM = require('./dom'); var createElement = DOM.createElement; module.exports = Class(Node, { element_initialize: Node.prototype.initialize, initialize: function(tag){ this.element_initialize(tag); this.brushes = {}; this.fill(); this.stroke(); }, _place: function(){ if (this.parentNode){ this._injectBrush('fill'); this._injectBrush('stroke'); } else { this._ejectBrush('fill'); this._ejectBrush('stroke'); } return this; }, _injectBrush: function(type){ if (!this.parentNode) return; var brush = type == 'fill' ? this.fillBrush : this.strokeBrush; if (brush) this.parentNode.defs.appendChild(brush); }, _ejectBrush: function(type){ var brush = this[type + 'Brush']; if (brush && brush.parentNode) brush.parentNode.removeChild(brush); }, /* styles */ _createBrush: function(type, tag){ this._ejectBrush(type); var brush = createElement(tag); if (type == 'fill') this.fillBrush = brush; else this.strokeBrush = brush; var id = type + '-brush-e' + DOM.uniqueID(); brush.setAttribute('id', id); this._injectBrush(type); this.element.setAttribute(type, 'url(#' + id + ')'); return brush; }, _createGradient: function(type, style, stops){ var gradient = this._createBrush(type, style); var addColor = function(offset, color){ color = Color.detach(color); var stop = createElement('stop'); stop.setAttribute('offset', offset); stop.setAttribute('stop-color', color[0]); stop.setAttribute('stop-opacity', color[1]); gradient.appendChild(stop); }; // Enumerate stops, assumes offsets are enumerated in order // TODO: Sort. Chrome doesn't always enumerate in expected order but requires stops to be specified in order. if ('length' in stops) for (var i = 0, l = stops.length - 1; i <= l; i++) addColor(i / l, stops[i]); else for (var offset in stops) addColor(offset, stops[offset]); gradient.setAttribute('spreadMethod', 'reflect'); // Closer to the VML gradient this.element.removeAttribute('fill-opacity'); return gradient; }, _setColor: function(type, color){ this._ejectBrush(type); this[type + 'Brush'] = null; var element = this.element; if (color == null){ element.setAttribute(type, 'none'); element.removeAttribute(type + '-opacity'); } else { color = Color.detach(color); element.setAttribute(type, color[0]); element.setAttribute(type + '-opacity', color[1]); } }, fill: function(color){ if (arguments.length > 1) this.fillLinear(arguments); else this._setColor('fill', color); return this; }, fillRadial: function(stops, focusX, focusY, radiusX, radiusY, centerX, centerY){ var gradient = this._createGradient('fill', 'radialGradient', stops); gradient.setAttribute('gradientUnits', 'userSpaceOnUse'); if (focusX == null) focusX = (this.left || 0) + (this.width || 0) * 0.5; if (focusY == null) focusY = (this.top || 0) + (this.height || 0) * 0.5; if (radiusY == null) radiusY = radiusX || (this.height * 0.5) || 0; if (radiusX == null) radiusX = (this.width || 0) * 0.5; if (centerX == null) centerX = focusX; if (centerY == null) centerY = focusY; var ys = radiusY / radiusX; gradient.setAttribute('fx', focusX); gradient.setAttribute('fy', focusY / ys); gradient.setAttribute('r', radiusX); if (ys != 1) gradient.setAttribute('gradientTransform', 'scale(1,' + ys + ')'); gradient.setAttribute('cx', centerX); gradient.setAttribute('cy', centerY / ys); return this; }, fillLinear: function(stops, x1, y1, x2, y2){ var gradient = this._createGradient('fill', 'linearGradient', stops); if (arguments.length == 5){ gradient.setAttribute('gradientUnits', 'userSpaceOnUse'); } else { var angle = ((x1 == null) ? 270 : x1) * Math.PI / 180; var x = Math.cos(angle), y = -Math.sin(angle), l = (Math.abs(x) + Math.abs(y)) / 2; x *= l; y *= l; x1 = 0.5 - x; x2 = 0.5 + x; y1 = 0.5 - y; y2 = 0.5 + y; } gradient.setAttribute('x1', x1); gradient.setAttribute('y1', y1); gradient.setAttribute('x2', x2); gradient.setAttribute('y2', y2); return this; }, fillImage: function(url, width, height, left, top, color1, color2){ var pattern = this._createBrush('fill', 'pattern'); var image = createElement('image'); DOM.link(image, url); image.setAttribute('width', width); image.setAttribute('height', height); image.setAttribute('preserveAspectRatio', 'none'); // none, xMidYMid slice, xMidYMid meet if (color1 != null){ color1 = new Color(color1); if (color2 == null){ color2 = new Color(color1); color2.alpha = 0; } else { color2 = new Color(color2); } var r = (color1.red - color2.red) / (255 * 3), g = (color1.green - color2.green) / (255 * 3), b = (color1.blue - color2.blue) / (255 * 3), a = (color1.alpha - color2.alpha) / 3; var matrix = [ r, r, r, 0, color2.red / 255, g, g, g, 0, color2.green / 255, b, b, b, 0, color2.blue / 255, a, a, a, 0, color2.alpha ]; var filter = createElement('filter'); filter.setAttribute('id', 'testfilter' + this.uid); var cm = createElement('feColorMatrix'); cm.setAttribute('type', 'matrix'); cm.setAttribute('values', matrix.join(' ')); image.setAttribute('fill', '#000'); image.setAttribute('filter', 'url(#testfilter' + this.uid + ')'); filter.appendChild(cm); pattern.appendChild(filter); } pattern.appendChild(image); pattern.setAttribute('patternUnits', 'userSpaceOnUse'); pattern.setAttribute('patternContentsUnits', 'userSpaceOnUse'); pattern.setAttribute('x', left || 0); pattern.setAttribute('y', top || 0); pattern.setAttribute('width', width); pattern.setAttribute('height', height); //pattern.setAttribute('viewBox', '0 0 75 50'); //pattern.setAttribute('preserveAspectRatio', 'xMidYMid slice'); return this; }, stroke: function(color, width, cap, join, dash){ var element = this.element; element.setAttribute('stroke-width', (width != null) ? width : 1); element.setAttribute('stroke-linecap', (cap != null) ? cap : 'round'); element.setAttribute('stroke-linejoin', (join != null) ? join : 'round'); if (dash) { element.setAttribute('stroke-dasharray', dash.join(',')); } this._setColor('stroke', color); return this; } });