UNPKG

cupiditatea

Version:

A two-dimensional drawing api meant for modern browsers.

490 lines (354 loc) 11.7 kB
(function(Two) { // Localize variables var mod = Two.Utils.mod, toFixed = Two.Utils.toFixed; var svg = { version: 1.1, ns: 'http://www.w3.org/2000/svg', xlink: 'http://www.w3.org/1999/xlink', /** * Create an svg namespaced element. */ createElement: function(name, attrs) { var tag = name; var elem = document.createElementNS(this.ns, tag); if (tag === 'svg') { attrs = _.defaults(attrs || {}, { version: this.version }); } if (_.isObject(attrs)) { svg.setAttributes(elem, attrs); } return elem; }, /** * Add attributes from an svg element. */ setAttributes: function(elem, attrs) { for (var key in attrs) { elem.setAttribute(key, attrs[key]); } return this; }, /** * Remove attributes from an svg element. */ removeAttributes: function(elem, attrs) { for (var key in attrs) { elem.removeAttribute(key); } return this; }, /** * Turn a set of vertices into a string for the d property of a path * element. It is imperative that the string collation is as fast as * possible, because this call will be happening multiple times a * second. */ toString: function(points, closed) { var l = points.length, last = l - 1, d, // The elusive last Two.Commands.move point ret = ''; for (var i = 0; i < l; i++) { var b = points[i]; var command; var prev = closed ? mod(i - 1, l) : Math.max(i - 1, 0); var next = closed ? mod(i + 1, l) : Math.min(i + 1, last); var a = points[prev]; var c = points[next]; var vx, vy, ux, uy, ar, bl, br, cl; // Access x and y directly, // bypassing the getter var x = toFixed(b._x); var y = toFixed(b._y); switch (b._command) { case Two.Commands.close: command = Two.Commands.close; break; case Two.Commands.curve: ar = (a.controls && a.controls.right) || a; bl = (b.controls && b.controls.left) || b; if (a._relative) { vx = toFixed((ar.x + a.x)); vy = toFixed((ar.y + a.y)); } else { vx = toFixed(ar.x); vy = toFixed(ar.y); } if (b._relative) { ux = toFixed((bl.x + b.x)); uy = toFixed((bl.y + b.y)); } else { ux = toFixed(bl.x); uy = toFixed(bl.y); } command = ((i === 0) ? Two.Commands.move : Two.Commands.curve) + ' ' + vx + ' ' + vy + ' ' + ux + ' ' + uy + ' ' + x + ' ' + y; break; case Two.Commands.move: d = b; command = Two.Commands.move + ' ' + x + ' ' + y; break; default: command = b._command + ' ' + x + ' ' + y; } // Add a final point and close it off if (i >= last && closed) { if (b._command === Two.Commands.curve) { // Make sure we close to the most previous Two.Commands.move c = d; br = (b.controls && b.controls.right) || b; cl = (c.controls && c.controls.left) || c; if (b._relative) { vx = toFixed((br.x + b.x)); vy = toFixed((br.y + b.y)); } else { vx = toFixed(br.x); vy = toFixed(br.y); } if (c._relative) { ux = toFixed((cl.x + c.x)); uy = toFixed((cl.y + c.y)); } else { ux = toFixed(cl.x); uy = toFixed(cl.y); } x = toFixed(c.x); y = toFixed(c.y); command += ' C ' + vx + ' ' + vy + ' ' + ux + ' ' + uy + ' ' + x + ' ' + y; } command += ' Z'; } ret += command + ' '; } return ret; }, getClip: function(shape) { clip = shape._renderer.clip; if (!clip) { root = shape; while (root.parent) { root = root.parent; } clip = shape._renderer.clip = svg.createElement('clipPath'); root.defs.appendChild(clip); } return clip; }, group: { // TODO: Can speed up. // TODO: How does this effect a f appendChild: function(id) { var elem = this.domElement.querySelector('#' + id); if (!elem) { return; } var tag = elem.nodeName; if (!tag) { return; } var tagName = tag.replace(/svg\:/ig, '').toLowerCase(); // Defer additions while clipping if (/clippath/.test(tagName)) { return; } this.elem.appendChild(elem); }, // TODO: Can speed up. removeChild: function(id) { var elem = this.domElement.querySelector('#' + id); if (!elem) { return; } var tag = elem.nodeName; if (!tag) { return; } var tagName = tag.replace(/svg\:/ig, '').toLowerCase(); // Defer subtractions while clipping if (/clippath/.test(tagName)) { return; } this.elem.removeChild(elem); }, renderChild: function(child) { svg[child._renderer.type].render.call(child, this); }, render: function(domElement) { this._update(); // Shortcut for hidden objects. // Doesn't reset the flags, so changes are stored and // applied once the object is visible again if (this._opacity === 0 && !this._flagOpacity) { return this; } if (!this._renderer.elem) { this._renderer.elem = svg.createElement('g', { id: this.id }); domElement.appendChild(this._renderer.elem); } // _Update styles for the <g> var flagMatrix = this._matrix.manual || this._flagMatrix; var context = { domElement: domElement, elem: this._renderer.elem }; if (flagMatrix) { this._renderer.elem.setAttribute('transform', 'matrix(' + this._matrix.toString() + ')'); } for (var id in this.children) { var child = this.children[id]; svg[child._renderer.type].render.call(child, domElement); } if (this._flagOpacity) { this._renderer.elem.setAttribute('opacity', this._opacity); } if (this._flagAdditions) { _.each(this.additions, svg.group.appendChild, context); } if (this._flagSubtractions) { _.each(this.subtractions, svg.group.removeChild, context); } /** * Commented two-way functionality of clips / masks with groups and * polygons. Uncomment when this bug is fixed: * https://code.google.com/p/chromium/issues/detail?id=370951 */ // if (this._flagClip) { // clip = svg.getClip(this); // elem = this._renderer.elem; // if (this._clip) { // elem.removeAttribute('id'); // clip.setAttribute('id', this.id); // clip.appendChild(elem); // } else { // clip.removeAttribute('id'); // elem.setAttribute('id', this.id); // this.parent._renderer.elem.appendChild(elem); // TODO: should be insertBefore // } // } if (this._flagMask) { if (this._mask) { this._renderer.elem.setAttribute('clip-path', 'url(#' + this._mask.id + ')'); } else { this._renderer.elem.removeAttribute('clip-path'); } } return this.flagReset(); } }, polygon: { render: function(domElement) { this._update(); // Shortcut for hidden objects. // Doesn't reset the flags, so changes are stored and // applied once the object is visible again if (this._opacity === 0 && !this._flagOpacity) { return this; } // Collect any attribute that needs to be changed here var changed = {}; var flagMatrix = this._matrix.manual || this._flagMatrix; if (flagMatrix) { changed.transform = 'matrix(' + this._matrix.toString() + ')'; } if (this._flagVertices) { var vertices = svg.toString(this._vertices, this._closed); changed.d = vertices; } if (this._flagFill) { changed.fill = this._fill; } if (this._flagStroke) { changed.stroke = this._stroke; } if (this._flagLinewidth) { changed['stroke-width'] = this._linewidth; } if (this._flagOpacity) { changed['stroke-opacity'] = this._opacity; changed['fill-opacity'] = this._opacity; } if (this._flagVisible) { changed.visibility = this._visible ? 'visible' : 'hidden'; } if (this._flagCap) { changed['stroke-linecap'] = this._cap; } if (this._flagJoin) { changed['stroke-linejoin'] = this._join; } if (this._flagMiter) { changed['stroke-miterlimit'] = this.miter; } // If there is no attached DOM element yet, // create it with all necessary attributes. if (!this._renderer.elem) { changed.id = this.id; this._renderer.elem = svg.createElement('path', changed); domElement.appendChild(this._renderer.elem); // Otherwise apply all pending attributes } else { svg.setAttributes(this._renderer.elem, changed); } if (this._flagClip) { clip = svg.getClip(this); elem = this._renderer.elem; if (this._clip) { elem.removeAttribute('id'); clip.setAttribute('id', this.id); clip.appendChild(elem); } else { clip.removeAttribute('id'); elem.setAttribute('id', this.id); this.parent._renderer.elem.appendChild(elem); // TODO: should be insertBefore } } /** * Commented two-way functionality of clips / masks with groups and * polygons. Uncomment when this bug is fixed: * https://code.google.com/p/chromium/issues/detail?id=370951 */ // if (this._flagMask) { // if (this._mask) { // elem.setAttribute('clip-path', 'url(#' + this._mask.id + ')'); // } else { // elem.removeAttribute('clip-path'); // } // } return this.flagReset(); } } }; /** * @class */ var Renderer = Two[Two.Types.svg] = function(params) { this.domElement = params.domElement || svg.createElement('svg'); this.scene = new Two.Group(); this.scene.parent = this; this.defs = svg.createElement('defs'); this.domElement.appendChild(this.defs); }; _.extend(Renderer, { Utils: svg }); _.extend(Renderer.prototype, Backbone.Events, { setSize: function(width, height) { this.width = width; this.height = height; svg.setAttributes(this.domElement, { width: width, height: height }); return this; }, render: function() { svg.group.render.call(this.scene, this.domElement); return this; } }); })(Two);