=this.text.length&&this.selectionEnd>=this.text.length)return;this.abortCursorAnimation(),this._currentCursorOpacity=1,e.shiftKey?this.moveCursorRightWithShift(e):this.moveCursorRightWithoutShift(e),this.initDelayedCursor()},moveCursorRightWithShift:function(e){this._selectionDirection==="left"&&this.selectionStart!==this.selectionEnd?this._moveRight(e,"selectionStart"):(this._selectionDirection="right",this._moveRight(e,"selectionEnd"),this.text.charAt(this.selectionEnd-1)==="\n"&&this.selectionEnd++,this.selectionEnd>this.text.length&&(this.selectionEnd=this.text.length))},moveCursorRightWithoutShift:function(e){this._selectionDirection="right",this.selectionStart===this.selectionEnd?(this._moveRight(e,"selectionStart"),this.selectionEnd=this.selectionStart):(this.selectionEnd+=this.getNumNewLinesInSelectedText(),this.selectionEnd>this.text.length&&(this.selectionEnd=this.text.length),this.selectionStart=this.selectionEnd)},removeChars:function(e){this.selectionStart===this.selectionEnd?this._removeCharsNearCursor(e):this._removeCharsFromTo(this.selectionStart,this.selectionEnd),this.selectionEnd=this.selectionStart,this._removeExtraneousStyles(),this.canvas&&this.canvas.renderAll().renderAll(),this.setCoords(),this.fire("text:changed")},_removeCharsNearCursor:function(e){if(this.selectionStart!==0)if(e.metaKey){var t=this.findLineBoundaryLeft(this.selectionStart);this._removeCharsFromTo(t,this.selectionStart),this.selectionStart=t}else if(e.altKey){var n=this.findWordBoundaryLeft(this.selectionStart);this._removeCharsFromTo(n,this.selectionStart),this.selectionStart=n}else{var r=this.text.slice(this.selectionStart-1,this.selectionStart)==="\n";this.removeStyleObject(r),this.selectionStart--,this.text=this.text.slice(0,this.selectionStart)+this.text.slice(this.selectionStart+1)}}}),fabric.util.object.extend(fabric.IText.prototype,{_setSVGTextLineText:function(e,t,n,r,i,s){this.styles[t]?this._setSVGTextLineChars(e,t,n,r,i,s):this.callSuper("_setSVGTextLineText",e,t,n,r,i)},_setSVGTextLineChars:function(e,t,n,r,i,s){var o=t===0||this.useNative?"y":"dy",u=e.split(""),a=0,f=this._getSVGLineLeftOffset(t),l=this._getSVGLineTopOffset(t),c=this._getHeightOfLine(this.ctx,t);for(var h=0,p=u.length;h'].join("")},_createTextCharSpan:function(e,t,n,r,i,s){var o=this.getSvgStyles.call(fabric.util.object.extend({visible:!0,fill:this.fill,stroke:this.stroke,type:"text"},t));return['',fabric.util.string.escapeXml(e),""].join("")}}),function(){function request(e,t,n){var r=URL.parse(e);r.port||(r.port=r.protocol.indexOf("https:")===0?443:80);var i=r.port===443?HTTPS:HTTP,s=i.request({hostname:r.hostname,port:r.port,path:r.path,method:"GET"},function(e){var r="";t&&e.setEncoding(t),e.on("end",function(){n(r)}),e.on("data",function(t){e.statusCode===200&&(r+=t)})});s.on("error",function(e){e.errno===process.ECONNREFUSED?fabric.log("ECONNREFUSED: connection refused to "+r.hostname+":"+r.port):fabric.log(e.message)}),s.end()}function request_fs(e,t){var n=require("fs");n.readFile(e,function(e,n){if(e)throw fabric.log(e),e;t(n)})}if(typeof document!="undefined"&&typeof window!="undefined")return;var DOMParser=(new require("xmldom")).DOMParser,URL=require("url"),HTTP=require("http"),HTTPS=require("https"),Canvas=require("canvas"),Image=require("canvas").Image;fabric.util.loadImage=function(e,t,n){var r=function(r){i.src=new Buffer(r,"binary"),i._src=e,t&&t.call(n,i)},i=new Image;e&&(e instanceof Buffer||e.indexOf("data")===0)?(i.src=i._src=e,t&&t.call(n,i)):e&&e.indexOf("http")!==0?request_fs(e,r):e?request(e,"binary",r):t&&t.call(n,e)},fabric.loadSVGFromURL=function(e,t,n){e=e.replace(/^\n\s*/,"").replace(/\?.*$/,"").trim(),e.indexOf("http")!==0?request_fs(e,function(e){fabric.loadSVGFromString(e,t,n)}):request(e,"",function(e){fabric.loadSVGFromString(e,t,n)})},fabric.loadSVGFromString=function(e,t,n){var r=(new DOMParser).parseFromString(e);fabric.parseSVGDocument(r.documentElement,function(e,n){t&&t(e,n)},n)},fabric.util.getScript=function(url,callback){request(url,"",function(body){eval(body),callback&&callback()})},fabric.Image.fromObject=function(e,t){fabric.util.loadImage(e.src,function(n){var r=new fabric.Image(n);r._initConfig(e),r._initFilters(e,function(e){r.filters=e||[],t&&t(r)})})},fabric.createCanvasForNode=function(e,t){var n=fabric.document.createElement("canvas"),r=new Canvas(e||600,t||600);n.style={},n.width=r.width,n.height=r.height;var i=fabric.Canvas||fabric.StaticCanvas,s=new i(n);return s.contextContainer=r.getContext("2d"),s.nodeCanvas=r,s.Font=Canvas.Font,s},fabric.StaticCanvas.prototype.createPNGStream=function(){return this.nodeCanvas.createPNGStream()},fabric.StaticCanvas.prototype.createJPEGStream=function(e){return this.nodeCanvas.createJPEGStream(e)};var origSetWidth=fabric.StaticCanvas.prototype.setWidth;fabric.StaticCanvas.prototype.setWidth=function(e){return origSetWidth.call(this,e),this.nodeCanvas.width=e,this},fabric.Canvas&&(fabric.Canvas.prototype.setWidth=fabric.StaticCanvas.prototype.setWidth);var origSetHeight=fabric.StaticCanvas.prototype.setHeight;fabric.StaticCanvas.prototype.setHeight=function(e){return origSetHeight.call(this,e),this.nodeCanvas.height=e,this},fabric.Canvas&&(fabric.Canvas.prototype.setHeight=fabric.StaticCanvas.prototype.setHeight)}();
\ No newline at end of file
diff --git a/dist/all.min.js.gz b/dist/all.min.js.gz
index 1766762a..c30821d5 100644
Binary files a/dist/all.min.js.gz and b/dist/all.min.js.gz differ
diff --git a/dist/all.require.js b/dist/all.require.js
index deb5fad2..83f4e68b 100644
--- a/dist/all.require.js
+++ b/dist/all.require.js
@@ -1,7 +1,11 @@
+<<<<<<< HEAD
/* build: `node build.js modules=ALL` */
+=======
+/* build: `node build.js modules=ALL exclude=gestures,cufon,json minifier=uglifyjs` */
+>>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f
/*! Fabric.js Copyright 2008-2013, Printio (Juriy Zaytsev, Maxim Chernyak) */
-var fabric = fabric || { version: "1.3.7" };
+var fabric = fabric || { version: "1.3.12" };
if (typeof exports !== 'undefined') {
exports.fabric = fabric;
}
@@ -12,7 +16,9 @@ if (typeof document !== 'undefined' && typeof window !== 'undefined') {
}
else {
// assume we're running under node.js when document/window are not present
- fabric.document = require("jsdom").jsdom("
");
+ fabric.document = require("jsdom")
+ .jsdom("");
+
fabric.window = fabric.document.createWindow();
}
@@ -26,1729 +32,1489 @@ fabric.isTouchSupported = "ontouchstart" in fabric.document.documentElement;
* True when in environment that's probably Node.js
* @type boolean
*/
-fabric.isLikelyNode = typeof Buffer !== 'undefined' && typeof window === 'undefined';
+fabric.isLikelyNode = typeof Buffer !== 'undefined' &&
+ typeof window === 'undefined';
-/*!
- * Copyright (c) 2009 Simo Kinnunen.
- * Licensed under the MIT license.
+/**
+ * Attributes parsed from all SVG elements
+ * @type array
*/
+fabric.SHARED_ATTRIBUTES = [
+ "transform",
+ "fill", "fill-opacity", "fill-rule",
+ "opacity",
+ "stroke", "stroke-dasharray", "stroke-linecap",
+ "stroke-linejoin", "stroke-miterlimit",
+ "stroke-opacity", "stroke-width"
+];
-var Cufon = (function() {
- /** @ignore */
- var api = function() {
- return api.replace.apply(null, arguments);
- };
+(function(){
- /** @ignore */
- var DOM = api.DOM = {
+ /**
+ * @private
+ * @param {String} eventName
+ * @param {Function} handler
+ */
+ function _removeEventListener(eventName, handler) {
+ if (!this.__eventListeners[eventName]) return;
- ready: (function() {
+ if (handler) {
+ fabric.util.removeFromArray(this.__eventListeners[eventName], handler);
+ }
+ else {
+ this.__eventListeners[eventName].length = 0;
+ }
+ }
- var complete = false, readyStatus = { loaded: 1, complete: 1 };
-
- var queue = [], /** @ignore */ perform = function() {
- if (complete) return;
- complete = true;
- for (var fn; fn = queue.shift(); fn());
- };
-
- // Gecko, Opera, WebKit r26101+
-
- if (fabric.document.addEventListener) {
- fabric.document.addEventListener('DOMContentLoaded', perform, false);
- fabric.window.addEventListener('pageshow', perform, false); // For cached Gecko pages
+ /**
+ * Observes specified event
+ * @deprecated `observe` deprecated since 0.8.34 (use `on` instead)
+ * @memberOf fabric.Observable
+ * @alias on
+ * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler})
+ * @param {Function} handler Function that receives a notification when an event of the specified type occurs
+ * @return {Self} thisArg
+ * @chainable
+ */
+ function observe(eventName, handler) {
+ if (!this.__eventListeners) {
+ this.__eventListeners = { };
+ }
+ // one object with key/value pairs was passed
+ if (arguments.length === 1) {
+ for (var prop in eventName) {
+ this.on(prop, eventName[prop]);
}
-
- // Old WebKit, Internet Explorer
-
- if (!fabric.window.opera && fabric.document.readyState) (function() {
- readyStatus[fabric.document.readyState] ? perform() : setTimeout(arguments.callee, 10);
- })();
-
- // Internet Explorer
-
- if (fabric.document.readyState && fabric.document.createStyleSheet) (function() {
- try {
- fabric.document.body.doScroll('left');
- perform();
- }
- catch (e) {
- setTimeout(arguments.callee, 1);
- }
- })();
-
- addEvent(fabric.window, 'load', perform); // Fallback
-
- return function(listener) {
- if (!arguments.length) perform();
- else complete ? listener() : queue.push(listener);
- };
-
- })()
-
- };
-
- /** @ignore */
- var CSS = api.CSS = /** @ignore */ {
-
- /** @ignore */
- Size: function(value, base) {
-
- this.value = parseFloat(value);
- this.unit = String(value).match(/[a-z%]*$/)[0] || 'px';
-
- /** @ignore */
- this.convert = function(value) {
- return value / base * this.value;
- };
-
- /** @ignore */
- this.convertFrom = function(value) {
- return value / this.value * base;
- };
-
- /** @ignore */
- this.toString = function() {
- return this.value + this.unit;
- };
-
- },
-
- /** @ignore */
- getStyle: function(el) {
- return new Style(el.style);
- /*
- var view = document.defaultView;
- if (view && view.getComputedStyle) return new Style(view.getComputedStyle(el, null));
- if (el.currentStyle) return new Style(el.currentStyle);
- return new Style(el.style);
- */
- },
-
- quotedList: cached(function(value) {
- // doesn't work properly with empty quoted strings (""), but
- // it's not worth the extra code.
- var list = [], re = /\s*((["'])([\s\S]*?[^\\])\2|[^,]+)\s*/g, match;
- while (match = re.exec(value)) list.push(match[3] || match[1]);
- return list;
- }),
-
- ready: (function() {
-
- var complete = false;
-
- var queue = [], perform = function() {
- complete = true;
- for (var fn; fn = queue.shift(); fn());
- };
-
- // Safari 2 does not include ');
+ if (arcToSegmentsCache[argsString]) {
+ return arcToSegmentsCache[argsString];
+ }
- function getFontSizeInPixels(el, value) {
- return getSizeInPixels(el, /(?:em|ex|%)$/i.test(value) ? '1em' : value);
- }
+ var coords = getXYCoords(rotateX, rx, ry, ox, oy, x, y);
- // Original by Dead Edwards.
- // Combined with getFontSizeInPixels it also works with relative units.
- function getSizeInPixels(el, value) {
- if (/px$/i.test(value)) return parseFloat(value);
- var style = el.style.left, runtimeStyle = el.runtimeStyle.left;
- el.runtimeStyle.left = el.currentStyle.left;
- el.style.left = value;
- var result = el.style.pixelLeft;
- el.style.left = style;
- el.runtimeStyle.left = runtimeStyle;
+ var d = (coords.x1-coords.x0) * (coords.x1-coords.x0) +
+ (coords.y1-coords.y0) * (coords.y1-coords.y0);
+
+ var sfactor_sq = 1 / d - 0.25;
+ if (sfactor_sq < 0) sfactor_sq = 0;
+
+ var sfactor = Math.sqrt(sfactor_sq);
+ if (sweep === large) sfactor = -sfactor;
+
+ var xc = 0.5 * (coords.x0 + coords.x1) - sfactor * (coords.y1-coords.y0);
+ var yc = 0.5 * (coords.y0 + coords.y1) + sfactor * (coords.x1-coords.x0);
+
+ var th0 = Math.atan2(coords.y0-yc, coords.x0-xc);
+ var th1 = Math.atan2(coords.y1-yc, coords.x1-xc);
+
+ var th_arc = th1-th0;
+ if (th_arc < 0 && sweep === 1) {
+ th_arc += 2*Math.PI;
+ }
+ else if (th_arc > 0 && sweep === 0) {
+ th_arc -= 2 * Math.PI;
+ }
+
+ var segments = Math.ceil(Math.abs(th_arc / (Math.PI * 0.5 + 0.001)));
+ var result = [];
+ for (var i=0; i 1) {
+ pl = Math.sqrt(pl);
+ rx *= pl;
+ ry *= pl;
}
- var wrapper, canvas;
+ var a00 = cos_th / rx;
+ var a01 = sin_th / rx;
+ var a10 = (-sin_th) / ry;
+ var a11 = (cos_th) / ry;
- if (redraw) {
- wrapper = node;
- canvas = node.firstChild;
+ return {
+ x0: a00 * ox + a01 * oy,
+ y0: a10 * ox + a11 * oy,
+ x1: a00 * x + a01 * y,
+ y1: a10 * x + a11 * y,
+ sin_th: sin_th,
+ cos_th: cos_th
+ };
+ }
+
+ function segmentToBezier(cx, cy, th0, th1, rx, ry, sin_th, cos_th) {
+ argsString = _join.call(arguments);
+ if (segmentToBezierCache[argsString]) {
+ return segmentToBezierCache[argsString];
}
- else {
- wrapper = fabric.document.createElement('span');
- wrapper.className = 'cufon cufon-vml';
- wrapper.alt = text;
- canvas = fabric.document.createElement('span');
- canvas.className = 'cufon-vml-canvas';
- wrapper.appendChild(canvas);
+ var a00 = cos_th * rx;
+ var a01 = -sin_th * ry;
+ var a10 = sin_th * rx;
+ var a11 = cos_th * ry;
- if (options.printable) {
- var print = fabric.document.createElement('span');
- print.className = 'cufon-alt';
- print.appendChild(fabric.document.createTextNode(text));
- wrapper.appendChild(print);
+ var th_half = 0.5 * (th1 - th0);
+ var t = (8/3) * Math.sin(th_half * 0.5) *
+ Math.sin(th_half * 0.5) / Math.sin(th_half);
+
+ var x1 = cx + Math.cos(th0) - t * Math.sin(th0);
+ var y1 = cy + Math.sin(th0) + t * Math.cos(th0);
+ var x3 = cx + Math.cos(th1);
+ var y3 = cy + Math.sin(th1);
+ var x2 = x3 + t * Math.sin(th1);
+ var y2 = y3 - t * Math.cos(th1);
+
+ segmentToBezierCache[argsString] = [
+ a00 * x1 + a01 * y1, a10 * x1 + a11 * y1,
+ a00 * x2 + a01 * y2, a10 * x2 + a11 * y2,
+ a00 * x3 + a01 * y3, a10 * x3 + a11 * y3
+ ];
+
+ return segmentToBezierCache[argsString];
+ }
+
+ /**
+ * Draws arc
+ * @param {CanvasRenderingContext2D} ctx
+ * @param {Number} x
+ * @param {Number} y
+ * @param {Array} coords
+ */
+ fabric.util.drawArc = function(ctx, x, y, coords) {
+ var rx = coords[0];
+ var ry = coords[1];
+ var rot = coords[2];
+ var large = coords[3];
+ var sweep = coords[4];
+ var ex = coords[5];
+ var ey = coords[6];
+ var segs = arcToSegments(ex, ey, rx, ry, large, sweep, rot, x, y);
+ for (var i=0; i>> 0;
+ if (len === 0) {
+ return -1;
+ }
+ var n = 0;
+ if (arguments.length > 0) {
+ n = Number(arguments[1]);
+ if (n !== n) { // shortcut for verifying if it's NaN
+ n = 0;
+ }
+ else if (n !== 0 && n !== Number.POSITIVE_INFINITY && n !== Number.NEGATIVE_INFINITY) {
+ n = (n > 0 || -1) * Math.floor(Math.abs(n));
+ }
+ }
+ if (n >= len) {
+ return -1;
+ }
+ var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
+ for (; k < len; k++) {
+ if (k in t && t[k] === searchElement) {
+ return k;
+ }
+ }
+ return -1;
+ };
+ }
- // ie6, for some reason, has trouble rendering the last VML element in the document.
- // we can work around this by injecting a dummy element where needed.
- // @todo find a better solution
- if (!hasNext) wrapper.appendChild(fabric.document.createElement('cvml:shape'));
- }
+ if (!Array.prototype.forEach) {
+ /**
+ * Iterates an array, invoking callback for each element
+ * @param {Function} fn Callback to invoke for each element
+ * @param {Object} [context] Context to invoke callback in
+ * @return {Array}
+ */
+ Array.prototype.forEach = function(fn, context) {
+ for (var i = 0, len = this.length >>> 0; i < len; i++) {
+ if (i in this) {
+ fn.call(context, this[i], i, this);
+ }
+ }
+ };
+ }
- var wStyle = wrapper.style;
- var cStyle = canvas.style;
+ if (!Array.prototype.map) {
+ /**
+ * Returns a result of iterating over an array, invoking callback for each element
+ * @param {Function} fn Callback to invoke for each element
+ * @param {Object} [context] Context to invoke callback in
+ * @return {Array}
+ */
+ Array.prototype.map = function(fn, context) {
+ var result = [ ];
+ for (var i = 0, len = this.length >>> 0; i < len; i++) {
+ if (i in this) {
+ result[i] = fn.call(context, this[i], i, this);
+ }
+ }
+ return result;
+ };
+ }
- var height = size.convert(viewBox.height), roundedHeight = Math.ceil(height);
- var roundingFactor = roundedHeight / height;
- var minX = viewBox.minX, minY = viewBox.minY;
+ if (!Array.prototype.every) {
+ /**
+ * Returns true if a callback returns truthy value for all elements in an array
+ * @param {Function} fn Callback to invoke for each element
+ * @param {Object} [context] Context to invoke callback in
+ * @return {Boolean}
+ */
+ Array.prototype.every = function(fn, context) {
+ for (var i = 0, len = this.length >>> 0; i < len; i++) {
+ if (i in this && !fn.call(context, this[i], i, this)) {
+ return false;
+ }
+ }
+ return true;
+ };
+ }
- cStyle.height = roundedHeight;
- cStyle.top = Math.round(size.convert(minY - font.ascent));
- cStyle.left = Math.round(size.convert(minX));
+ if (!Array.prototype.some) {
+ /**
+ * Returns true if a callback returns truthy value for at least one element in an array
+ * @param {Function} fn Callback to invoke for each element
+ * @param {Object} [context] Context to invoke callback in
+ * @return {Boolean}
+ */
+ Array.prototype.some = function(fn, context) {
+ for (var i = 0, len = this.length >>> 0; i < len; i++) {
+ if (i in this && fn.call(context, this[i], i, this)) {
+ return true;
+ }
+ }
+ return false;
+ };
+ }
- wStyle.height = size.convert(font.height) + 'px';
+ if (!Array.prototype.filter) {
+ /**
+ * Returns the result of iterating over elements in an array
+ * @param {Function} fn Callback to invoke for each element
+ * @param {Object} [context] Context to invoke callback in
+ * @return {Array}
+ */
+ Array.prototype.filter = function(fn, context) {
+ var result = [ ], val;
+ for (var i = 0, len = this.length >>> 0; i < len; i++) {
+ if (i in this) {
+ val = this[i]; // in case fn mutates this
+ if (fn.call(context, val, i, this)) {
+ result.push(val);
+ }
+ }
+ }
+ return result;
+ };
+ }
- var textDecoration = Cufon.getTextDecoration(options);
+ if (!Array.prototype.reduce) {
+ /**
+ * Returns "folded" (reduced) result of iterating over elements in an array
+ * @param {Function} fn Callback to invoke for each element
+ * @param {Object} [context] Context to invoke callback in
+ * @return {Any}
+ */
+ Array.prototype.reduce = function(fn /*, initial*/) {
+ var len = this.length >>> 0,
+ i = 0,
+ rv;
- var color = style.get('color');
-
- var chars = Cufon.CSS.textTransform(text, style).split('');
-
- var width = 0, offsetX = 0, advance = null;
-
- var glyph, shape, shadows = options.textShadow;
-
- // pre-calculate width
- for (var i = 0, k = 0, l = chars.length; i < l; ++i) {
- glyph = font.glyphs[chars[i]] || font.missingGlyph;
- if (glyph) width += advance = ~~(glyph.w || font.w) + letterSpacing;
- }
-
- if (advance === null) return null;
-
- var fullWidth = -minX + width + (viewBox.width - advance);
-
- var shapeWidth = size.convert(fullWidth * roundingFactor), roundedShapeWidth = Math.round(shapeWidth);
-
- var coordSize = fullWidth + ',' + viewBox.height, coordOrigin;
- var stretch = 'r' + coordSize + 'nsnf';
-
- for (i = 0; i < l; ++i) {
-
- glyph = font.glyphs[chars[i]] || font.missingGlyph;
- if (!glyph) continue;
-
- if (redraw) {
- // some glyphs may be missing so we can't use i
- shape = canvas.childNodes[k];
- if (shape.firstChild) shape.removeChild(shape.firstChild); // shadow
+ if (arguments.length > 1) {
+ rv = arguments[1];
}
else {
- shape = fabric.document.createElement('cvml:shape');
- canvas.appendChild(shape);
- }
-
- shape.stroked = 'f';
- shape.coordsize = coordSize;
- shape.coordorigin = coordOrigin = (minX - offsetX) + ',' + minY;
- shape.path = (glyph.d ? 'm' + glyph.d + 'xe' : '') + 'm' + coordOrigin + stretch;
- shape.fillcolor = color;
-
- // it's important to not set top/left or IE8 will grind to a halt
- var sStyle = shape.style;
- sStyle.width = roundedShapeWidth;
- sStyle.height = roundedHeight;
-
- if (shadows) {
- // due to the limitations of the VML shadow element there
- // can only be two visible shadows. opacity is shared
- // for all shadows.
- var shadow1 = shadows[0], shadow2 = shadows[1];
- var color1 = Cufon.CSS.color(shadow1.color), color2;
- var shadow = fabric.document.createElement('cvml:shadow');
- shadow.on = 't';
- shadow.color = color1.color;
- shadow.offset = shadow1.offX + ',' + shadow1.offY;
- if (shadow2) {
- color2 = Cufon.CSS.color(shadow2.color);
- shadow.type = 'double';
- shadow.color2 = color2.color;
- shadow.offset2 = shadow2.offX + ',' + shadow2.offY;
+ do {
+ if (i in this) {
+ rv = this[i++];
+ break;
+ }
+ // if array contains no values, no initial value to return
+ if (++i >= len) {
+ throw new TypeError();
+ }
}
- shadow.opacity = color1.opacity || (color2 && color2.opacity) || 1;
- shape.appendChild(shadow);
+ while (true);
}
+ for (; i < len; i++) {
+ if (i in this) {
+ rv = fn.call(null, rv, this[i], i, this);
+ }
+ }
+ return rv;
+ };
+ }
- offsetX += ~~(glyph.w || font.w) + letterSpacing;
-
- ++k;
+ /* _ES5_COMPAT_END_ */
+ /**
+ * Invokes method on all items in a given array
+ * @memberOf fabric.util.array
+ * @param {Array} array Array to iterate over
+ * @param {String} method Name of a method to invoke
+ * @return {Array}
+ */
+ function invoke(array, method) {
+ var args = slice.call(arguments, 2), result = [ ];
+ for (var i = 0, len = array.length; i < len; i++) {
+ result[i] = args.length ? array[i][method].apply(array[i], args) : array[i][method].call(array[i]);
}
+ return result;
+ }
- wStyle.width = Math.max(Math.ceil(size.convert(width * roundingFactor)), 0);
+ /**
+ * Finds maximum value in array (not necessarily "first" one)
+ * @memberOf fabric.util.array
+ * @param {Array} array Array to iterate over
+ * @param {String} byProperty
+ * @return {Any}
+ */
+ function max(array, byProperty) {
+ return find(array, byProperty, function(value1, value2) {
+ return value1 >= value2;
+ });
+ }
- return wrapper;
+ /**
+ * Finds minimum value in array (not necessarily "first" one)
+ * @memberOf fabric.util.array
+ * @param {Array} array Array to iterate over
+ * @param {String} byProperty
+ * @return {Any}
+ */
+ function min(array, byProperty) {
+ return find(array, byProperty, function(value1, value2) {
+ return value1 < value2;
+ });
+ }
+ /**
+ * @private
+ */
+ function find(array, byProperty, condition) {
+ if (!array || array.length === 0) return undefined;
+
+ var i = array.length - 1,
+ result = byProperty ? array[i][byProperty] : array[i];
+ if (byProperty) {
+ while (i--) {
+ if (condition(array[i][byProperty], result)) {
+ result = array[i][byProperty];
+ }
+ }
+ }
+ else {
+ while (i--) {
+ if (condition(array[i], result)) {
+ result = array[i];
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * @namespace fabric.util.array
+ */
+ fabric.util.array = {
+ invoke: invoke,
+ min: min,
+ max: max
};
-})());
+})();
-Cufon.getTextDecoration = function(options) {
- return {
- underline: options.textDecoration === 'underline',
- overline: options.textDecoration === 'overline',
- 'line-through': options.textDecoration === 'line-through'
+
+(function(){
+
+ /**
+ * Copies all enumerable properties of one object to another
+ * @memberOf fabric.util.object
+ * @param {Object} destination Where to copy to
+ * @param {Object} source Where to copy from
+ * @return {Object}
+ */
+ function extend(destination, source) {
+ // JScript DontEnum bug is not taken care of
+ for (var property in source) {
+ destination[property] = source[property];
+ }
+ return destination;
+ }
+
+ /**
+ * Creates an empty object and copies all enumerable properties of another object to it
+ * @memberOf fabric.util.object
+ * @param {Object} object Object to clone
+ * @return {Object}
+ */
+ function clone(object) {
+ return extend({ }, object);
+ }
+
+ /** @namespace fabric.util.object */
+ fabric.util.object = {
+ extend: extend,
+ clone: clone
};
+
+})();
+
+
+(function() {
+
+/* _ES5_COMPAT_START_ */
+if (!String.prototype.trim) {
+ /**
+ * Trims a string (removing whitespace from the beginning and the end)
+ * @function external:String#trim
+ * @see String#trim on MDN
+ */
+ String.prototype.trim = function () {
+ // this trim is not fully ES3 or ES5 compliant, but it should cover most cases for now
+ return this.replace(/^[\s\xA0]+/, '').replace(/[\s\xA0]+$/, '');
+ };
+}
+/* _ES5_COMPAT_END_ */
+
+/**
+ * Camelizes a string
+ * @memberOf fabric.util.string
+ * @param {String} string String to camelize
+ * @return {String} Camelized version of a string
+ */
+function camelize(string) {
+ return string.replace(/-+(.)?/g, function(match, character) {
+ return character ? character.toUpperCase() : '';
+ });
+}
+
+/**
+ * Capitalizes a string
+ * @memberOf fabric.util.string
+ * @param {String} string String to capitalize
+ * @param {Boolean} [firstLetterOnly] If true only first letter is capitalized
+ * and other letters stay untouched, if false first letter is capitalized
+ * and other letters are converted to lowercase.
+ * @return {String} Capitalized version of a string
+ */
+function capitalize(string, firstLetterOnly) {
+ return string.charAt(0).toUpperCase() +
+ (firstLetterOnly ? string.slice(1) : string.slice(1).toLowerCase());
+}
+
+/**
+ * Escapes XML in a string
+ * @memberOf fabric.util.string
+ * @param {String} string String to escape
+ * @return {String} Escaped version of a string
+ */
+function escapeXml(string) {
+ return string.replace(/&/g, '&')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''')
+ .replace(//g, '>');
+}
+
+/**
+ * String utilities
+ * @namespace fabric.util.string
+ */
+fabric.util.string = {
+ camelize: camelize,
+ capitalize: capitalize,
+ escapeXml: escapeXml
};
-
-if (typeof exports != 'undefined') {
- exports.Cufon = Cufon;
-}
-
-
-/*
- json2.js
- 2011-10-19
-
- Public Domain.
-
- NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
-
- See http://www.JSON.org/js.html
-
-
- This code should be minified before deployment.
- See http://javascript.crockford.com/jsmin.html
-
- USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
- NOT CONTROL.
-
-
- This file creates a global JSON object containing two methods: stringify
- and parse.
-
- JSON.stringify(value, replacer, space)
- value any JavaScript value, usually an object or array.
-
- replacer an optional parameter that determines how object
- values are stringified for objects. It can be a
- function or an array of strings.
-
- space an optional parameter that specifies the indentation
- of nested structures. If it is omitted, the text will
- be packed without extra whitespace. If it is a number,
- it will specify the number of spaces to indent at each
- level. If it is a string (such as '\t' or ' '),
- it contains the characters used to indent at each level.
-
- This method produces a JSON text from a JavaScript value.
-
- When an object value is found, if the object contains a toJSON
- method, its toJSON method will be called and the result will be
- stringified. A toJSON method does not serialize: it returns the
- value represented by the name/value pair that should be serialized,
- or undefined if nothing should be serialized. The toJSON method
- will be passed the key associated with the value, and this will be
- bound to the value
-
- For example, this would serialize Dates as ISO strings.
-
- Date.prototype.toJSON = function (key) {
- function f(n) {
- // Format integers to have at least two digits.
- return n < 10 ? '0' + n : n;
- }
-
- return this.getUTCFullYear() + '-' +
- f(this.getUTCMonth() + 1) + '-' +
- f(this.getUTCDate()) + 'T' +
- f(this.getUTCHours()) + ':' +
- f(this.getUTCMinutes()) + ':' +
- f(this.getUTCSeconds()) + 'Z';
- };
-
- You can provide an optional replacer method. It will be passed the
- key and value of each member, with this bound to the containing
- object. The value that is returned from your method will be
- serialized. If your method returns undefined, then the member will
- be excluded from the serialization.
-
- If the replacer parameter is an array of strings, then it will be
- used to select the members to be serialized. It filters the results
- such that only members with keys listed in the replacer array are
- stringified.
-
- Values that do not have JSON representations, such as undefined or
- functions, will not be serialized. Such values in objects will be
- dropped; in arrays they will be replaced with null. You can use
- a replacer function to replace those with JSON values.
- JSON.stringify(undefined) returns undefined.
-
- The optional space parameter produces a stringification of the
- value that is filled with line breaks and indentation to make it
- easier to read.
-
- If the space parameter is a non-empty string, then that string will
- be used for indentation. If the space parameter is a number, then
- the indentation will be that many spaces.
-
- Example:
-
- text = JSON.stringify(['e', {pluribus: 'unum'}]);
- // text is '["e",{"pluribus":"unum"}]'
-
-
- text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
- // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
-
- text = JSON.stringify([new Date()], function (key, value) {
- return this[key] instanceof Date ?
- 'Date(' + this[key] + ')' : value;
- });
- // text is '["Date(---current time---)"]'
-
-
- JSON.parse(text, reviver)
- This method parses a JSON text to produce an object or array.
- It can throw a SyntaxError exception.
-
- The optional reviver parameter is a function that can filter and
- transform the results. It receives each of the keys and values,
- and its return value is used instead of the original value.
- If it returns what it received, then the structure is not modified.
- If it returns undefined then the member is deleted.
-
- Example:
-
- // Parse the text. Values that look like ISO date strings will
- // be converted to Date objects.
-
- myData = JSON.parse(text, function (key, value) {
- var a;
- if (typeof value === 'string') {
- a =
-/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
- if (a) {
- return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
- +a[5], +a[6]));
- }
- }
- return value;
- });
-
- myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
- var d;
- if (typeof value === 'string' &&
- value.slice(0, 5) === 'Date(' &&
- value.slice(-1) === ')') {
- d = new Date(value.slice(5, -1));
- if (d) {
- return d;
- }
- }
- return value;
- });
-
-
- This is a reference implementation. You are free to copy, modify, or
- redistribute.
-*/
-
-/*jslint evil: true, regexp: true */
-
-/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
- call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
- getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
- lastIndex, length, parse, prototype, push, replace, slice, stringify,
- test, toJSON, toString, valueOf
-*/
-
-
-// Create a JSON object only if one does not already exist. We create the
-// methods in a closure to avoid creating global variables.
-
-var JSON;
-if (!JSON) {
- JSON = {};
-}
-
-(function () {
- 'use strict';
-
- function f(n) {
- // Format integers to have at least two digits.
- return n < 10 ? '0' + n : n;
- }
-
- if (typeof Date.prototype.toJSON !== 'function') {
-
- /** @ignore */
- Date.prototype.toJSON = function (key) {
-
- return isFinite(this.valueOf())
- ? this.getUTCFullYear() + '-' +
- f(this.getUTCMonth() + 1) + '-' +
- f(this.getUTCDate()) + 'T' +
- f(this.getUTCHours()) + ':' +
- f(this.getUTCMinutes()) + ':' +
- f(this.getUTCSeconds()) + 'Z'
- : null;
- };
-
- String.prototype.toJSON =
- Number.prototype.toJSON =
- /** @ignore */
- Boolean.prototype.toJSON = function (key) {
- return this.valueOf();
- };
- }
-
- var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
- escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
- gap,
- indent,
- meta = { // table of character substitutions
- '\b': '\\b',
- '\t': '\\t',
- '\n': '\\n',
- '\f': '\\f',
- '\r': '\\r',
- '"' : '\\"',
- '\\': '\\\\'
- },
- rep;
-
-
- function quote(string) {
-
-// If the string contains no control characters, no quote characters, and no
-// backslash characters, then we can safely slap some quotes around it.
-// Otherwise we must also replace the offending characters with safe escape
-// sequences.
-
- escapable.lastIndex = 0;
- return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
- var c = meta[a];
- return typeof c === 'string'
- ? c
- : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
- }) + '"' : '"' + string + '"';
- }
-
-
- function str(key, holder) {
-
-// Produce a string from holder[key].
-
- var i, // The loop counter.
- k, // The member key.
- v, // The member value.
- length,
- mind = gap,
- partial,
- value = holder[key];
-
-// If the value has a toJSON method, call it to obtain a replacement value.
-
- if (value && typeof value === 'object' &&
- typeof value.toJSON === 'function') {
- value = value.toJSON(key);
- }
-
-// If we were called with a replacer function, then call the replacer to
-// obtain a replacement value.
-
- if (typeof rep === 'function') {
- value = rep.call(holder, key, value);
- }
-
-// What happens next depends on the value's type.
-
- switch (typeof value) {
- case 'string':
- return quote(value);
-
- case 'number':
-
-// JSON numbers must be finite. Encode non-finite numbers as null.
-
- return isFinite(value) ? String(value) : 'null';
-
- case 'boolean':
- case 'null':
-
-// If the value is a boolean or null, convert it to a string. Note:
-// typeof null does not produce 'null'. The case is included here in
-// the remote chance that this gets fixed someday.
-
- return String(value);
-
-// If the type is 'object', we might be dealing with an object or an array or
-// null.
-
- case 'object':
-
-// Due to a specification blunder in ECMAScript, typeof null is 'object',
-// so watch out for that case.
-
- if (!value) {
- return 'null';
- }
-
-// Make an array to hold the partial results of stringifying this object value.
-
- gap += indent;
- partial = [];
-
-// Is the value an array?
-
- if (Object.prototype.toString.apply(value) === '[object Array]') {
-
-// The value is an array. Stringify every element. Use null as a placeholder
-// for non-JSON values.
-
- length = value.length;
- for (i = 0; i < length; i += 1) {
- partial[i] = str(i, value) || 'null';
- }
-
-// Join all of the elements together, separated with commas, and wrap them in
-// brackets.
-
- v = partial.length === 0
- ? '[]'
- : gap
- ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']'
- : '[' + partial.join(',') + ']';
- gap = mind;
- return v;
- }
-
-// If the replacer is an array, use it to select the members to be stringified.
-
- if (rep && typeof rep === 'object') {
- length = rep.length;
- for (i = 0; i < length; i += 1) {
- if (typeof rep[i] === 'string') {
- k = rep[i];
- v = str(k, value);
- if (v) {
- partial.push(quote(k) + (gap ? ': ' : ':') + v);
- }
- }
- }
- } else {
-
-// Otherwise, iterate through all of the keys in the object.
-
- for (k in value) {
- if (Object.prototype.hasOwnProperty.call(value, k)) {
- v = str(k, value);
- if (v) {
- partial.push(quote(k) + (gap ? ': ' : ':') + v);
- }
- }
- }
- }
-
-// Join all of the member texts together, separated with commas,
-// and wrap them in braces.
-
- v = partial.length === 0
- ? '{}'
- : gap
- ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}'
- : '{' + partial.join(',') + '}';
- gap = mind;
- return v;
- }
- }
-
-// If the JSON object does not yet have a stringify method, give it one.
-
- if (typeof JSON.stringify !== 'function') {
- /** @ignore */
- JSON.stringify = function (value, replacer, space) {
-
-// The stringify method takes a value and an optional replacer, and an optional
-// space parameter, and returns a JSON text. The replacer can be a function
-// that can replace values, or an array of strings that will select the keys.
-// A default replacer method can be provided. Use of the space parameter can
-// produce text that is more easily readable.
-
- var i;
- gap = '';
- indent = '';
-
-// If the space parameter is a number, make an indent string containing that
-// many spaces.
-
- if (typeof space === 'number') {
- for (i = 0; i < space; i += 1) {
- indent += ' ';
- }
-
-// If the space parameter is a string, it will be used as the indent string.
-
- } else if (typeof space === 'string') {
- indent = space;
- }
-
-// If there is a replacer, it must be a function or an array.
-// Otherwise, throw an error.
-
- rep = replacer;
- if (replacer && typeof replacer !== 'function' &&
- (typeof replacer !== 'object' ||
- typeof replacer.length !== 'number')) {
- throw new Error('JSON.stringify');
- }
-
-// Make a fake root object containing our value under the key of ''.
-// Return the result of stringifying the value.
-
- return str('', {'': value});
- };
- }
-
-
-// If the JSON object does not yet have a parse method, give it one.
-
- if (typeof JSON.parse !== 'function') {
- /** @ignore */
- JSON.parse = function (text, reviver) {
-
-// The parse method takes a text and an optional reviver function, and returns
-// a JavaScript value if the text is a valid JSON text.
-
- var j;
-
- function walk(holder, key) {
-
-// The walk method is used to recursively walk the resulting structure so
-// that modifications can be made.
-
- var k, v, value = holder[key];
- if (value && typeof value === 'object') {
- for (k in value) {
- if (Object.prototype.hasOwnProperty.call(value, k)) {
- v = walk(value, k);
- if (v !== undefined) {
- value[k] = v;
- } else {
- delete value[k];
- }
- }
- }
- }
- return reviver.call(holder, key, value);
- }
-
-
-// Parsing happens in four stages. In the first stage, we replace certain
-// Unicode characters with escape sequences. JavaScript handles many characters
-// incorrectly, either silently deleting them, or treating them as line endings.
-
- text = String(text);
- cx.lastIndex = 0;
- if (cx.test(text)) {
- text = text.replace(cx, function (a) {
- return '\\u' +
- ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
- });
- }
-
-// In the second stage, we run the text against regular expressions that look
-// for non-JSON patterns. We are especially concerned with '()' and 'new'
-// because they can cause invocation, and '=' because it can cause mutation.
-// But just to be safe, we want to reject all unexpected forms.
-
-// We split the second stage into 4 regexp operations in order to work around
-// crippling inefficiencies in IE's and Safari's regexp engines. First we
-// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
-// replace all simple value tokens with ']' characters. Third, we delete all
-// open brackets that follow a colon or comma or that begin the text. Finally,
-// we look to see that the remaining characters are only whitespace or ']' or
-// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
-
- if (/^[\],:{}\s]*$/
- .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
- .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
- .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
-
-// In the third stage we use the eval function to compile the text into a
-// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
-// in JavaScript: it can begin a block or an object literal. We wrap the text
-// in parens to eliminate the ambiguity.
-
- j = eval('(' + text + ')');
-
-// In the optional fourth stage, we recursively walk the new structure, passing
-// each name/value pair to a reviver function for possible transformation.
-
- return typeof reviver === 'function'
- ? walk({'': j}, '')
- : j;
- }
-
-// If the text is not JSON parseable, then a SyntaxError is thrown.
-
- throw new SyntaxError('JSON.parse');
- };
- }
}());
+
+/* _ES5_COMPAT_START_ */
+(function() {
+
+ var slice = Array.prototype.slice,
+ apply = Function.prototype.apply,
+ Dummy = function() { };
+
+ if (!Function.prototype.bind) {
+ /**
+ * Cross-browser approximation of ES5 Function.prototype.bind (not fully spec conforming)
+ * @see Function#bind on MDN
+ * @param {Object} thisArg Object to bind function to
+ * @param {Any[]} [...] Values to pass to a bound function
+ * @return {Function}
+ */
+ Function.prototype.bind = function(thisArg) {
+ var fn = this, args = slice.call(arguments, 1), bound;
+ if (args.length) {
+ bound = function() {
+ return apply.call(fn, this instanceof Dummy ? this : thisArg, args.concat(slice.call(arguments)));
+ };
+ }
+ else {
+ /** @ignore */
+ bound = function() {
+ return apply.call(fn, this instanceof Dummy ? this : thisArg, arguments);
+ };
+ }
+ Dummy.prototype = this.prototype;
+ bound.prototype = new Dummy();
+
+ return bound;
+ };
+ }
+
+})();
+/* _ES5_COMPAT_END_ */
+
+
+(function() {
+
+ var slice = Array.prototype.slice, emptyFunction = function() { };
+
+ var IS_DONTENUM_BUGGY = (function(){
+ for (var p in { toString: 1 }) {
+ if (p === 'toString') return false;
+ }
+ return true;
+ })();
+
+ /** @ignore */
+ var addMethods = function(klass, source, parent) {
+ for (var property in source) {
+
+ if (property in klass.prototype &&
+ typeof klass.prototype[property] === 'function' &&
+ (source[property] + '').indexOf('callSuper') > -1) {
+
+ klass.prototype[property] = (function(property) {
+ return function() {
+
+ var superclass = this.constructor.superclass;
+ this.constructor.superclass = parent;
+ var returnValue = source[property].apply(this, arguments);
+ this.constructor.superclass = superclass;
+
+ if (property !== 'initialize') {
+ return returnValue;
+ }
+ };
+ })(property);
+ }
+ else {
+ klass.prototype[property] = source[property];
+ }
+
+ if (IS_DONTENUM_BUGGY) {
+ if (source.toString !== Object.prototype.toString) {
+ klass.prototype.toString = source.toString;
+ }
+ if (source.valueOf !== Object.prototype.valueOf) {
+ klass.prototype.valueOf = source.valueOf;
+ }
+ }
+ }
+ };
+
+ function Subclass() { }
+
+ function callSuper(methodName) {
+ var fn = this.constructor.superclass.prototype[methodName];
+ return (arguments.length > 1)
+ ? fn.apply(this, slice.call(arguments, 1))
+ : fn.call(this);
+ }
+
+ /**
+ * Helper for creation of "classes".
+ * @memberOf fabric.util
+ * @param parent optional "Class" to inherit from
+ * @param properties Properties shared by all instances of this class
+ * (be careful modifying objects defined here as this would affect all instances)
+ */
+ function createClass() {
+ var parent = null,
+ properties = slice.call(arguments, 0);
+
+ if (typeof properties[0] === 'function') {
+ parent = properties.shift();
+ }
+ function klass() {
+ this.initialize.apply(this, arguments);
+ }
+
+ klass.superclass = parent;
+ klass.subclasses = [ ];
+
+ if (parent) {
+ Subclass.prototype = parent.prototype;
+ klass.prototype = new Subclass();
+ parent.subclasses.push(klass);
+ }
+ for (var i = 0, length = properties.length; i < length; i++) {
+ addMethods(klass, properties[i], parent);
+ }
+ if (!klass.prototype.initialize) {
+ klass.prototype.initialize = emptyFunction;
+ }
+ klass.prototype.constructor = klass;
+ klass.prototype.callSuper = callSuper;
+ return klass;
+ }
+
+ fabric.util.createClass = createClass;
+})();
+
+
+(function () {
+
+ var unknown = 'unknown';
+
+ /* EVENT HANDLING */
+
+ function areHostMethods(object) {
+ var methodNames = Array.prototype.slice.call(arguments, 1),
+ t, i, len = methodNames.length;
+ for (i = 0; i < len; i++) {
+ t = typeof object[methodNames[i]];
+ if (!(/^(?:function|object|unknown)$/).test(t)) return false;
+ }
+ return true;
+ }
+ var getUniqueId = (function () {
+ var uid = 0;
+ return function (element) {
+ return element.__uniqueID || (element.__uniqueID = 'uniqueID__' + uid++);
+ };
+ })();
+
+ /** @ignore */
+ var getElement, setElement;
+
+ (function () {
+ var elements = { };
+ /** @ignore */
+ getElement = function (uid) {
+ return elements[uid];
+ };
+ /** @ignore */
+ setElement = function (uid, element) {
+ elements[uid] = element;
+ };
+ })();
+
+ function createListener(uid, handler) {
+ return {
+ handler: handler,
+ wrappedHandler: createWrappedHandler(uid, handler)
+ };
+ }
+
+ function createWrappedHandler(uid, handler) {
+ return function (e) {
+ handler.call(getElement(uid), e || fabric.window.event);
+ };
+ }
+
+ function createDispatcher(uid, eventName) {
+ return function (e) {
+ if (handlers[uid] && handlers[uid][eventName]) {
+ var handlersForEvent = handlers[uid][eventName];
+ for (var i = 0, len = handlersForEvent.length; i < len; i++) {
+ handlersForEvent[i].call(this, e || fabric.window.event);
+ }
+ }
+ };
+ }
+
+ var shouldUseAddListenerRemoveListener = (
+ areHostMethods(fabric.document.documentElement, 'addEventListener', 'removeEventListener') &&
+ areHostMethods(fabric.window, 'addEventListener', 'removeEventListener')),
+
+ shouldUseAttachEventDetachEvent = (
+ areHostMethods(fabric.document.documentElement, 'attachEvent', 'detachEvent') &&
+ areHostMethods(fabric.window, 'attachEvent', 'detachEvent')),
+
+ // IE branch
+ listeners = { },
+
+<<<<<<< HEAD
/*
----------------------------------------------------
Event.js : 1.1.1 : 2012/11/19 : MIT License
@@ -3660,1416 +3426,10 @@ Event.proxy = (function(root) {
* @param {Any} values Values to log
*/
fabric.log = function() { };
-
-/**
- * Wrapper around `console.warn` (when available)
- * @param {Any} Values to log as a warning
- */
-fabric.warn = function() { };
-
-if (typeof console !== 'undefined') {
- if (typeof console.log !== 'undefined' && console.log.apply) {
- fabric.log = function() {
- return console.log.apply(console, arguments);
- };
- }
- if (typeof console.warn !== 'undefined' && console.warn.apply) {
- fabric.warn = function() {
- return console.warn.apply(console, arguments);
- };
- }
-}
-
-
-(function(){
-
- /**
- * @private
- * @param {String} eventName
- * @param {Function} handler
- */
- function _removeEventListener(eventName, handler) {
- if (!this.__eventListeners[eventName]) return;
-
- if (handler) {
- fabric.util.removeFromArray(this.__eventListeners[eventName], handler);
- }
- else {
- this.__eventListeners[eventName].length = 0;
- }
- }
-
- /**
- * Observes specified event
- * @deprecated `observe` deprecated since 0.8.34 (use `on` instead)
- * @memberOf fabric.Observable
- * @alias on
- * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler})
- * @param {Function} handler Function that receives a notification when an event of the specified type occurs
- * @return {Self} thisArg
- * @chainable
- */
- function observe(eventName, handler) {
- if (!this.__eventListeners) {
- this.__eventListeners = { };
- }
- // one object with key/value pairs was passed
- if (arguments.length === 1) {
- for (var prop in eventName) {
- this.on(prop, eventName[prop]);
- }
- }
- else {
- if (!this.__eventListeners[eventName]) {
- this.__eventListeners[eventName] = [ ];
- }
- this.__eventListeners[eventName].push(handler);
- }
- return this;
- }
-
- /**
- * Stops event observing for a particular event handler
- * @deprecated `stopObserving` deprecated since 0.8.34 (use `off` instead)
- * @memberOf fabric.Observable
- * @alias off
- * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler})
- * @param {Function} handler Function to be deleted from EventListeners
- * @return {Self} thisArg
- * @chainable
- */
- function stopObserving(eventName, handler) {
- if (!this.__eventListeners) return;
-
- // one object with key/value pairs was passed
- if (arguments.length === 1 && typeof arguments[0] === 'object') {
- for (var prop in eventName) {
- _removeEventListener.call(this, prop, eventName[prop]);
- }
- }
- else {
- _removeEventListener.call(this, eventName, handler);
- }
- return this;
- }
-
- /**
- * Fires event with an optional options object
- * @deprecated `fire` deprecated since 1.0.7 (use `trigger` instead)
- * @memberOf fabric.Observable
- * @alias trigger
- * @param {String} eventName Event name to fire
- * @param {Object} [options] Options object
- * @return {Self} thisArg
- * @chainable
- */
- function fire(eventName, options) {
- if (!this.__eventListeners) return;
-
- var listenersForEvent = this.__eventListeners[eventName];
- if (!listenersForEvent) return;
- for (var i = 0, len = listenersForEvent.length; i < len; i++) {
- // avoiding try/catch for perf. reasons
- listenersForEvent[i].call(this, options || { });
- }
- return this;
- }
-
- /**
- * @namespace fabric.Observable
- * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#events}
- * @see {@link http://fabricjs.com/events/|Events demo}
- */
- fabric.Observable = {
- observe: observe,
- stopObserving: stopObserving,
- fire: fire,
-
- on: observe,
- off: stopObserving,
- trigger: fire
- };
-})();
-
-
-/**
- * @namespace fabric.Collection
- */
-fabric.Collection = {
-
- /**
- * Adds objects to collection, then renders canvas (if `renderOnAddRemove` is not `false`)
- * Objects should be instances of (or inherit from) fabric.Object
- * @param [...] Zero or more fabric instances
- * @return {Self} thisArg
- */
- add: function () {
- this._objects.push.apply(this._objects, arguments);
- for (var i = arguments.length; i--; ) {
- this._onObjectAdded(arguments[i]);
- }
- this.renderOnAddRemove && this.renderAll();
- return this;
- },
-
- /**
- * Inserts an object into collection at specified index, then renders canvas (if `renderOnAddRemove` is not `false`)
- * An object should be an instance of (or inherit from) fabric.Object
- * @param {Object} object Object to insert
- * @param {Number} index Index to insert object at
- * @param {Boolean} nonSplicing When `true`, no splicing (shifting) of objects occurs
- * @return {Self} thisArg
- */
- insertAt: function (object, index, nonSplicing) {
- var objects = this.getObjects();
- if (nonSplicing) {
- objects[index] = object;
- }
- else {
- objects.splice(index, 0, object);
- }
- this._onObjectAdded(object);
- this.renderOnAddRemove && this.renderAll();
- return this;
- },
-
- /**
- * Removes an object from a collection, then renders canvas (if `renderOnAddRemove` is not `false`)
- * @param {Object} object Object to remove
- * @return {Self} thisArg
- */
- remove: function(object) {
- var objects = this.getObjects(),
- index = objects.indexOf(object);
-
- // only call onObjectRemoved if an object was actually removed
- if (index !== -1) {
- objects.splice(index, 1);
- this._onObjectRemoved(object);
- }
-
- this.renderOnAddRemove && this.renderAll();
- return object;
- },
-
- /**
- * Executes given function for each object in this group
- * @param {Function} callback
- * Callback invoked with current object as first argument,
- * index - as second and an array of all objects - as third.
- * Iteration happens in reverse order (for performance reasons).
- * Callback is invoked in a context of Global Object (e.g. `window`)
- * when no `context` argument is given
- *
- * @param {Object} context Context (aka thisObject)
- * @return {Self} thisArg
- */
- forEachObject: function(callback, context) {
- var objects = this.getObjects(),
- i = objects.length;
- while (i--) {
- callback.call(context, objects[i], i, objects);
- }
- return this;
- },
-
- /**
- * Returns object at specified index
- * @param {Number} index
- * @return {Self} thisArg
- */
- item: function (index) {
- return this.getObjects()[index];
- },
-
- /**
- * Returns true if collection contains no objects
- * @return {Boolean} true if collection is empty
- */
- isEmpty: function () {
- return this.getObjects().length === 0;
- },
-
- /**
- * Returns a size of a collection (i.e: length of an array containing its objects)
- * @return {Number} Collection size
- */
- size: function() {
- return this.getObjects().length;
- },
-
- /**
- * Returns true if collection contains an object
- * @param {Object} object Object to check against
- * @return {Boolean} `true` if collection contains an object
- */
- contains: function(object) {
- return this.getObjects().indexOf(object) > -1;
- },
-
- /**
- * Returns number representation of a collection complexity
- * @return {Number} complexity
- */
- complexity: function () {
- return this.getObjects().reduce(function (memo, current) {
- memo += current.complexity ? current.complexity() : 0;
- return memo;
- }, 0);
- }
-};
-
-
-(function(global) {
-
- var sqrt = Math.sqrt,
- atan2 = Math.atan2;
-
- /**
- * @namespace fabric.util
- */
- fabric.util = { };
-
- /**
- * Removes value from an array.
- * Presence of value (and its position in an array) is determined via `Array.prototype.indexOf`
- * @static
- * @memberOf fabric.util
- * @param {Array} array
- * @param {Any} value
- * @return {Array} original array
- */
- function removeFromArray(array, value) {
- var idx = array.indexOf(value);
- if (idx !== -1) {
- array.splice(idx, 1);
- }
- return array;
- }
-
- /**
- * Returns random number between 2 specified ones.
- * @static
- * @memberOf fabric.util
- * @param {Number} min lower limit
- * @param {Number} max upper limit
- * @return {Number} random value (between min and max)
- */
- function getRandomInt(min, max) {
- return Math.floor(Math.random() * (max - min + 1)) + min;
- }
-
- var PiBy180 = Math.PI / 180;
-
- /**
- * Transforms degrees to radians.
- * @static
- * @memberOf fabric.util
- * @param {Number} degrees value in degrees
- * @return {Number} value in radians
- */
- function degreesToRadians(degrees) {
- return degrees * PiBy180;
- }
-
- /**
- * Transforms radians to degrees.
- * @static
- * @memberOf fabric.util
- * @param {Number} radians value in radians
- * @return {Number} value in degrees
- */
- function radiansToDegrees(radians) {
- return radians / PiBy180;
- }
-
- /**
- * Rotates `point` around `origin` with `radians`
- * @static
- * @memberOf fabric.util
- * @param {fabric.Point} The point to rotate
- * @param {fabric.Point} The origin of the rotation
- * @param {Number} The radians of the angle for the rotation
- * @return {fabric.Point} The new rotated point
- */
- function rotatePoint(point, origin, radians) {
- var sin = Math.sin(radians),
- cos = Math.cos(radians);
-
- point.subtractEquals(origin);
-
- var rx = point.x * cos - point.y * sin;
- var ry = point.x * sin + point.y * cos;
-
- return new fabric.Point(rx, ry).addEquals(origin);
- }
-
- /**
- * Apply transform t to point p
- * @static
- * @memberOf fabric.util
- * @param {fabric.Point} p The point to transform
- * @param {Array} t The transform
- * @param {Boolean} [ignoreOffset] Indicates that the offset should not be applied
- * @return {fabric.Point} The transformed point
- */
- function transformPoint(p, t, ignoreOffset) {
- if (ignoreOffset) {
- return new fabric.Point(
- t[0] * p.x + t[1] * p.y,
- t[2] * p.x + t[3] * p.y
- );
- }
- return new fabric.Point(
- t[0] * p.x + t[1] * p.y + t[4],
- t[2] * p.x + t[3] * p.y + t[5]
- );
- }
-
- /**
- * Invert transformation t
- * @static
- * @memberOf fabric.util
- * @param {Array} t The transform
- * @return {Array} The inverted transform
- */
- function invertTransform(t) {
- var r = t.slice(),
- a = 1 / (t[0] * t[3] - t[1] * t[2]);
- r = [a * t[3], -a * t[1], -a * t[2], a * t[0], 0, 0];
- var o = transformPoint({x: t[4], y: t[5]}, r);
- r[4] = -o.x;
- r[5] = -o.y;
- return r
- }
-
- /**
- * A wrapper around Number#toFixed, which contrary to native method returns number, not string.
- * @static
- * @memberOf fabric.util
- * @param {Number | String} number number to operate on
- * @param {Number} fractionDigits number of fraction digits to "leave"
- * @return {Number}
- */
- function toFixed(number, fractionDigits) {
- return parseFloat(Number(number).toFixed(fractionDigits));
- }
-
- /**
- * Function which always returns `false`.
- * @static
- * @memberOf fabric.util
- * @return {Boolean}
- */
- function falseFunction() {
- return false;
- }
-
- /**
- * Returns klass "Class" object of given namespace
- * @memberOf fabric.util
- * @param {String} type Type of object (eg. 'circle')
- * @param {String} namespace Namespace to get klass "Class" object from
- * @return {Object} klass "Class"
- */
- function getKlass(type, namespace) {
- // capitalize first letter only
- type = fabric.util.string.camelize(type.charAt(0).toUpperCase() + type.slice(1));
- return resolveNamespace(namespace)[type];
- }
-
- /**
- * Returns object of given namespace
- * @memberOf fabric.util
- * @param {String} namespace Namespace string e.g. 'fabric.Image.filter' or 'fabric'
- * @return {Object} Object for given namespace (default fabric)
- */
- function resolveNamespace(namespace) {
- if (!namespace) return fabric;
-
- var parts = namespace.split('.'),
- len = parts.length,
- obj = global || fabric.window;
-
- for (var i = 0; i < len; ++i) {
- obj = obj[parts[i]];
- }
-
- return obj;
- }
-
- /**
- * Loads image element from given url and passes it to a callback
- * @memberOf fabric.util
- * @param {String} url URL representing an image
- * @param {Function} callback Callback; invoked with loaded image
- * @param {Any} context optional Context to invoke callback in
- */
- function loadImage(url, callback, context) {
- if (url) {
- var img = fabric.util.createImage();
- /** @ignore */
- img.onload = function () {
- callback && callback.call(context, img);
- img = img.onload = null;
- };
- img.src = url;
- }
- else {
- callback && callback.call(context, url);
- }
- }
-
- /**
- * Creates corresponding fabric instances from their object representations
- * @static
- * @memberOf fabric.util
- * @param {Array} objects Objects to enliven
- * @param {Function} callback Callback to invoke when all objects are created
- * @param {Function} [reviver] Method for further parsing of object elements, called after each fabric object created.
- */
- function enlivenObjects(objects, callback, namespace, reviver) {
-
- function onLoaded() {
- if (++numLoadedObjects === numTotalObjects) {
- if (callback) {
- callback(enlivenedObjects);
- }
- }
- }
-
- var enlivenedObjects = [ ],
- numLoadedObjects = 0,
- numTotalObjects = objects.length;
-
- objects.forEach(function (o, index) {
- // if sparse array
- if (!o || !o.type) {
- onLoaded();
- return;
- }
- var klass = fabric.util.getKlass(o.type, namespace);
- if (klass.async) {
- klass.fromObject(o, function (obj, error) {
- if (!error) {
- enlivenedObjects[index] = obj;
- reviver && reviver(o, enlivenedObjects[index]);
- }
- onLoaded();
- });
- }
- else {
- enlivenedObjects[index] = klass.fromObject(o);
- reviver && reviver(o, enlivenedObjects[index]);
- onLoaded();
- }
- });
- }
-
- /**
- * Groups SVG elements (usually those retrieved from SVG document)
- * @static
- * @memberOf fabric.util
- * @param {Array} elements SVG elements to group
- * @param {Object} [options] Options object
- * @return {fabric.Object|fabric.PathGroup}
- */
- function groupSVGElements(elements, options, path) {
- var object;
-
- if (elements.length > 1) {
- object = new fabric.PathGroup(elements, options);
- }
- else {
- object = elements[0];
- }
-
- if (typeof path !== 'undefined') {
- object.setSourcePath(path);
- }
- return object;
- }
-
- /**
- * Populates an object with properties of another object
- * @static
- * @memberOf fabric.util
- * @param {Object} source Source object
- * @param {Object} destination Destination object
- * @return {Array} properties Propertie names to include
- */
- function populateWithProperties(source, destination, properties) {
- if (properties && Object.prototype.toString.call(properties) === '[object Array]') {
- for (var i = 0, len = properties.length; i < len; i++) {
- if (properties[i] in source) {
- destination[properties[i]] = source[properties[i]];
- }
- }
- }
- }
-
- /**
- * Draws a dashed line between two points
- *
- * This method is used to draw dashed line around selection area.
- * See dotted stroke in canvas
- *
- * @param ctx {Canvas} context
- * @param x {Number} start x coordinate
- * @param y {Number} start y coordinate
- * @param x2 {Number} end x coordinate
- * @param y2 {Number} end y coordinate
- * @param da {Array} dash array pattern
- */
- function drawDashedLine(ctx, x, y, x2, y2, da) {
- var dx = x2 - x,
- dy = y2 - y,
- len = sqrt(dx*dx + dy*dy),
- rot = atan2(dy, dx),
- dc = da.length,
- di = 0,
- draw = true;
-
- ctx.save();
- ctx.translate(x, y);
- ctx.moveTo(0, 0);
- ctx.rotate(rot);
-
- x = 0;
- while (len > x) {
- x += da[di++ % dc];
- if (x > len) {
- x = len;
- }
- ctx[draw ? 'lineTo' : 'moveTo'](x, 0);
- draw = !draw;
- }
-
- ctx.restore();
- }
-
- /**
- * Creates canvas element and initializes it via excanvas if necessary
- * @static
- * @memberOf fabric.util
- * @param {CanvasElement} [canvasEl] optional canvas element to initialize; when not given, element is created implicitly
- * @return {CanvasElement} initialized canvas element
- */
- function createCanvasElement(canvasEl) {
- canvasEl || (canvasEl = fabric.document.createElement('canvas'));
- if (!canvasEl.getContext && typeof G_vmlCanvasManager !== 'undefined') {
- G_vmlCanvasManager.initElement(canvasEl);
- }
- return canvasEl;
- }
-
- /**
- * Creates image element (works on client and node)
- * @static
- * @memberOf fabric.util
- * @return {HTMLImageElement} HTML image element
- */
- function createImage() {
- return fabric.isLikelyNode
- ? new (require('canvas').Image)()
- : fabric.document.createElement('img');
- }
-
- /**
- * Creates accessors (getXXX, setXXX) for a "class", based on "stateProperties" array
- * @static
- * @memberOf fabric.util
- * @param {Object} klass "Class" to create accessors for
- */
- function createAccessors(klass) {
- var proto = klass.prototype;
-
- for (var i = proto.stateProperties.length; i--; ) {
-
- var propName = proto.stateProperties[i],
- capitalizedPropName = propName.charAt(0).toUpperCase() + propName.slice(1),
- setterName = 'set' + capitalizedPropName,
- getterName = 'get' + capitalizedPropName;
-
- // using `new Function` for better introspection
- if (!proto[getterName]) {
- proto[getterName] = (function(property) {
- return new Function('return this.get("' + property + '")');
- })(propName);
- }
- if (!proto[setterName]) {
- proto[setterName] = (function(property) {
- return new Function('value', 'return this.set("' + property + '", value)');
- })(propName);
- }
- }
- }
-
- /**
- * @static
- * @memberOf fabric.util
- * @param {fabric.Object} receiver Object implementing `clipTo` method
- * @param {CanvasRenderingContext2D} ctx Context to clip
- */
- function clipContext(receiver, ctx) {
- ctx.save();
- ctx.beginPath();
- receiver.clipTo(ctx);
- ctx.clip();
- }
-
- /**
- * Multiply matrix A by matrix B to nest transformations
- * @static
- * @memberOf fabric.util
- * @param {Array} matrixA First transformMatrix
- * @param {Array} matrixB Second transformMatrix
- * @return {Array} The product of the two transform matrices
- */
- function multiplyTransformMatrices(matrixA, matrixB) {
- // Matrix multiply matrixA * matrixB
- var a = [
- [matrixA[0], matrixA[2], matrixA[4]],
- [matrixA[1], matrixA[3], matrixA[5]],
- [0 , 0 , 1 ]
- ];
-
- var b = [
- [matrixB[0], matrixB[2], matrixB[4]],
- [matrixB[1], matrixB[3], matrixB[5]],
- [0 , 0 , 1 ]
- ];
-
- var result = [];
- for (var r=0; r<3; r++) {
- result[r] = [];
- for (var c=0; c<3; c++) {
- var sum = 0;
- for (var k=0; k<3; k++) {
- sum += a[r][k]*b[k][c];
- }
-
- result[r][c] = sum;
- }
- }
-
- return [
- result[0][0],
- result[1][0],
- result[0][1],
- result[1][1],
- result[0][2],
- result[1][2]
- ];
- }
-
- function getFunctionBody(fn) {
- return (String(fn).match(/function[^{]*\{([\s\S]*)\}/) || {})[1];
- }
-
- function drawArc(ctx, x, y, coords) {
- var rx = coords[0];
- var ry = coords[1];
- var rot = coords[2];
- var large = coords[3];
- var sweep = coords[4];
- var ex = coords[5];
- var ey = coords[6];
- var segs = arcToSegments(ex, ey, rx, ry, large, sweep, rot, x, y);
- for (var i=0; i 1) {
- pl = Math.sqrt(pl);
- rx *= pl;
- ry *= pl;
- }
-
- var a00 = cos_th / rx;
- var a01 = sin_th / rx;
- var a10 = (-sin_th) / ry;
- var a11 = (cos_th) / ry;
- var x0 = a00 * ox + a01 * oy;
- var y0 = a10 * ox + a11 * oy;
- var x1 = a00 * x + a01 * y;
- var y1 = a10 * x + a11 * y;
-
- var d = (x1-x0) * (x1-x0) + (y1-y0) * (y1-y0);
- var sfactor_sq = 1 / d - 0.25;
- if (sfactor_sq < 0) sfactor_sq = 0;
- var sfactor = Math.sqrt(sfactor_sq);
- if (sweep === large) sfactor = -sfactor;
- var xc = 0.5 * (x0 + x1) - sfactor * (y1-y0);
- var yc = 0.5 * (y0 + y1) + sfactor * (x1-x0);
-
- var th0 = Math.atan2(y0-yc, x0-xc);
- var th1 = Math.atan2(y1-yc, x1-xc);
-
- var th_arc = th1-th0;
- if (th_arc < 0 && sweep === 1){
- th_arc += 2*Math.PI;
- } else if (th_arc > 0 && sweep === 0) {
- th_arc -= 2 * Math.PI;
- }
-
- var segments = Math.ceil(Math.abs(th_arc / (Math.PI * 0.5 + 0.001)));
- var result = [];
- for (var i=0; i>> 0;
- if (len === 0) {
- return -1;
- }
- var n = 0;
- if (arguments.length > 0) {
- n = Number(arguments[1]);
- if (n !== n) { // shortcut for verifying if it's NaN
- n = 0;
- }
- else if (n !== 0 && n !== Number.POSITIVE_INFINITY && n !== Number.NEGATIVE_INFINITY) {
- n = (n > 0 || -1) * Math.floor(Math.abs(n));
- }
- }
- if (n >= len) {
- return -1;
- }
- var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
- for (; k < len; k++) {
- if (k in t && t[k] === searchElement) {
- return k;
- }
- }
- return -1;
- };
- }
-
- if (!Array.prototype.forEach) {
- /**
- * Iterates an array, invoking callback for each element
- * @param {Function} fn Callback to invoke for each element
- * @param {Object} [context] Context to invoke callback in
- * @return {Array}
- */
- Array.prototype.forEach = function(fn, context) {
- for (var i = 0, len = this.length >>> 0; i < len; i++) {
- if (i in this) {
- fn.call(context, this[i], i, this);
- }
- }
- };
- }
-
- if (!Array.prototype.map) {
- /**
- * Returns a result of iterating over an array, invoking callback for each element
- * @param {Function} fn Callback to invoke for each element
- * @param {Object} [context] Context to invoke callback in
- * @return {Array}
- */
- Array.prototype.map = function(fn, context) {
- var result = [ ];
- for (var i = 0, len = this.length >>> 0; i < len; i++) {
- if (i in this) {
- result[i] = fn.call(context, this[i], i, this);
- }
- }
- return result;
- };
- }
-
- if (!Array.prototype.every) {
- /**
- * Returns true if a callback returns truthy value for all elements in an array
- * @param {Function} fn Callback to invoke for each element
- * @param {Object} [context] Context to invoke callback in
- * @return {Boolean}
- */
- Array.prototype.every = function(fn, context) {
- for (var i = 0, len = this.length >>> 0; i < len; i++) {
- if (i in this && !fn.call(context, this[i], i, this)) {
- return false;
- }
- }
- return true;
- };
- }
-
- if (!Array.prototype.some) {
- /**
- * Returns true if a callback returns truthy value for at least one element in an array
- * @param {Function} fn Callback to invoke for each element
- * @param {Object} [context] Context to invoke callback in
- * @return {Boolean}
- */
- Array.prototype.some = function(fn, context) {
- for (var i = 0, len = this.length >>> 0; i < len; i++) {
- if (i in this && fn.call(context, this[i], i, this)) {
- return true;
- }
- }
- return false;
- };
- }
-
- if (!Array.prototype.filter) {
- /**
- * Returns the result of iterating over elements in an array
- * @param {Function} fn Callback to invoke for each element
- * @param {Object} [context] Context to invoke callback in
- * @return {Array}
- */
- Array.prototype.filter = function(fn, context) {
- var result = [ ], val;
- for (var i = 0, len = this.length >>> 0; i < len; i++) {
- if (i in this) {
- val = this[i]; // in case fn mutates this
- if (fn.call(context, val, i, this)) {
- result.push(val);
- }
- }
- }
- return result;
- };
- }
-
- if (!Array.prototype.reduce) {
- /**
- * Returns "folded" (reduced) result of iterating over elements in an array
- * @param {Function} fn Callback to invoke for each element
- * @param {Object} [context] Context to invoke callback in
- * @return {Any}
- */
- Array.prototype.reduce = function(fn /*, initial*/) {
- var len = this.length >>> 0,
- i = 0,
- rv;
-
- if (arguments.length > 1) {
- rv = arguments[1];
- }
- else {
- do {
- if (i in this) {
- rv = this[i++];
- break;
- }
- // if array contains no values, no initial value to return
- if (++i >= len) {
- throw new TypeError();
- }
- }
- while (true);
- }
- for (; i < len; i++) {
- if (i in this) {
- rv = fn.call(null, rv, this[i], i, this);
- }
- }
- return rv;
- };
- }
-
- /* _ES5_COMPAT_END_ */
-
- /**
- * Invokes method on all items in a given array
- * @memberOf fabric.util.array
- * @param {Array} array Array to iterate over
- * @param {String} method Name of a method to invoke
- * @return {Array}
- */
- function invoke(array, method) {
- var args = slice.call(arguments, 2), result = [ ];
- for (var i = 0, len = array.length; i < len; i++) {
- result[i] = args.length ? array[i][method].apply(array[i], args) : array[i][method].call(array[i]);
- }
- return result;
- }
-
- /**
- * Finds maximum value in array (not necessarily "first" one)
- * @memberOf fabric.util.array
- * @param {Array} array Array to iterate over
- * @param {String} byProperty
- * @return {Any}
- */
- function max(array, byProperty) {
- if (!array || array.length === 0) return undefined;
-
- var i = array.length - 1,
- result = byProperty ? array[i][byProperty] : array[i];
- if (byProperty) {
- while (i--) {
- if (array[i][byProperty] >= result) {
- result = array[i][byProperty];
- }
- }
- }
- else {
- while (i--) {
- if (array[i] >= result) {
- result = array[i];
- }
- }
- }
- return result;
- }
-
- /**
- * Finds minimum value in array (not necessarily "first" one)
- * @memberOf fabric.util.array
- * @param {Array} array Array to iterate over
- * @param {String} byProperty
- * @return {Any}
- */
- function min(array, byProperty) {
- if (!array || array.length === 0) return undefined;
-
- var i = array.length - 1,
- result = byProperty ? array[i][byProperty] : array[i];
-
- if (byProperty) {
- while (i--) {
- if (array[i][byProperty] < result) {
- result = array[i][byProperty];
- }
- }
- }
- else {
- while (i--) {
- if (array[i] < result) {
- result = array[i];
- }
- }
- }
- return result;
- }
-
- /**
- * @namespace fabric.util.array
- */
- fabric.util.array = {
- invoke: invoke,
- min: min,
- max: max
- };
-
-})();
-
-
-(function(){
-
- /**
- * Copies all enumerable properties of one object to another
- * @memberOf fabric.util.object
- * @param {Object} destination Where to copy to
- * @param {Object} source Where to copy from
- * @return {Object}
- */
- function extend(destination, source) {
- // JScript DontEnum bug is not taken care of
- for (var property in source) {
- destination[property] = source[property];
- }
- return destination;
- }
-
- /**
- * Creates an empty object and copies all enumerable properties of another object to it
- * @memberOf fabric.util.object
- * @param {Object} object Object to clone
- * @return {Object}
- */
- function clone(object) {
- return extend({ }, object);
- }
-
- /** @namespace fabric.util.object */
- fabric.util.object = {
- extend: extend,
- clone: clone
- };
-
-})();
-
-
-(function() {
-
-/* _ES5_COMPAT_START_ */
-if (!String.prototype.trim) {
- /**
- * Trims a string (removing whitespace from the beginning and the end)
- * @function external:String#trim
- * @see String#trim on MDN
- */
- String.prototype.trim = function () {
- // this trim is not fully ES3 or ES5 compliant, but it should cover most cases for now
- return this.replace(/^[\s\xA0]+/, '').replace(/[\s\xA0]+$/, '');
- };
-}
-/* _ES5_COMPAT_END_ */
-
-/**
- * Camelizes a string
- * @memberOf fabric.util.string
- * @param {String} string String to camelize
- * @return {String} Camelized version of a string
- */
-function camelize(string) {
- return string.replace(/-+(.)?/g, function(match, character) {
- return character ? character.toUpperCase() : '';
- });
-}
-
-/**
- * Capitalizes a string
- * @memberOf fabric.util.string
- * @param {String} string String to capitalize
- * @return {String} Capitalized version of a string
- */
-function capitalize(string) {
- return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
-}
-
-/**
- * Escapes XML in a string
- * @memberOf fabric.util.string
- * @param {String} string String to escape
- * @return {String} Escaped version of a string
- */
-function escapeXml(string) {
- return string.replace(/&/g, '&')
- .replace(/"/g, '"')
- .replace(/'/g, ''')
- .replace(//g, '>');
-}
-
-/**
- * String utilities
- * @namespace fabric.util.string
- */
-fabric.util.string = {
- camelize: camelize,
- capitalize: capitalize,
- escapeXml: escapeXml
-};
-}());
-
-
-/* _ES5_COMPAT_START_ */
-(function() {
-
- var slice = Array.prototype.slice,
- apply = Function.prototype.apply,
- Dummy = function() { };
-
- if (!Function.prototype.bind) {
- /**
- * Cross-browser approximation of ES5 Function.prototype.bind (not fully spec conforming)
- * @see Function#bind on MDN
- * @param {Object} thisArg Object to bind function to
- * @param {Any[]} [...] Values to pass to a bound function
- * @return {Function}
- */
- Function.prototype.bind = function(thisArg) {
- var fn = this, args = slice.call(arguments, 1), bound;
- if (args.length) {
- bound = function() {
- return apply.call(fn, this instanceof Dummy ? this : thisArg, args.concat(slice.call(arguments)));
- };
- }
- else {
- /** @ignore */
- bound = function() {
- return apply.call(fn, this instanceof Dummy ? this : thisArg, arguments);
- };
- }
- Dummy.prototype = this.prototype;
- bound.prototype = new Dummy();
-
- return bound;
- };
- }
-
-})();
-/* _ES5_COMPAT_END_ */
-
-
-(function() {
-
- var slice = Array.prototype.slice, emptyFunction = function() { };
-
- var IS_DONTENUM_BUGGY = (function(){
- for (var p in { toString: 1 }) {
- if (p === 'toString') return false;
- }
- return true;
- })();
-
- /** @ignore */
- var addMethods = function(klass, source, parent) {
- for (var property in source) {
-
- if (property in klass.prototype &&
- typeof klass.prototype[property] === 'function' &&
- (source[property] + '').indexOf('callSuper') > -1) {
-
- klass.prototype[property] = (function(property) {
- return function() {
-
- var superclass = this.constructor.superclass;
- this.constructor.superclass = parent;
- var returnValue = source[property].apply(this, arguments);
- this.constructor.superclass = superclass;
-
- if (property !== 'initialize') {
- return returnValue;
- }
- };
- })(property);
- }
- else {
- klass.prototype[property] = source[property];
- }
-
- if (IS_DONTENUM_BUGGY) {
- if (source.toString !== Object.prototype.toString) {
- klass.prototype.toString = source.toString;
- }
- if (source.valueOf !== Object.prototype.valueOf) {
- klass.prototype.valueOf = source.valueOf;
- }
- }
- }
- };
-
- function Subclass() { }
-
- function callSuper(methodName) {
- var fn = this.constructor.superclass.prototype[methodName];
- return (arguments.length > 1)
- ? fn.apply(this, slice.call(arguments, 1))
- : fn.call(this);
- }
-
- /**
- * Helper for creation of "classes".
- * @memberOf fabric.util
- * @param parent optional "Class" to inherit from
- * @param properties Properties shared by all instances of this class
- * (be careful modifying objects defined here as this would affect all instances)
- */
- function createClass() {
- var parent = null,
- properties = slice.call(arguments, 0);
-
- if (typeof properties[0] === 'function') {
- parent = properties.shift();
- }
- function klass() {
- this.initialize.apply(this, arguments);
- }
-
- klass.superclass = parent;
- klass.subclasses = [ ];
-
- if (parent) {
- Subclass.prototype = parent.prototype;
- klass.prototype = new Subclass();
- parent.subclasses.push(klass);
- }
- for (var i = 0, length = properties.length; i < length; i++) {
- addMethods(klass, properties[i], parent);
- }
- if (!klass.prototype.initialize) {
- klass.prototype.initialize = emptyFunction;
- }
- klass.prototype.constructor = klass;
- klass.prototype.callSuper = callSuper;
- return klass;
- }
-
- fabric.util.createClass = createClass;
-})();
-
-
-(function () {
-
- /* EVENT HANDLING */
-
- function areHostMethods(object) {
- var methodNames = Array.prototype.slice.call(arguments, 1),
- t, i, len = methodNames.length;
- for (i = 0; i < len; i++) {
- t = typeof object[methodNames[i]];
- if (!(/^(?:function|object|unknown)$/).test(t)) return false;
- }
- return true;
- }
- var getUniqueId = (function () {
- var uid = 0;
- return function (element) {
- return element.__uniqueID || (element.__uniqueID = 'uniqueID__' + uid++);
- };
- })();
-
- /** @ignore */
- var getElement, setElement;
-
- (function () {
- var elements = { };
- /** @ignore */
- getElement = function (uid) {
- return elements[uid];
- };
- /** @ignore */
- setElement = function (uid, element) {
- elements[uid] = element;
- };
- })();
-
- function createListener(uid, handler) {
- return {
- handler: handler,
- wrappedHandler: createWrappedHandler(uid, handler)
- };
- }
-
- function createWrappedHandler(uid, handler) {
- return function (e) {
- handler.call(getElement(uid), e || fabric.window.event);
- };
- }
-
- function createDispatcher(uid, eventName) {
- return function (e) {
- if (handlers[uid] && handlers[uid][eventName]) {
- var handlersForEvent = handlers[uid][eventName];
- for (var i = 0, len = handlersForEvent.length; i < len; i++) {
- handlersForEvent[i].call(this, e || fabric.window.event);
- }
- }
- };
- }
-
- var shouldUseAddListenerRemoveListener = (
- areHostMethods(fabric.document.documentElement, 'addEventListener', 'removeEventListener') &&
- areHostMethods(fabric.window, 'addEventListener', 'removeEventListener')),
-
- shouldUseAttachEventDetachEvent = (
- areHostMethods(fabric.document.documentElement, 'attachEvent', 'detachEvent') &&
- areHostMethods(fabric.window, 'attachEvent', 'detachEvent')),
-
- // IE branch
- listeners = { },
-
+=======
// DOM L0 branch
handlers = { },
+>>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f
addListener, removeListener;
@@ -5174,41 +3534,14 @@ fabric.util.string = {
function getPointer(event, upperCanvasEl) {
event || (event = fabric.window.event);
- var element = event.target || (typeof event.srcElement !== 'unknown' ? event.srcElement : null),
- body = fabric.document.body || {scrollLeft: 0, scrollTop: 0},
- docElement = fabric.document.documentElement,
- orgElement = element,
- scrollLeft = 0,
- scrollTop = 0,
- firstFixedAncestor;
+ var element = event.target ||
+ (typeof event.srcElement !== unknown ? event.srcElement : null);
- while (element && element.parentNode && !firstFixedAncestor) {
- element = element.parentNode;
-
- if (element !== fabric.document &&
- fabric.util.getElementStyle(element, 'position') === 'fixed') {
- firstFixedAncestor = element;
- }
-
- if (element !== fabric.document &&
- orgElement !== upperCanvasEl &&
- fabric.util.getElementStyle(element, 'position') === 'absolute') {
- scrollLeft = 0;
- scrollTop = 0;
- }
- else if (element === fabric.document) {
- scrollLeft = body.scrollLeft || docElement.scrollLeft || 0;
- scrollTop = body.scrollTop || docElement.scrollTop || 0;
- }
- else {
- scrollLeft += element.scrollLeft || 0;
- scrollTop += element.scrollTop || 0;
- }
- }
+ var scroll = fabric.util.getScrollLeftTop(element, upperCanvasEl);
return {
- x: pointerX(event) + scrollLeft,
- y: pointerY(event) + scrollTop
+ x: pointerX(event) + scroll.left,
+ y: pointerY(event) + scroll.top
};
}
@@ -5216,29 +3549,28 @@ fabric.util.string = {
// looks like in IE (<9) clientX at certain point (apparently when mouseup fires on VML element)
// is represented as COM object, with all the consequences, like "unknown" type and error on [[Get]]
// need to investigate later
- return (typeof event.clientX !== 'unknown' ? event.clientX : 0);
+ return (typeof event.clientX !== unknown ? event.clientX : 0);
};
var pointerY = function(event) {
- return (typeof event.clientY !== 'unknown' ? event.clientY : 0);
+ return (typeof event.clientY !== unknown ? event.clientY : 0);
};
+ function _getPointer(event, pageProp, clientProp) {
+ var touchProp = event.type === 'touchend' ? 'changedTouches' : 'touches';
+
+ return (event[touchProp] && event[touchProp][0]
+ ? (event[touchProp][0][pageProp] - (event[touchProp][0][pageProp] - event[touchProp][0][clientProp]))
+ || event[clientProp]
+ : event[clientProp]);
+ }
+
if (fabric.isTouchSupported) {
pointerX = function(event) {
- if (event.type !== 'touchend') {
- return (event.touches && event.touches[0] ?
- (event.touches[0].pageX - (event.touches[0].pageX - event.touches[0].clientX)) || event.clientX : event.clientX);
- }
- return (event.changedTouches && event.changedTouches[0]
- ? (event.changedTouches[0].pageX - (event.changedTouches[0].pageX - event.changedTouches[0].clientX)) || event.clientX : event.clientX);
+ return _getPointer(event, 'pageX', 'clientX');
};
pointerY = function(event) {
- if (event.type !== 'touchend') {
- return (event.touches && event.touches[0]
- ? (event.touches[0].pageY - (event.touches[0].pageY - event.touches[0].clientY)) || event.clientY : event.clientY);
- }
- return (event.changedTouches && event.changedTouches[0]
- ? (event.changedTouches[0].pageY - (event.changedTouches[0].pageY - event.changedTouches[0].clientY)) || event.clientY : event.clientY);
+ return _getPointer(event, 'pageY', 'clientY');
};
}
@@ -5395,9 +3727,53 @@ fabric.util.string = {
element.className += (element.className ? ' ' : '') + className;
}
}
+
+ /**
+ * Apply transform t to point p
+ * @static
+ * @memberOf fabric.util
+ * @param {fabric.Point} p The point to transform
+ * @param {Array} t The transform
+ * @param {Boolean} [ignoreOffset] Indicates that the offset should not be applied
+ * @return {fabric.Point} The transformed point
+ */
+ function transformPoint(p, t, ignoreOffset) {
+ if (ignoreOffset) {
+ return new fabric.Point(
+ t[0] * p.x + t[1] * p.y,
+ t[2] * p.x + t[3] * p.y
+ );
+ }
+ return new fabric.Point(
+ t[0] * p.x + t[1] * p.y + t[4],
+ t[2] * p.x + t[3] * p.y + t[5]
+ );
+ }
/**
+<<<<<<< HEAD
+ * Invert transformation t
+ * @static
+ * @memberOf fabric.util
+ * @param {Array} t The transform
+ * @return {Array} The inverted transform
+ */
+ function invertTransform(t) {
+ var r = t.slice(),
+ a = 1 / (t[0] * t[3] - t[1] * t[2]);
+ r = [a * t[3], -a * t[1], -a * t[2], a * t[0], 0, 0];
+ var o = transformPoint({x: t[4], y: t[5]}, r);
+ r[4] = -o.x;
+ r[5] = -o.y;
+ return r
+ }
+
+ /**
+ * A wrapper around Number#toFixed, which contrary to native method returns number, not string.
+ * @static
+=======
* Wraps element with another element
+>>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f
* @memberOf fabric.util
* @param {HTMLElement} element Element to wrap
* @param {HTMLElement|String} wrapper Element to wrap with
@@ -5414,6 +3790,47 @@ fabric.util.string = {
wrapper.appendChild(element);
return wrapper;
}
+
+ function getScrollLeftTop(element, upperCanvasEl) {
+
+ var firstFixedAncestor,
+ origElement,
+ left = 0,
+ top = 0,
+ docElement = fabric.document.documentElement,
+ body = fabric.document.body || {
+ scrollLeft: 0, scrollTop: 0
+ };
+
+ origElement = element;
+
+ while (element && element.parentNode && !firstFixedAncestor) {
+
+ element = element.parentNode;
+
+ if (element !== fabric.document &&
+ fabric.util.getElementStyle(element, 'position') === 'fixed') {
+ firstFixedAncestor = element;
+ }
+
+ if (element !== fabric.document &&
+ origElement !== upperCanvasEl &&
+ fabric.util.getElementStyle(element, 'position') === 'absolute') {
+ left = 0;
+ top = 0;
+ }
+ else if (element === fabric.document) {
+ left = body.scrollLeft || docElement.scrollLeft || 0;
+ top = body.scrollTop || docElement.scrollTop || 0;
+ }
+ else {
+ left += element.scrollLeft || 0;
+ top += element.scrollTop || 0;
+ }
+ }
+
+ return { left: left, top: top };
+ }
/**
* Returns offset for a given element
@@ -5423,10 +3840,11 @@ fabric.util.string = {
* @return {Object} Object with "left" and "top" properties
*/
function getElementOffset(element) {
- var docElem, win,
+ var docElem,
box = {left: 0, top: 0},
doc = element && element.ownerDocument,
offset = {left: 0, top: 0},
+ scrollLeftTop,
offsetAttributes = {
'borderLeftWidth': 'left',
'borderTopWidth': 'top',
@@ -5446,14 +3864,12 @@ fabric.util.string = {
if ( typeof element.getBoundingClientRect !== "undefined" ) {
box = element.getBoundingClientRect();
}
- if(doc != null && doc === doc.window){
- win = doc;
- } else {
- win = doc.nodeType === 9 && (doc.defaultView || doc.parentWindow);
- }
+
+ scrollLeftTop = fabric.util.getScrollLeftTop(element, null);
+
return {
- left: box.left + (win.pageXOffset || docElem.scrollLeft) - (docElem.clientLeft || 0) + offset.left,
- top: box.top + (win.pageYOffset || docElem.scrollTop) - (docElem.clientTop || 0) + offset.top
+ left: box.left + scrollLeftTop.left - (docElem.clientLeft || 0) + offset.left,
+ top: box.top + scrollLeftTop.top - (docElem.clientTop || 0) + offset.top
};
}
@@ -5572,6 +3988,7 @@ fabric.util.string = {
fabric.util.makeElement = makeElement;
fabric.util.addClass = addClass;
fabric.util.wrapElement = wrapElement;
+ fabric.util.getScrollLeftTop = getScrollLeftTop;
fabric.util.getElementOffset = getElementOffset;
fabric.util.getElementStyle = getElementStyle;
@@ -5651,6 +4068,55 @@ fabric.util.string = {
})();
+/**
+ * Wrapper around `console.log` (when available)
+ * @param {Any} values Values to log
+ */
+fabric.log = function() { };
+
+<<<<<<< HEAD
+ fabric.util.removeFromArray = removeFromArray;
+ fabric.util.degreesToRadians = degreesToRadians;
+ fabric.util.radiansToDegrees = radiansToDegrees;
+ fabric.util.rotatePoint = rotatePoint;
+ fabric.util.transformPoint = transformPoint;
+ fabric.util.invertTransform = invertTransform;
+ fabric.util.toFixed = toFixed;
+ fabric.util.getRandomInt = getRandomInt;
+ fabric.util.falseFunction = falseFunction;
+ fabric.util.getKlass = getKlass;
+ fabric.util.resolveNamespace = resolveNamespace;
+ fabric.util.loadImage = loadImage;
+ fabric.util.enlivenObjects = enlivenObjects;
+ fabric.util.groupSVGElements = groupSVGElements;
+ fabric.util.populateWithProperties = populateWithProperties;
+ fabric.util.drawDashedLine = drawDashedLine;
+ fabric.util.createCanvasElement = createCanvasElement;
+ fabric.util.createImage = createImage;
+ fabric.util.createAccessors = createAccessors;
+ fabric.util.clipContext = clipContext;
+ fabric.util.multiplyTransformMatrices = multiplyTransformMatrices;
+ fabric.util.getFunctionBody = getFunctionBody;
+ fabric.util.drawArc = drawArc;
+=======
+/**
+ * Wrapper around `console.warn` (when available)
+ * @param {Any} Values to log as a warning
+ */
+fabric.warn = function() { };
+>>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f
+
+if (typeof console !== 'undefined') {
+ ['log', 'warn'].forEach(function(methodName) {
+ if (typeof console[methodName] !== 'undefined' && console[methodName].apply) {
+ fabric[methodName] = function() {
+ return console[methodName].apply(console, arguments);
+ };
+ }
+ });
+}
+
+
(function() {
/**
@@ -5667,34 +4133,37 @@ fabric.util.string = {
*/
function animate(options) {
- options || (options = { });
+ requestAnimFrame(function(timestamp) {
+ options || (options = { });
- var start = +new Date(),
- duration = options.duration || 500,
- finish = start + duration, time,
- onChange = options.onChange || function() { },
- abort = options.abort || function() { return false; },
- easing = options.easing || function(t, b, c, d) {return -c * Math.cos(t/d * (Math.PI/2)) + c + b;},
- startValue = 'startValue' in options ? options.startValue : 0,
- endValue = 'endValue' in options ? options.endValue : 100,
- byValue = options.byValue || endValue - startValue;
+ var start = timestamp || +new Date(),
+ duration = options.duration || 500,
+ finish = start + duration, time,
+ onChange = options.onChange || function() { },
+ abort = options.abort || function() { return false; },
+ easing = options.easing || function(t, b, c, d) {return -c * Math.cos(t/d * (Math.PI/2)) + c + b;},
+ startValue = 'startValue' in options ? options.startValue : 0,
+ endValue = 'endValue' in options ? options.endValue : 100,
+ byValue = options.byValue || endValue - startValue;
- options.onStart && options.onStart();
+ options.onStart && options.onStart();
+
+ (function tick(ticktime) {
+ time = ticktime || +new Date();
+ var currentTime = time > finish ? duration : (time - start);
+ if (abort()) {
+ options.onComplete && options.onComplete();
+ return;
+ }
+ onChange(easing(currentTime, startValue, byValue, duration));
+ if (time > finish) {
+ options.onComplete && options.onComplete();
+ return;
+ }
+ requestAnimFrame(tick);
+ })(start);
+ });
- (function tick() {
- time = +new Date();
- var currentTime = time > finish ? duration : (time - start);
- if (abort()) {
- options.onComplete && options.onComplete();
- return;
- }
- onChange(easing(currentTime, startValue, byValue, duration));
- if (time > finish) {
- options.onComplete && options.onComplete();
- return;
- }
- requestAnimFrame(tick);
- })();
}
var _requestAnimFrame = fabric.window.requestAnimationFrame ||
@@ -5707,6 +4176,7 @@ fabric.util.string = {
};
/**
* requestAnimationFrame polyfill based on http://paulirish.com/2011/requestanimationframe-for-smart-animating/
+ * In order to get a precise start time, `requestAnimFrame` should be called as an entry into the method
* @memberOf fabric.util
* @param {Function} callback Callback to invoke
* @param {DOMElement} element optional Element to associate with animation
@@ -5723,38 +4193,16 @@ fabric.util.string = {
(function() {
- /**
- * Quadratic easing in
- * @memberOf fabric.util.ease
- */
- function easeInQuad(t, b, c, d) {
- return c*(t/=d)*t + b;
+ function normalize(a, c, p, s) {
+ if (a < Math.abs(c)) { a=c; s=p/4; }
+ else s = p/(2*Math.PI) * Math.asin (c/a);
+ return { a: a, c: c, p: p, s: s };
}
- /**
- * Quadratic easing out
- * @memberOf fabric.util.ease
- */
- function easeOutQuad(t, b, c, d) {
- return -c *(t/=d)*(t-2) + b;
- }
-
- /**
- * Quadratic easing in and out
- * @memberOf fabric.util.ease
- */
- function easeInOutQuad(t, b, c, d) {
- t /= (d/2);
- if (t < 1) return c/2*t*t + b;
- return -c/2 * ((--t)*(t-2) - 1) + b;
- }
-
- /**
- * Cubic easing in
- * @memberOf fabric.util.ease
- */
- function easeInCubic(t, b, c, d) {
- return c*(t/=d)*t*t + b;
+ function elastic(opts, t, d) {
+ return opts.a *
+ Math.pow(2, 10 * (t -= 1)) *
+ Math.sin( (t * d - opts.s) * (2 * Math.PI) / opts.p );
}
/**
@@ -5915,9 +4363,8 @@ fabric.util.string = {
t /= d;
if (t===1) return b+c;
if (!p) p=d*0.3;
- if (a < Math.abs(c)) { a=c; s=p/4; }
- else s = p/(2*Math.PI) * Math.asin (c/a);
- return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
+ var opts = normalize(a, c, p, s);
+ return -elastic(opts, t, d) + b;
}
/**
@@ -5930,9 +4377,8 @@ fabric.util.string = {
t /= d;
if (t===1) return b+c;
if (!p) p=d*0.3;
- if (a < Math.abs(c)) { a=c; s=p/4; }
- else s = p/(2*Math.PI) * Math.asin (c/a);
- return a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b;
+ var opts = normalize(a, c, p, s);
+ return opts.a*Math.pow(2,-10*t) * Math.sin( (t*d-opts.s)*(2*Math.PI)/opts.p ) + opts.c + b;
}
/**
@@ -5945,10 +4391,9 @@ fabric.util.string = {
t /= d/2;
if (t===2) return b+c;
if (!p) p=d*(0.3*1.5);
- if (a < Math.abs(c)) { a=c; s=p/4; }
- else s = p/(2*Math.PI) * Math.asin (c/a);
- if (t < 1) return -0.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
- return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )*0.5 + c + b;
+ var opts = normalize(a, c, p, s);
+ if (t < 1) return -0.5 * elastic(opts, t, d) + b;
+ return opts.a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-opts.s)*(2*Math.PI)/opts.p )*0.5 + opts.c + b;
}
/**
@@ -6019,10 +4464,41 @@ fabric.util.string = {
* @namespace fabric.util.ease
*/
fabric.util.ease = {
- easeInQuad: easeInQuad,
- easeOutQuad: easeOutQuad,
- easeInOutQuad: easeInOutQuad,
- easeInCubic: easeInCubic,
+
+ /**
+ * Quadratic easing in
+ * @memberOf fabric.util.ease
+ */
+ easeInQuad: function(t, b, c, d) {
+ return c*(t/=d)*t + b;
+ },
+
+ /**
+ * Quadratic easing out
+ * @memberOf fabric.util.ease
+ */
+ easeOutQuad: function(t, b, c, d) {
+ return -c *(t/=d)*(t-2) + b;
+ },
+
+ /**
+ * Quadratic easing in and out
+ * @memberOf fabric.util.ease
+ */
+ easeInOutQuad: function(t, b, c, d) {
+ t /= (d/2);
+ if (t < 1) return c/2*t*t + b;
+ return -c/2 * ((--t)*(t-2) - 1) + b;
+ },
+
+ /**
+ * Cubic easing in
+ * @memberOf fabric.util.ease
+ */
+ easeInCubic: function(t, b, c, d) {
+ return c*(t/=d)*t*t + b;
+ },
+
easeOutCubic: easeOutCubic,
easeInOutCubic: easeInOutCubic,
easeInQuart: easeInQuart,
@@ -6070,13 +4546,6 @@ fabric.util.string = {
toFixed = fabric.util.toFixed,
multiplyTransformMatrices = fabric.util.multiplyTransformMatrices;
- fabric.SHARED_ATTRIBUTES = [
- "transform",
- "fill", "fill-opacity", "fill-rule",
- "opacity",
- "stroke", "stroke-dasharray", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke-width"
- ];
-
var attributesMap = {
'fill-opacity': 'fillOpacity',
'fill-rule': 'fillRule',
@@ -6161,55 +4630,13 @@ fabric.util.string = {
return attributes;
}
- /**
- * Returns an object of attributes' name/value, given element and an array of attribute names;
- * Parses parent "g" nodes recursively upwards.
- * @static
- * @memberOf fabric
- * @param {DOMElement} element Element to parse
- * @param {Array} attributes Array of attributes to parse
- * @return {Object} object containing parsed attributes' names/values
- */
- function parseAttributes(element, attributes) {
-
- if (!element) {
- return;
- }
-
- var value,
- parentAttributes = { };
-
- // if there's a parent container (`g` node), parse its attributes recursively upwards
- if (element.parentNode && /^g$/i.test(element.parentNode.nodeName)) {
- parentAttributes = fabric.parseAttributes(element.parentNode, attributes);
- }
-
- var ownAttributes = attributes.reduce(function(memo, attr) {
- value = element.getAttribute(attr);
- if (value) {
- attr = normalizeAttr(attr);
- value = normalizeValue(attr, value, parentAttributes);
-
- memo[attr] = value;
- }
- return memo;
- }, { });
-
- // add values parsed from style, which take precedence over attributes
- // (see: http://www.w3.org/TR/SVG/styling.html#UsingPresentationAttributes)
-
- ownAttributes = extend(ownAttributes,
- extend(getGlobalStylesForElement(element), fabric.parseStyleAttribute(element)));
- return _setStrokeFillOpacity(extend(parentAttributes, ownAttributes));
- }
-
/**
* Parses "transform" attribute, returning an array of values
* @static
* @function
* @memberOf fabric
- * @param attributeValue {String} string containing attribute value
- * @return {Array} array of 6 elements representing transformation matrix
+ * @param {String} attributeValue String containing attribute value
+ * @return {Array} Array of 6 elements representing transformation matrix
*/
fabric.parseTransformAttribute = (function() {
function rotateMatrix(matrix, args) {
@@ -6256,13 +4683,22 @@ fabric.util.string = {
// == begin transform regexp
number = '(?:[-+]?\\d+(?:\\.\\d+)?(?:e[-+]?\\d+)?)',
+
comma_wsp = '(?:\\s+,?\\s*|,\\s*)',
skewX = '(?:(skewX)\\s*\\(\\s*(' + number + ')\\s*\\))',
+
skewY = '(?:(skewY)\\s*\\(\\s*(' + number + ')\\s*\\))',
- rotate = '(?:(rotate)\\s*\\(\\s*(' + number + ')(?:' + comma_wsp + '(' + number + ')' + comma_wsp + '(' + number + '))?\\s*\\))',
- scale = '(?:(scale)\\s*\\(\\s*(' + number + ')(?:' + comma_wsp + '(' + number + '))?\\s*\\))',
- translate = '(?:(translate)\\s*\\(\\s*(' + number + ')(?:' + comma_wsp + '(' + number + '))?\\s*\\))',
+
+ rotate = '(?:(rotate)\\s*\\(\\s*(' + number + ')(?:' +
+ comma_wsp + '(' + number + ')' +
+ comma_wsp + '(' + number + '))?\\s*\\))',
+
+ scale = '(?:(scale)\\s*\\(\\s*(' + number + ')(?:' +
+ comma_wsp + '(' + number + '))?\\s*\\))',
+
+ translate = '(?:(translate)\\s*\\(\\s*(' + number + ')(?:' +
+ comma_wsp + '(' + number + '))?\\s*\\))',
matrix = '(?:(matrix)\\s*\\(\\s*' +
'(' + number + ')' + comma_wsp +
@@ -6348,49 +4784,6 @@ fabric.util.string = {
};
})();
- /**
- * Parses "points" attribute, returning an array of values
- * @static
- * @memberOf fabric
- * @param points {String} points attribute string
- * @return {Array} array of points
- */
- function parsePointsAttribute(points) {
-
- // points attribute is required and must not be empty
- if (!points) return null;
-
- points = points.trim();
- var asPairs = points.indexOf(',') > -1;
-
- points = points.split(/\s+/);
- var parsedPoints = [ ], i, len;
-
- // points could look like "10,20 30,40" or "10 20 30 40"
- if (asPairs) {
- i = 0;
- len = points.length;
- for (; i < len; i++) {
- var pair = points[i].split(',');
- parsedPoints.push({ x: parseFloat(pair[0]), y: parseFloat(pair[1]) });
- }
- }
- else {
- i = 0;
- len = points.length;
- for (; i < len; i+=2) {
- parsedPoints.push({ x: parseFloat(points[i]), y: parseFloat(points[i+1]) });
- }
- }
-
- // odd number of points is an error
- if (parsedPoints.length % 2 !== 0) {
- // return null;
- }
-
- return parsedPoints;
- }
-
function parseFontDeclaration(value, oStyle) {
// TODO: support non-px font size
@@ -6424,166 +4817,43 @@ fabric.util.string = {
}
/**
- * Parses "style" attribute, retuning an object with values
- * @static
- * @memberOf fabric
- * @param {SVGElement} element Element to parse
- * @return {Object} Objects with values parsed from style attribute of an element
+ * @private
*/
- function parseStyleAttribute(element) {
- var oStyle = { },
- style = element.getAttribute('style'),
- attr, value;
+ function parseStyleString(style, oStyle) {
+ var attr, value;
+ style.replace(/;$/, '').split(';').forEach(function (chunk) {
+ var pair = chunk.split(':');
- if (!style) return oStyle;
+ attr = normalizeAttr(pair[0].trim().toLowerCase());
+ value = normalizeValue(attr, pair[1].trim());
- if (typeof style === 'string') {
- style.replace(/;$/, '').split(';').forEach(function (chunk) {
- var pair = chunk.split(':');
-
- attr = normalizeAttr(pair[0].trim().toLowerCase());
- value = normalizeValue(attr, pair[1].trim());
-
- if (attr === 'font') {
- parseFontDeclaration(value, oStyle);
- }
- else {
- oStyle[attr] = value;
- }
- });
- }
- else {
- for (var prop in style) {
- if (typeof style[prop] === 'undefined') continue;
-
- attr = normalizeAttr(prop.toLowerCase());
- value = normalizeValue(attr, style[prop]);
-
- if (attr === 'font') {
- parseFontDeclaration(value, oStyle);
- }
- else {
- oStyle[attr] = value;
- }
- }
- }
-
- return oStyle;
- }
-
- function resolveGradients(instances) {
- for (var i = instances.length; i--; ) {
- var instanceFillValue = instances[i].get('fill');
-
- if (/^url\(/.test(instanceFillValue)) {
-
- var gradientId = instanceFillValue.slice(5, instanceFillValue.length - 1);
-
- if (fabric.gradientDefs[gradientId]) {
- instances[i].set('fill',
- fabric.Gradient.fromElement(fabric.gradientDefs[gradientId], instances[i]));
- }
- }
- }
- }
-
- /**
- * Transforms an array of svg elements to corresponding fabric.* instances
- * @static
- * @memberOf fabric
- * @param {Array} elements Array of elements to parse
- * @param {Function} callback Being passed an array of fabric instances (transformed from SVG elements)
- * @param {Object} [options] Options object
- * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
- */
- function parseElements(elements, callback, options, reviver) {
- var instances = new Array(elements.length), i = elements.length;
-
- function checkIfDone() {
- if (--i === 0) {
- instances = instances.filter(function(el) {
- return el != null;
- });
- resolveGradients(instances);
- callback(instances);
- }
- }
-
- for (var index = 0, el, len = elements.length; index < len; index++) {
- el = elements[index];
- var klass = fabric[capitalize(el.tagName)];
- if (klass && klass.fromElement) {
- try {
- if (klass.async) {
- klass.fromElement(el, (function(index, el) {
- return function(obj) {
- reviver && reviver(el, obj);
- instances.splice(index, 0, obj);
- checkIfDone();
- };
- })(index, el), options);
- }
- else {
- var obj = klass.fromElement(el, options);
- reviver && reviver(el, obj);
- instances.splice(index, 0, obj);
- checkIfDone();
- }
- }
- catch(err) {
- fabric.log(err);
- }
+ if (attr === 'font') {
+ parseFontDeclaration(value, oStyle);
}
else {
- checkIfDone();
+ oStyle[attr] = value;
}
- }
+ });
}
/**
- * Returns CSS rules for a given SVG document
- * @static
- * @function
- * @memberOf fabric
- * @param {SVGDocument} doc SVG document to parse
- * @return {Object} CSS rules of this document
+ * @private
*/
- function getCSSRules(doc) {
- var styles = doc.getElementsByTagName('style'),
- allRules = { },
- rules;
+ function parseStyleObject(style, oStyle) {
+ var attr, value;
+ for (var prop in style) {
+ if (typeof style[prop] === 'undefined') continue;
- // very crude parsing of style contents
- for (var i = 0, len = styles.length; i < len; i++) {
- var styleContents = styles[0].textContent;
+ attr = normalizeAttr(prop.toLowerCase());
+ value = normalizeValue(attr, style[prop]);
- // remove comments
- styleContents = styleContents.replace(/\/\*[\s\S]*?\*\//g, '');
-
- rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g);
- rules = rules.map(function(rule) { return rule.trim(); });
-
- rules.forEach(function(rule) {
- var match = rule.match(/([\s\S]*?)\s*\{([^}]*)\}/);
- rule = match[1];
- var declaration = match[2].trim(),
- propertyValuePairs = declaration.replace(/;$/, '').split(/\s*;\s*/);
-
- if (!allRules[rule]) {
- allRules[rule] = { };
- }
-
- for (var i = 0, len = propertyValuePairs.length; i < len; i++) {
- var pair = propertyValuePairs[i].split(/\s*:\s*/),
- property = pair[0],
- value = pair[1];
-
- allRules[rule][property] = value;
- }
- });
+ if (attr === 'font') {
+ parseFontDeclaration(value, oStyle);
+ }
+ else {
+ oStyle[attr] = value;
+ }
}
-
- return allRules;
}
/**
@@ -6696,7 +4966,7 @@ fabric.util.string = {
};
fabric.gradientDefs = fabric.getGradientDefs(doc);
- fabric.cssRules = getCSSRules(doc);
+ fabric.cssRules = fabric.getCSSRules(doc);
// Precedence of rules: style > class > attribute
@@ -6740,53 +5010,6 @@ fabric.util.string = {
}
};
- /**
- * Takes url corresponding to an SVG document, and parses it into a set of fabric objects. Note that SVG is fetched via XMLHttpRequest, so it needs to conform to SOP (Same Origin Policy)
- * @memberof fabric
- * @param {String} url
- * @param {Function} callback
- * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
- */
- function loadSVGFromURL(url, callback, reviver) {
-
- url = url.replace(/^\n\s*/, '').trim();
-
- svgCache.has(url, function (hasUrl) {
- if (hasUrl) {
- svgCache.get(url, function (value) {
- var enlivedRecord = _enlivenCachedObject(value);
- callback(enlivedRecord.objects, enlivedRecord.options);
- });
- }
- else {
- new fabric.util.request(url, {
- method: 'get',
- onComplete: onComplete
- });
- }
- });
-
- function onComplete(r) {
-
- var xml = r.responseXML;
- if (!xml.documentElement && fabric.window.ActiveXObject && r.responseText) {
- xml = new ActiveXObject('Microsoft.XMLDOM');
- xml.async = 'false';
- //IE chokes on DOCTYPE
- xml.loadXML(r.responseText.replace(//i,''));
- }
- if (!xml.documentElement) return;
-
- fabric.parseSVGDocument(xml.documentElement, function (results, options) {
- svgCache.set(url, {
- objects: fabric.util.array.invoke(results, 'toObject'),
- options: options
- });
- callback(results, options);
- }, reviver);
- }
- }
-
/**
* @private
*/
@@ -6803,139 +5026,435 @@ fabric.util.string = {
}
/**
- * Takes string corresponding to an SVG document, and parses it into a set of fabric objects
- * @memberof fabric
- * @param {String} string
- * @param {Function} callback
- * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
- */
- function loadSVGFromString(string, callback, reviver) {
- string = string.trim();
- var doc;
- if (typeof DOMParser !== 'undefined') {
- var parser = new DOMParser();
- if (parser && parser.parseFromString) {
- doc = parser.parseFromString(string, 'text/xml');
- }
- }
- else if (fabric.window.ActiveXObject) {
- doc = new ActiveXObject('Microsoft.XMLDOM');
- doc.async = 'false';
- //IE chokes on DOCTYPE
- doc.loadXML(string.replace(//i,''));
- }
-
- fabric.parseSVGDocument(doc.documentElement, function (results, options) {
- callback(results, options);
- }, reviver);
- }
-
- /**
- * Creates markup containing SVG font faces
- * @param {Array} objects Array of fabric objects
- * @return {String}
+ * @private
*/
- function createSVGFontFacesMarkup(objects) {
- var markup = '';
-
- for (var i = 0, len = objects.length; i < len; i++) {
- if (objects[i].type !== 'text' || !objects[i].path) continue;
-
- markup += [
- '@font-face {',
- 'font-family: ', objects[i].fontFamily, '; ',
- 'src: url(\'', objects[i].path, '\')',
- '}'
- ].join('');
- }
-
- if (markup) {
- markup = [
- ''
- ].join('');
- }
-
- return markup;
- }
-
- /**
- * Creates markup containing SVG referenced elements like patterns, gradients etc.
- * @param {fabric.Canvas} canvas instance of fabric.Canvas
- * @return {String}
- */
- function createSVGRefElementsMarkup(canvas) {
- var markup = '';
-
- if (canvas.backgroundColor && canvas.backgroundColor.source) {
- markup = [
- '',
''
- ].join('');
+ );
}
-
- return markup;
- }
-
- /**
- * Parses an SVG document, returning all of the gradient declarations found in it
- * @static
- * @function
- * @memberOf fabric
- * @param {SVGDocument} doc SVG document to parse
- * @return {Object} Gradient definitions; key corresponds to element id, value -- to gradient definition element
- */
- function getGradientDefs(doc) {
- var linearGradientEls = doc.getElementsByTagName('linearGradient'),
- radialGradientEls = doc.getElementsByTagName('radialGradient'),
- el, i,
- gradientDefs = { };
-
- i = linearGradientEls.length;
- for (; i--; ) {
- el = linearGradientEls[i];
- gradientDefs[el.getAttribute('id')] = el;
- }
-
- i = radialGradientEls.length;
- for (; i--; ) {
- el = radialGradientEls[i];
- gradientDefs[el.getAttribute('id')] = el;
- }
-
- return gradientDefs;
}
extend(fabric, {
- parseAttributes: parseAttributes,
- parseElements: parseElements,
- parseStyleAttribute: parseStyleAttribute,
- parsePointsAttribute: parsePointsAttribute,
- getCSSRules: getCSSRules,
+ /**
+ * Initializes gradients on instances, according to gradients parsed from a document
+ * @param {Array} instances
+ */
+ resolveGradients: function(instances) {
+ for (var i = instances.length; i--; ) {
+ var instanceFillValue = instances[i].get('fill');
- loadSVGFromURL: loadSVGFromURL,
- loadSVGFromString: loadSVGFromString,
+ if (!(/^url\(/).test(instanceFillValue)) continue;
- createSVGFontFacesMarkup: createSVGFontFacesMarkup,
- createSVGRefElementsMarkup: createSVGRefElementsMarkup,
+ var gradientId = instanceFillValue.slice(5, instanceFillValue.length - 1);
- getGradientDefs: getGradientDefs
+ if (fabric.gradientDefs[gradientId]) {
+ instances[i].set('fill',
+ fabric.Gradient.fromElement(fabric.gradientDefs[gradientId], instances[i]));
+ }
+ }
+ },
+
+ /**
+ * Parses an SVG document, returning all of the gradient declarations found in it
+ * @static
+ * @function
+ * @memberOf fabric
+ * @param {SVGDocument} doc SVG document to parse
+ * @return {Object} Gradient definitions; key corresponds to element id, value -- to gradient definition element
+ */
+ getGradientDefs: function(doc) {
+ var linearGradientEls = doc.getElementsByTagName('linearGradient'),
+ radialGradientEls = doc.getElementsByTagName('radialGradient'),
+ el, i,
+ gradientDefs = { };
+
+ i = linearGradientEls.length;
+ for (; i--; ) {
+ el = linearGradientEls[i];
+ gradientDefs[el.getAttribute('id')] = el;
+ }
+
+ i = radialGradientEls.length;
+ for (; i--; ) {
+ el = radialGradientEls[i];
+ gradientDefs[el.getAttribute('id')] = el;
+ }
+
+ return gradientDefs;
+ },
+
+ /**
+ * Returns an object of attributes' name/value, given element and an array of attribute names;
+ * Parses parent "g" nodes recursively upwards.
+ * @static
+ * @memberOf fabric
+ * @param {DOMElement} element Element to parse
+ * @param {Array} attributes Array of attributes to parse
+ * @return {Object} object containing parsed attributes' names/values
+ */
+ parseAttributes: function(element, attributes) {
+
+ if (!element) {
+ return;
+ }
+
+ var value,
+ parentAttributes = { };
+
+ // if there's a parent container (`g` node), parse its attributes recursively upwards
+ if (element.parentNode && /^g$/i.test(element.parentNode.nodeName)) {
+ parentAttributes = fabric.parseAttributes(element.parentNode, attributes);
+ }
+
+ var ownAttributes = attributes.reduce(function(memo, attr) {
+ value = element.getAttribute(attr);
+ if (value) {
+ attr = normalizeAttr(attr);
+ value = normalizeValue(attr, value, parentAttributes);
+
+ memo[attr] = value;
+ }
+ return memo;
+ }, { });
+
+ // add values parsed from style, which take precedence over attributes
+ // (see: http://www.w3.org/TR/SVG/styling.html#UsingPresentationAttributes)
+ ownAttributes = extend(ownAttributes,
+ extend(getGlobalStylesForElement(element), fabric.parseStyleAttribute(element)));
+
+ return _setStrokeFillOpacity(extend(parentAttributes, ownAttributes));
+ },
+
+ /**
+ * Transforms an array of svg elements to corresponding fabric.* instances
+ * @static
+ * @memberOf fabric
+ * @param {Array} elements Array of elements to parse
+ * @param {Function} callback Being passed an array of fabric instances (transformed from SVG elements)
+ * @param {Object} [options] Options object
+ * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
+ */
+ parseElements: function(elements, callback, options, reviver) {
+ fabric.ElementsParser.parse(elements, callback, options, reviver);
+ },
+
+ /**
+ * Parses "style" attribute, retuning an object with values
+ * @static
+ * @memberOf fabric
+ * @param {SVGElement} element Element to parse
+ * @return {Object} Objects with values parsed from style attribute of an element
+ */
+ parseStyleAttribute: function(element) {
+ var oStyle = { },
+ style = element.getAttribute('style');
+
+ if (!style) return oStyle;
+
+ if (typeof style === 'string') {
+ parseStyleString(style, oStyle);
+ }
+ else {
+ parseStyleObject(style, oStyle);
+ }
+
+ return oStyle;
+ },
+
+ /**
+ * Parses "points" attribute, returning an array of values
+ * @static
+ * @memberOf fabric
+ * @param points {String} points attribute string
+ * @return {Array} array of points
+ */
+ parsePointsAttribute: function(points) {
+
+ // points attribute is required and must not be empty
+ if (!points) return null;
+
+ points = points.trim();
+ var asPairs = points.indexOf(',') > -1;
+
+ points = points.split(/\s+/);
+ var parsedPoints = [ ], i, len;
+
+ // points could look like "10,20 30,40" or "10 20 30 40"
+ if (asPairs) {
+ i = 0;
+ len = points.length;
+ for (; i < len; i++) {
+ var pair = points[i].split(',');
+ parsedPoints.push({ x: parseFloat(pair[0]), y: parseFloat(pair[1]) });
+ }
+ }
+ else {
+ i = 0;
+ len = points.length;
+ for (; i < len; i+=2) {
+ parsedPoints.push({ x: parseFloat(points[i]), y: parseFloat(points[i+1]) });
+ }
+ }
+
+ // odd number of points is an error
+ if (parsedPoints.length % 2 !== 0) {
+ // return null;
+ }
+
+ return parsedPoints;
+ },
+
+ /**
+ * Returns CSS rules for a given SVG document
+ * @static
+ * @function
+ * @memberOf fabric
+ * @param {SVGDocument} doc SVG document to parse
+ * @return {Object} CSS rules of this document
+ */
+ getCSSRules: function(doc) {
+ var styles = doc.getElementsByTagName('style'),
+ allRules = { },
+ rules;
+
+ // very crude parsing of style contents
+ for (var i = 0, len = styles.length; i < len; i++) {
+ var styleContents = styles[0].textContent;
+
+ // remove comments
+ styleContents = styleContents.replace(/\/\*[\s\S]*?\*\//g, '');
+
+ rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g);
+ rules = rules.map(function(rule) { return rule.trim(); });
+
+ rules.forEach(function(rule) {
+ var match = rule.match(/([\s\S]*?)\s*\{([^}]*)\}/);
+ rule = match[1];
+ var declaration = match[2].trim(),
+ propertyValuePairs = declaration.replace(/;$/, '').split(/\s*;\s*/);
+
+ if (!allRules[rule]) {
+ allRules[rule] = { };
+ }
+
+ for (var i = 0, len = propertyValuePairs.length; i < len; i++) {
+ var pair = propertyValuePairs[i].split(/\s*:\s*/),
+ property = pair[0],
+ value = pair[1];
+
+ allRules[rule][property] = value;
+ }
+ });
+ }
+
+ return allRules;
+ },
+
+ /**
+ * Takes url corresponding to an SVG document, and parses it into a set of fabric objects. Note that SVG is fetched via XMLHttpRequest, so it needs to conform to SOP (Same Origin Policy)
+ * @memberof fabric
+ * @param {String} url
+ * @param {Function} callback
+ * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
+ */
+ loadSVGFromURL: function(url, callback, reviver) {
+
+ url = url.replace(/^\n\s*/, '').trim();
+
+ svgCache.has(url, function (hasUrl) {
+ if (hasUrl) {
+ svgCache.get(url, function (value) {
+ var enlivedRecord = _enlivenCachedObject(value);
+ callback(enlivedRecord.objects, enlivedRecord.options);
+ });
+ }
+ else {
+ new fabric.util.request(url, {
+ method: 'get',
+ onComplete: onComplete
+ });
+ }
+ });
+
+ function onComplete(r) {
+
+ var xml = r.responseXML;
+ if (!xml.documentElement && fabric.window.ActiveXObject && r.responseText) {
+ xml = new ActiveXObject('Microsoft.XMLDOM');
+ xml.async = 'false';
+ //IE chokes on DOCTYPE
+ xml.loadXML(r.responseText.replace(//i,''));
+ }
+ if (!xml.documentElement) return;
+
+ fabric.parseSVGDocument(xml.documentElement, function (results, options) {
+ svgCache.set(url, {
+ objects: fabric.util.array.invoke(results, 'toObject'),
+ options: options
+ });
+ callback(results, options);
+ }, reviver);
+ }
+ },
+
+ /**
+ * Takes string corresponding to an SVG document, and parses it into a set of fabric objects
+ * @memberof fabric
+ * @param {String} string
+ * @param {Function} callback
+ * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
+ */
+ loadSVGFromString: function(string, callback, reviver) {
+ string = string.trim();
+ var doc;
+ if (typeof DOMParser !== 'undefined') {
+ var parser = new DOMParser();
+ if (parser && parser.parseFromString) {
+ doc = parser.parseFromString(string, 'text/xml');
+ }
+ }
+ else if (fabric.window.ActiveXObject) {
+ doc = new ActiveXObject('Microsoft.XMLDOM');
+ doc.async = 'false';
+ //IE chokes on DOCTYPE
+ doc.loadXML(string.replace(//i,''));
+ }
+
+ fabric.parseSVGDocument(doc.documentElement, function (results, options) {
+ callback(results, options);
+ }, reviver);
+ },
+
+ /**
+ * Creates markup containing SVG font faces
+ * @param {Array} objects Array of fabric objects
+ * @return {String}
+ */
+ createSVGFontFacesMarkup: function(objects) {
+ var markup = '';
+
+ for (var i = 0, len = objects.length; i < len; i++) {
+ if (objects[i].type !== 'text' || !objects[i].path) continue;
+
+ markup += [
+ '@font-face {',
+ 'font-family: ', objects[i].fontFamily, '; ',
+ 'src: url(\'', objects[i].path, '\')',
+ '}'
+ ].join('');
+ }
+
+ if (markup) {
+ markup = [
+ ''
+ ].join('');
+ }
+
+ return markup;
+ },
+
+ /**
+ * Creates markup containing SVG referenced elements like patterns, gradients etc.
+ * @param {fabric.Canvas} canvas instance of fabric.Canvas
+ * @return {String}
+ */
+ createSVGRefElementsMarkup: function(canvas) {
+ var markup = [ ];
+
+ _createSVGPattern(markup, canvas, 'backgroundColor');
+ _createSVGPattern(markup, canvas, 'overlayColor');
+
+ return markup.join('');
+ }
});
})(typeof exports !== 'undefined' ? exports : this);
+fabric.ElementsParser = {
+
+ parse: function(elements, callback, options, reviver) {
+
+ this.elements = elements;
+ this.callback = callback;
+ this.options = options;
+ this.reviver = reviver;
+
+ this.instances = new Array(elements.length);
+ this.numElements = elements.length;
+
+ this.createObjects();
+ },
+
+ createObjects: function() {
+ for (var i = 0, len = this.elements.length; i < len; i++) {
+ this.createObject(this.elements[i], i);
+ }
+ },
+
+ createObject: function(el, index) {
+ var klass = fabric[fabric.util.string.capitalize(el.tagName)];
+ if (klass && klass.fromElement) {
+ try {
+ this._createObject(klass, el, index);
+ }
+ catch(err) {
+ fabric.log(err);
+ }
+ }
+ else {
+ this.checkIfDone();
+ }
+ },
+
+ _createObject: function(klass, el, index) {
+ if (klass.async) {
+ klass.fromElement(el, this.createCallback(index, el), this.options);
+ }
+ else {
+ var obj = klass.fromElement(el, this.options);
+ this.reviver && this.reviver(el, obj);
+ this.instances.splice(index, 0, obj);
+ this.checkIfDone();
+ }
+ },
+
+ createCallback: function(index, el) {
+ var _this = this;
+ return function(obj) {
+ _this.reviver && _this.reviver(el, obj);
+ _this.instances.splice(index, 0, obj);
+ _this.checkIfDone();
+ };
+ },
+
+ checkIfDone: function() {
+ if (--this.numElements === 0) {
+ this.instances = this.instances.filter(function(el) {
+ return el != null;
+ });
+ fabric.resolveGradients(this.instances);
+ this.callback(this.instances);
+ }
+ }
+};
+
+
(function(global) {
"use strict";
@@ -7227,167 +5746,167 @@ fabric.util.string = {
})(typeof exports !== 'undefined' ? exports : this);
-(function(global) {
-
- "use strict";
-
- /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */
-
- var fabric = global.fabric || (global.fabric = { });
-
- if (fabric.Intersection) {
- fabric.warn('fabric.Intersection is already defined');
- return;
- }
-
- /**
- * Intersection class
- * @class fabric.Intersection
- * @memberOf fabric
- * @constructor
- */
- function Intersection(status) {
- this.status = status;
- this.points = [];
- }
-
- fabric.Intersection = Intersection;
-
- fabric.Intersection.prototype = /** @lends fabric.Intersection.prototype */ {
-
- /**
- * Appends a point to intersection
- * @param {fabric.Point} point
- */
- appendPoint: function (point) {
- this.points.push(point);
- },
-
- /**
- * Appends points to intersection
- * @param {Array} points
- */
- appendPoints: function (points) {
- this.points = this.points.concat(points);
- }
- };
-
- /**
- * Checks if one line intersects another
- * @static
- * @param {fabric.Point} a1
- * @param {fabric.Point} a2
- * @param {fabric.Point} b1
- * @param {fabric.Point} b2
- * @return {fabric.Intersection}
- */
- fabric.Intersection.intersectLineLine = function (a1, a2, b1, b2) {
- var result,
- ua_t = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x),
- ub_t = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x),
- u_b = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y);
- if (u_b !== 0) {
- var ua = ua_t / u_b,
- ub = ub_t / u_b;
- if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) {
- result = new Intersection("Intersection");
- result.points.push(new fabric.Point(a1.x + ua * (a2.x - a1.x), a1.y + ua * (a2.y - a1.y)));
- }
- else {
- result = new Intersection();
- }
- }
- else {
- if (ua_t === 0 || ub_t === 0) {
- result = new Intersection("Coincident");
- }
- else {
- result = new Intersection("Parallel");
- }
- }
- return result;
- };
-
- /**
- * Checks if line intersects polygon
- * @static
- * @param {fabric.Point} a1
- * @param {fabric.Point} a2
- * @param {Array} points
- * @return {fabric.Intersection}
- */
- fabric.Intersection.intersectLinePolygon = function(a1,a2,points){
- var result = new Intersection(),
- length = points.length;
-
- for (var i = 0; i < length; i++) {
- var b1 = points[i],
- b2 = points[(i+1) % length],
- inter = Intersection.intersectLineLine(a1, a2, b1, b2);
-
- result.appendPoints(inter.points);
- }
- if (result.points.length > 0) {
- result.status = "Intersection";
- }
- return result;
- };
-
- /**
- * Checks if polygon intersects another polygon
- * @static
- * @param {Array} points1
- * @param {Array} points2
- * @return {fabric.Intersection}
- */
- fabric.Intersection.intersectPolygonPolygon = function (points1, points2) {
- var result = new Intersection(),
- length = points1.length;
-
- for (var i = 0; i < length; i++) {
- var a1 = points1[i],
- a2 = points1[(i+1) % length],
- inter = Intersection.intersectLinePolygon(a1, a2, points2);
-
- result.appendPoints(inter.points);
- }
- if (result.points.length > 0) {
- result.status = "Intersection";
- }
- return result;
- };
-
- /**
- * Checks if polygon intersects rectangle
- * @static
- * @param {Array} points
- * @param {Number} r1
- * @param {Number} r2
- * @return {fabric.Intersection}
- */
- fabric.Intersection.intersectPolygonRectangle = function (points, r1, r2) {
- var min = r1.min(r2),
- max = r1.max(r2),
- topRight = new fabric.Point(max.x, min.y),
- bottomLeft = new fabric.Point(min.x, max.y),
- inter1 = Intersection.intersectLinePolygon(min, topRight, points),
- inter2 = Intersection.intersectLinePolygon(topRight, max, points),
- inter3 = Intersection.intersectLinePolygon(max, bottomLeft, points),
- inter4 = Intersection.intersectLinePolygon(bottomLeft, min, points),
- result = new Intersection();
-
- result.appendPoints(inter1.points);
- result.appendPoints(inter2.points);
- result.appendPoints(inter3.points);
- result.appendPoints(inter4.points);
-
- if (result.points.length > 0) {
- result.status = "Intersection";
- }
- return result;
- };
-
-})(typeof exports !== 'undefined' ? exports : this);
+(function(global) {
+
+ "use strict";
+
+ /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */
+
+ var fabric = global.fabric || (global.fabric = { });
+
+ if (fabric.Intersection) {
+ fabric.warn('fabric.Intersection is already defined');
+ return;
+ }
+
+ /**
+ * Intersection class
+ * @class fabric.Intersection
+ * @memberOf fabric
+ * @constructor
+ */
+ function Intersection(status) {
+ this.status = status;
+ this.points = [];
+ }
+
+ fabric.Intersection = Intersection;
+
+ fabric.Intersection.prototype = /** @lends fabric.Intersection.prototype */ {
+
+ /**
+ * Appends a point to intersection
+ * @param {fabric.Point} point
+ */
+ appendPoint: function (point) {
+ this.points.push(point);
+ },
+
+ /**
+ * Appends points to intersection
+ * @param {Array} points
+ */
+ appendPoints: function (points) {
+ this.points = this.points.concat(points);
+ }
+ };
+
+ /**
+ * Checks if one line intersects another
+ * @static
+ * @param {fabric.Point} a1
+ * @param {fabric.Point} a2
+ * @param {fabric.Point} b1
+ * @param {fabric.Point} b2
+ * @return {fabric.Intersection}
+ */
+ fabric.Intersection.intersectLineLine = function (a1, a2, b1, b2) {
+ var result,
+ ua_t = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x),
+ ub_t = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x),
+ u_b = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y);
+ if (u_b !== 0) {
+ var ua = ua_t / u_b,
+ ub = ub_t / u_b;
+ if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) {
+ result = new Intersection("Intersection");
+ result.points.push(new fabric.Point(a1.x + ua * (a2.x - a1.x), a1.y + ua * (a2.y - a1.y)));
+ }
+ else {
+ result = new Intersection();
+ }
+ }
+ else {
+ if (ua_t === 0 || ub_t === 0) {
+ result = new Intersection("Coincident");
+ }
+ else {
+ result = new Intersection("Parallel");
+ }
+ }
+ return result;
+ };
+
+ /**
+ * Checks if line intersects polygon
+ * @static
+ * @param {fabric.Point} a1
+ * @param {fabric.Point} a2
+ * @param {Array} points
+ * @return {fabric.Intersection}
+ */
+ fabric.Intersection.intersectLinePolygon = function(a1,a2,points){
+ var result = new Intersection(),
+ length = points.length;
+
+ for (var i = 0; i < length; i++) {
+ var b1 = points[i],
+ b2 = points[(i+1) % length],
+ inter = Intersection.intersectLineLine(a1, a2, b1, b2);
+
+ result.appendPoints(inter.points);
+ }
+ if (result.points.length > 0) {
+ result.status = "Intersection";
+ }
+ return result;
+ };
+
+ /**
+ * Checks if polygon intersects another polygon
+ * @static
+ * @param {Array} points1
+ * @param {Array} points2
+ * @return {fabric.Intersection}
+ */
+ fabric.Intersection.intersectPolygonPolygon = function (points1, points2) {
+ var result = new Intersection(),
+ length = points1.length;
+
+ for (var i = 0; i < length; i++) {
+ var a1 = points1[i],
+ a2 = points1[(i+1) % length],
+ inter = Intersection.intersectLinePolygon(a1, a2, points2);
+
+ result.appendPoints(inter.points);
+ }
+ if (result.points.length > 0) {
+ result.status = "Intersection";
+ }
+ return result;
+ };
+
+ /**
+ * Checks if polygon intersects rectangle
+ * @static
+ * @param {Array} points
+ * @param {Number} r1
+ * @param {Number} r2
+ * @return {fabric.Intersection}
+ */
+ fabric.Intersection.intersectPolygonRectangle = function (points, r1, r2) {
+ var min = r1.min(r2),
+ max = r1.max(r2),
+ topRight = new fabric.Point(max.x, min.y),
+ bottomLeft = new fabric.Point(min.x, max.y),
+ inter1 = Intersection.intersectLinePolygon(min, topRight, points),
+ inter2 = Intersection.intersectLinePolygon(topRight, max, points),
+ inter3 = Intersection.intersectLinePolygon(max, bottomLeft, points),
+ inter4 = Intersection.intersectLinePolygon(bottomLeft, min, points),
+ result = new Intersection();
+
+ result.appendPoints(inter1.points);
+ result.appendPoints(inter2.points);
+ result.appendPoints(inter3.points);
+ result.appendPoints(inter4.points);
+
+ if (result.points.length > 0) {
+ result.status = "Intersection";
+ }
+ return result;
+ };
+
+})(typeof exports !== 'undefined' ? exports : this);
(function(global) {
@@ -7908,12 +6427,33 @@ fabric.util.string = {
opacity: isNaN(parseFloat(opacity)) ? 1 : parseFloat(opacity)
};
}
+
+ function getLinearCoords(el) {
+ return {
+ x1: el.getAttribute('x1') || 0,
+ y1: el.getAttribute('y1') || 0,
+ x2: el.getAttribute('x2') || '100%',
+ y2: el.getAttribute('y2') || 0
+ };
+ }
+
+ function getRadialCoords(el) {
+ return {
+ x1: el.getAttribute('fx') || el.getAttribute('cx') || '50%',
+ y1: el.getAttribute('fy') || el.getAttribute('cy') || '50%',
+ r1: 0,
+ x2: el.getAttribute('cx') || '50%',
+ y2: el.getAttribute('cy') || '50%',
+ r2: el.getAttribute('r') || '50%'
+ };
+ }
/* _FROM_SVG_END_ */
/**
* Gradient class
* @class fabric.Gradient
* @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#gradients}
+ * @see {@link fabric.Gradient#initialize} for constructor definition
*/
fabric.Gradient = fabric.util.createClass(/** @lends fabric.Gradient.prototype */ {
@@ -8060,6 +6600,169 @@ fabric.util.string = {
this.coords.x1, this.coords.y1, this.coords.r1, this.coords.x2, this.coords.y2, this.coords.r2);
}
+<<<<<<< HEAD
+(function(global) {
+
+ "use strict";
+
+ /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */
+
+ var fabric = global.fabric || (global.fabric = { });
+
+ if (fabric.Intersection) {
+ fabric.warn('fabric.Intersection is already defined');
+ return;
+ }
+
+ /**
+ * Intersection class
+ * @class fabric.Intersection
+ * @memberOf fabric
+ * @constructor
+ */
+ function Intersection(status) {
+ this.status = status;
+ this.points = [];
+ }
+
+ fabric.Intersection = Intersection;
+
+ fabric.Intersection.prototype = /** @lends fabric.Intersection.prototype */ {
+
+ /**
+ * Appends a point to intersection
+ * @param {fabric.Point} point
+ */
+ appendPoint: function (point) {
+ this.points.push(point);
+ },
+
+ /**
+ * Appends points to intersection
+ * @param {Array} points
+ */
+ appendPoints: function (points) {
+ this.points = this.points.concat(points);
+ }
+ };
+
+ /**
+ * Checks if one line intersects another
+ * @static
+ * @param {fabric.Point} a1
+ * @param {fabric.Point} a2
+ * @param {fabric.Point} b1
+ * @param {fabric.Point} b2
+ * @return {fabric.Intersection}
+ */
+ fabric.Intersection.intersectLineLine = function (a1, a2, b1, b2) {
+ var result,
+ ua_t = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x),
+ ub_t = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x),
+ u_b = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y);
+ if (u_b !== 0) {
+ var ua = ua_t / u_b,
+ ub = ub_t / u_b;
+ if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) {
+ result = new Intersection("Intersection");
+ result.points.push(new fabric.Point(a1.x + ua * (a2.x - a1.x), a1.y + ua * (a2.y - a1.y)));
+ }
+ else {
+ result = new Intersection();
+ }
+ }
+ else {
+ if (ua_t === 0 || ub_t === 0) {
+ result = new Intersection("Coincident");
+ }
+ else {
+ result = new Intersection("Parallel");
+ }
+ }
+ return result;
+ };
+
+ /**
+ * Checks if line intersects polygon
+ * @static
+ * @param {fabric.Point} a1
+ * @param {fabric.Point} a2
+ * @param {Array} points
+ * @return {fabric.Intersection}
+ */
+ fabric.Intersection.intersectLinePolygon = function(a1,a2,points){
+ var result = new Intersection(),
+ length = points.length;
+
+ for (var i = 0; i < length; i++) {
+ var b1 = points[i],
+ b2 = points[(i+1) % length],
+ inter = Intersection.intersectLineLine(a1, a2, b1, b2);
+
+ result.appendPoints(inter.points);
+ }
+ if (result.points.length > 0) {
+ result.status = "Intersection";
+ }
+ return result;
+ };
+
+ /**
+ * Checks if polygon intersects another polygon
+ * @static
+ * @param {Array} points1
+ * @param {Array} points2
+ * @return {fabric.Intersection}
+ */
+ fabric.Intersection.intersectPolygonPolygon = function (points1, points2) {
+ var result = new Intersection(),
+ length = points1.length;
+
+ for (var i = 0; i < length; i++) {
+ var a1 = points1[i],
+ a2 = points1[(i+1) % length],
+ inter = Intersection.intersectLinePolygon(a1, a2, points2);
+
+ result.appendPoints(inter.points);
+ }
+ if (result.points.length > 0) {
+ result.status = "Intersection";
+ }
+ return result;
+ };
+
+ /**
+ * Checks if polygon intersects rectangle
+ * @static
+ * @param {Array} points
+ * @param {Number} r1
+ * @param {Number} r2
+ * @return {fabric.Intersection}
+ */
+ fabric.Intersection.intersectPolygonRectangle = function (points, r1, r2) {
+ var min = r1.min(r2),
+ max = r1.max(r2),
+ topRight = new fabric.Point(max.x, min.y),
+ bottomLeft = new fabric.Point(min.x, max.y),
+ inter1 = Intersection.intersectLinePolygon(min, topRight, points),
+ inter2 = Intersection.intersectLinePolygon(topRight, max, points),
+ inter3 = Intersection.intersectLinePolygon(max, bottomLeft, points),
+ inter4 = Intersection.intersectLinePolygon(bottomLeft, min, points),
+ result = new Intersection();
+
+ result.appendPoints(inter1.points);
+ result.appendPoints(inter2.points);
+ result.appendPoints(inter3.points);
+ result.appendPoints(inter4.points);
+
+ if (result.points.length > 0) {
+ result.status = "Intersection";
+ }
+ return result;
+ };
+
+})(typeof exports !== 'undefined' ? exports : this);
+=======
for (var i = 0, len = this.colorStops.length; i < len; i++) {
var color = this.colorStops[i].color,
opacity = this.colorStops[i].opacity,
@@ -8130,22 +6833,10 @@ fabric.util.string = {
coords = { };
if (type === 'linear') {
- coords = {
- x1: el.getAttribute('x1') || 0,
- y1: el.getAttribute('y1') || 0,
- x2: el.getAttribute('x2') || '100%',
- y2: el.getAttribute('y2') || 0
- };
+ coords = getLinearCoords(el);
}
else if (type === 'radial') {
- coords = {
- x1: el.getAttribute('fx') || el.getAttribute('cx') || '50%',
- y1: el.getAttribute('fy') || el.getAttribute('cy') || '50%',
- r1: 0,
- x2: el.getAttribute('cx') || '50%',
- y2: el.getAttribute('cy') || '50%',
- r2: el.getAttribute('r') || '50%'
- };
+ coords = getRadialCoords(el);
}
for (var i = colorStopEls.length; i--; ) {
@@ -8191,13 +6882,17 @@ fabric.util.string = {
options[prop] = fabric.util.toFixed(object.height * percents / 100, 2);
}
}
- // normalize rendering point (should be from top/left corner rather than center of the shape)
- if (prop === 'x1' || prop === 'x2') {
- options[prop] -= fabric.util.toFixed(object.width / 2, 2);
- }
- else if (prop === 'y1' || prop === 'y2') {
- options[prop] -= fabric.util.toFixed(object.height / 2, 2);
- }
+ normalize(options, prop, object);
+ }
+ }
+
+ // normalize rendering point (should be from top/left corner rather than center of the shape)
+ function normalize(options, prop, object) {
+ if (prop === 'x1' || prop === 'x2') {
+ options[prop] -= fabric.util.toFixed(object.width / 2, 2);
+ }
+ else if (prop === 'y1' || prop === 'y2') {
+ options[prop] -= fabric.util.toFixed(object.height / 2, 2);
}
}
@@ -8207,13 +6902,9 @@ fabric.util.string = {
*/
function _convertValuesToPercentUnits(object, options) {
for (var prop in options) {
- // normalize rendering point (should be from center rather than top/left corner of the shape)
- if (prop === 'x1' || prop === 'x2') {
- options[prop] += fabric.util.toFixed(object.width / 2, 2);
- }
- else if (prop === 'y1' || prop === 'y2') {
- options[prop] += fabric.util.toFixed(object.height / 2, 2);
- }
+
+ normalize(options, prop, object);
+
// convert to percent units
if (prop === 'x1' || prop === 'x2' || prop === 'r2') {
options[prop] = fabric.util.toFixed(options[prop] / object.width * 100, 2) + '%';
@@ -8233,6 +6924,7 @@ fabric.util.string = {
* @class fabric.Pattern
* @see {@link http://fabricjs.com/patterns/|Pattern demo}
* @see {@link http://fabricjs.com/dynamic-patterns/|DynamicPattern demo}
+ * @see {@link fabric.Pattern#initialize} for constructor definition
*/
fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ {
@@ -8363,9 +7055,15 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */
*/
toLive: function(ctx) {
var source = typeof this.source === 'function' ? this.source() : this.source;
+ // if an image
+ if (typeof source.src !== 'undefined') {
+ if (!source.complete) return '';
+ if (source.naturalWidth === 0 || source.naturalHeight === 0) return '';
+ }
return ctx.createPattern(source, this.repeat);
}
});
+>>>>>>> f84ac95f75347b051e52321b790b8aa936d7076f
(function(global) {
@@ -8383,6 +7081,7 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */
* Shadow class
* @class fabric.Shadow
* @see {@link http://fabricjs.com/shadows/|Shadow demo}
+ * @see {@link fabric.Shadow#initialize} for constructor definition
*/
fabric.Shadow = fabric.util.createClass(/** @lends fabric.Shadow.prototype */ {
@@ -8420,6 +7119,13 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */
*/
affectStroke: false,
+ /**
+ * Indicates whether toObject should include default values
+ * @type Boolean
+ * @default
+ */
+ includeDefaultValues: true,
+
/**
* Constructor
* @param {Object|String} [options] Options object with any of color, blur, offsetX, offsetX properties or string (e.g. "rgba(0,0,0,0.2) 2px 2px 10px, "2px 2px 10px rgba(0,0,0,0.2)")
@@ -8497,12 +7203,28 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */
* @return {Object} Object representation of a shadow instance
*/
toObject: function() {
- return {
- color: this.color,
- blur: this.blur,
- offsetX: this.offsetX,
- offsetY: this.offsetY
- };
+ if (this.includeDefaultValues) {
+ return {
+ color: this.color,
+ blur: this.blur,
+ offsetX: this.offsetX,
+ offsetY: this.offsetY
+ };
+ }
+ var obj = { }, proto = fabric.Shadow.prototype;
+ if (this.color !== proto.color) {
+ obj.color = this.color;
+ }
+ if (this.blur !== proto.blur) {
+ obj.blur = this.blur;
+ }
+ if (this.offsetX !== proto.offsetX) {
+ obj.offsetX = this.offsetX;
+ }
+ if (this.offsetY !== proto.offsetY) {
+ obj.offsetY = this.offsetY;
+ }
+ return obj;
}
});
@@ -8530,7 +7252,6 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */
var extend = fabric.util.object.extend,
getElementOffset = fabric.util.getElementOffset,
removeFromArray = fabric.util.removeFromArray,
- removeListener = fabric.util.removeListener,
CANVAS_INIT_ERROR = new Error('Could not initialize `canvas` element');
@@ -8540,6 +7261,7 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */
* @mixes fabric.Collection
* @mixes fabric.Observable
* @see {@link http://fabricjs.com/static_canvas/|StaticCanvas demo}
+ * @see {@link fabric.StaticCanvas#initialize} for constructor definition
* @fires before:render
* @fires after:render
* @fires canvas:cleared
@@ -8562,56 +7284,43 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */
},
/**
- * Background color of canvas instance
- * @type String
+ * Background color of canvas instance.
+ * Should be set via {@link fabric.StaticCanvas#setBackgroundColor}.
+ * @type {(String|fabric.Pattern)}
* @default
*/
backgroundColor: '',
/**
- * Background image of canvas instance
- * Should be set via {@link fabric.StaticCanvas#setBackgroundImage}
- * @type String
+ * Background image of canvas instance.
+ * Should be set via {@link fabric.StaticCanvas#setBackgroundImage}.
+ * Backwards incompatibility note: The "backgroundImageOpacity"
+ * and "backgroundImageStretch" properties are deprecated since 1.3.9.
+ * Use {@link fabric.Image#opacity}, {@link fabric.Image#width} and {@link fabric.Image#height}.
+ * @type fabric.Image
* @default
*/
- backgroundImage: '',
+ backgroundImage: null,
/**
- * Opacity of the background image of the canvas instance
- * @type Float
+ * Overlay color of canvas instance.
+ * Should be set via {@link fabric.StaticCanvas#setOverlayColor}
+ * @since 1.3.9
+ * @type {(String|fabric.Pattern)}
* @default
*/
- backgroundImageOpacity: 1,
+ overlayColor: '',
/**
- * Indicates whether the background image should be stretched to fit the
- * dimensions of the canvas instance.
- * @type Boolean
+ * Overlay image of canvas instance.
+ * Should be set via {@link fabric.StaticCanvas#setOverlayImage}.
+ * Backwards incompatibility note: The "overlayImageLeft"
+ * and "overlayImageTop" properties are deprecated since 1.3.9.
+ * Use {@link fabric.Image#left} and {@link fabric.Image#top}.
+ * @type fabric.Image
* @default
*/
- backgroundImageStretch: true,
-
- /**
- * Overlay image of canvas instance
- * Should be set via {@link fabric.StaticCanvas#setOverlayImage}
- * @type String
- * @default
- */
- overlayImage: '',
-
- /**
- * Left offset of overlay image (if present)
- * @type Number
- * @default
- */
- overlayImageLeft: 0,
-
- /**
- * Top offset of overlay image (if present)
- * @type Number
- * @default
- */
- overlayImageTop: 0,
+ overlayImage: null,
/**
* Indicates whether toObject/toDatalessObject should include default values
@@ -8658,13 +7367,6 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */
*/
allowTouchScrolling: false,
- /**
- * The transformation (in the format of Canvas transform) which focuses the viewport
- * @type Array
- * @default
- */
- viewportTransform: [1, 0, 0, 1, 0, 0],
-
/**
* Callback; invoked right before object is about to be scaled/rotated
* @param {fabric.Object} target Object that's about to be scaled/rotated
@@ -8693,6 +7395,9 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */
if (options.backgroundColor) {
this.setBackgroundColor(options.backgroundColor, this.renderAll.bind(this));
}
+ if (options.overlayColor) {
+ this.setOverlayColor(options.overlayColor, this.renderAll.bind(this));
+ }
this.calcOffset();
},
@@ -8709,74 +7414,115 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */
/**
* Sets {@link fabric.StaticCanvas#overlayImage|overlay image} for this canvas
- * @param {String} url url of an image to set overlay to
+ * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set overlay to
* @param {Function} callback callback to invoke when image is loaded and set as an overlay
- * @param {Object} [options] Optional options to set for the overlay image
- * @param {Number} [options.overlayImageLeft] {@link fabric.StaticCanvas#overlayImageLeft|Left offset} of overlay image
- * @param {Number} [options.overlayImageTop] {@link fabric.StaticCanvas#overlayImageTop|Top offset} of overlay image
+ * @param {Object} [options] Optional options to set for the {@link fabric.Image|overlay image}.
* @return {fabric.Canvas} thisArg
* @chainable
* @see {@link http://jsfiddle.net/fabricjs/MnzHT/|jsFiddle demo}
- * @example Normal overlayImage
- * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas));
- * @example Displaced overlayImage (left and top != 0)
+ * @example Normal overlayImage with left/top = 0
* canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {
- * overlayImageLeft: 100,
- * overlayImageTop: 100
+ * // Needed to position overlayImage at 0/0
+ * originX: 'left',
+ * originY: 'top'
+ * });
+ * @example overlayImage with different properties
+ * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {
+ * opacity: 0.5,
+ * angle: 45,
+ * left: 400,
+ * top: 400,
+ * originX: 'left',
+ * originY: 'top'
+ * });
+ * @example Stretched overlayImage #1 - width/height correspond to canvas width/height
+ * fabric.Image.fromURL('http://fabricjs.com/assets/jail_cell_bars.png', function(img) {
+ * img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'});
+ * canvas.setOverlayImage(img, canvas.renderAll.bind(canvas));
+ * });
+ * @example Stretched overlayImage #2 - width/height correspond to canvas width/height
+ * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {
+ * width: canvas.width,
+ * height: canvas.height,
+ * // Needed to position overlayImage at 0/0
+ * originX: 'left',
+ * originY: 'top'
* });
*/
- setOverlayImage: function (url, callback, options) { // TODO (kangax): test callback
- fabric.util.loadImage(url, function(img) {
- this.overlayImage = img;
- if (options && ('overlayImageLeft' in options)) {
- this.overlayImageLeft = options.overlayImageLeft;
- }
- if (options && ('overlayImageTop' in options)) {
- this.overlayImageTop = options.overlayImageTop;
- }
- callback && callback();
- }, this);
-
- return this;
+ setOverlayImage: function (image, callback, options) {
+ return this.__setBgOverlayImage('overlayImage', image, callback, options);
},
/**
* Sets {@link fabric.StaticCanvas#backgroundImage|background image} for this canvas
- * @param {String} url URL of an image to set background to
- * @param {Function} callback callback to invoke when image is loaded and set as background
- * @param {Object} [options] Optional options to set for the background image
- * @param {Float} [options.backgroundImageOpacity] {@link fabric.StaticCanvas#backgroundImageOpacity|Opacity} of the background image of the canvas instance
- * @param {Boolean} [options.backgroundImageStretch] Indicates whether the background image should be {@link fabric.StaticCanvas#backgroundImageStretch|strechted} to fit the canvas
+ * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set background to
+ * @param {Function} callback Callback to invoke when image is loaded and set as background
+ * @param {Object} [options] Optional options to set for the {@link fabric.Image|background image}.
* @return {fabric.Canvas} thisArg
* @chainable
* @see {@link http://jsfiddle.net/fabricjs/YH9yD/|jsFiddle demo}
- * @example Normal backgroundImage
- * canvas.setBackgroundImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas));
- * @example Stretched backgroundImage with opacity
- * canvas.setBackgroundImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {
- * backgroundImageOpacity: 0.5,
- * backgroundImageStretch: true
+ * @example Normal backgroundImage with left/top = 0
+ * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), {
+ * // Needed to position backgroundImage at 0/0
+ * originX: 'left',
+ * originY: 'top'
+ * });
+ * @example backgroundImage with different properties
+ * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), {
+ * opacity: 0.5,
+ * angle: 45,
+ * left: 400,
+ * top: 400,
+ * originX: 'left',
+ * originY: 'top'
+ * });
+ * @example Stretched backgroundImage #1 - width/height correspond to canvas width/height
+ * fabric.Image.fromURL('http://fabricjs.com/assets/honey_im_subtle.png', function(img) {
+ * img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'});
+ * canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas));
+ * });
+ * @example Stretched backgroundImage #2 - width/height correspond to canvas width/height
+ * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), {
+ * width: canvas.width,
+ * height: canvas.height,
+ * // Needed to position backgroundImage at 0/0
+ * originX: 'left',
+ * originY: 'top'
* });
*/
- setBackgroundImage: function (url, callback, options) {
- fabric.util.loadImage(url, function(img) {
- this.backgroundImage = img;
- if (options && ('backgroundImageOpacity' in options)) {
- this.backgroundImageOpacity = options.backgroundImageOpacity;
- }
- if (options && ('backgroundImageStretch' in options)) {
- this.backgroundImageStretch = options.backgroundImageStretch;
- }
- callback && callback();
- }, this);
+ setBackgroundImage: function (image, callback, options) {
+ return this.__setBgOverlayImage('backgroundImage', image, callback, options);
+ },
- return this;
+ /**
+ * Sets {@link fabric.StaticCanvas#overlayColor|background color} for this canvas
+ * @param {(String|fabric.Pattern)} overlayColor Color or pattern to set background color to
+ * @param {Function} callback Callback to invoke when background color is set
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ * @see {@link http://jsfiddle.net/fabricjs/pB55h/|jsFiddle demo}
+ * @example Normal overlayColor - color value
+ * canvas.setOverlayColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas));
+ * @example fabric.Pattern used as overlayColor
+ * canvas.setOverlayColor({
+ * source: 'http://fabricjs.com/assets/escheresque_ste.png'
+ * }, canvas.renderAll.bind(canvas));
+ * @example fabric.Pattern used as overlayColor with repeat and offset
+ * canvas.setOverlayColor({
+ * source: 'http://fabricjs.com/assets/escheresque_ste.png',
+ * repeat: 'repeat',
+ * offsetX: 200,
+ * offsetY: 100
+ * }, canvas.renderAll.bind(canvas));
+ */
+ setOverlayColor: function(overlayColor, callback) {
+ return this.__setBgOverlayColor('overlayColor', overlayColor, callback);
},
/**
* Sets {@link fabric.StaticCanvas#backgroundColor|background color} for this canvas
- * @param {String|fabric.Pattern} backgroundColor Color or pattern to set background color to
- * @param {Function} callback callback to invoke when background color is set
+ * @param {(String|fabric.Pattern)} backgroundColor Color or pattern to set background color to
+ * @param {Function} callback Callback to invoke when background color is set
* @return {fabric.Canvas} thisArg
* @chainable
* @see {@link http://jsfiddle.net/fabricjs/hXzvk/|jsFiddle demo}
@@ -8786,20 +7532,63 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */
* canvas.setBackgroundColor({
* source: 'http://fabricjs.com/assets/escheresque_ste.png'
* }, canvas.renderAll.bind(canvas));
+ * @example fabric.Pattern used as backgroundColor with repeat and offset
+ * canvas.setBackgroundColor({
+ * source: 'http://fabricjs.com/assets/escheresque_ste.png',
+ * repeat: 'repeat',
+ * offsetX: 200,
+ * offsetY: 100
+ * }, canvas.renderAll.bind(canvas));
*/
setBackgroundColor: function(backgroundColor, callback) {
- if (backgroundColor.source) {
+ return this.__setBgOverlayColor('backgroundColor', backgroundColor, callback);
+ },
+
+ /**
+ * @private
+ * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundImage|backgroundImage}
+ * or {@link fabric.StaticCanvas#overlayImage|overlayImage})
+ * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set background or overlay to
+ * @param {Function} callback Callback to invoke when image is loaded and set as background or overlay
+ * @param {Object} [options] Optional options to set for the {@link fabric.Image|image}.
+ */
+ __setBgOverlayImage: function(property, image, callback, options) {
+ if (typeof image === 'string') {
+ fabric.util.loadImage(image, function(img) {
+ this[property] = new fabric.Image(img, options);
+ callback && callback();
+ }, this);
+ }
+ else {
+ this[property] = image;
+ callback && callback();
+ }
+
+ return this;
+ },
+
+ /**
+ * @private
+ * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundColor|backgroundColor}
+ * or {@link fabric.StaticCanvas#overlayColor|overlayColor})
+ * @param {(Object|String)} color Object with pattern information or color value
+ * @param {Function} [callback] Callback is invoked when color is set
+ */
+ __setBgOverlayColor: function(property, color, callback) {
+ if (color.source) {
var _this = this;
- fabric.util.loadImage(backgroundColor.source, function(img) {
- _this.backgroundColor = new fabric.Pattern({
+ fabric.util.loadImage(color.source, function(img) {
+ _this[property] = new fabric.Pattern({
source: img,
- repeat: backgroundColor.repeat
+ repeat: color.repeat,
+ offsetX: color.offsetX,
+ offsetY: color.offsetY
});
callback && callback();
});
}
else {
- this.backgroundColor = backgroundColor;
+ this[property] = color;
callback && callback();
}
@@ -8953,73 +7742,6 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */
return this;
},
- /**
- * Returns canvas zoom level
- * @return {Number}
- */
- getZoom: function () {
- return sqrt(this.viewportTransform[0] * this.viewportTransform[3]);
- },
-
- /**
- * Returns point at center of viewport
- * @return {fabric.Point} the top left corner of the viewport
- */
- getViewportCenter: function () {
- var wh = fabric.util.transformPoint(
- new fabric.Point(this.getWidth(), this.getHeight()),
- this.viewportTransform
- ),
- x = this.viewportTransform[4],
- y = this.viewportTransform[5];
-
- return new fabric.Point(this.getWidth()/2 + x, this.getHeight()/2 + y);
- },
-
- /**
- * Sets zoom level of this canvas instance
- * @param {Number} value to set zoom to, less than 1 zooms out
- * @return {fabric.Canvas} instance
- * @chainable true
- */
- setZoom: function (value) {
- // TODO: just change the scale, preserve other transformations
- this.viewportTransform[0] = value;
- this.viewportTransform[3] = value;
- this.renderAll();
- for (var i = 0, len = this._objects.length; i < len; i++) {
- this._objects[i].setCoords();
- }
- return this;
- },
-
- /**
- * Centers viewport of this canvas instance on given point
- * @param {Numer} x value for center of viewport
- * @param {Numer} y value for center of viewport
- * @return {fabric.Canvas} instance
- * @chainable true
- */
- setViewportCenter: function (x, y) {
- var wh = fabric.util.transformPoint(
- new fabric.Point(this.getWidth(), this.getHeight()),
- this.viewportTransform
- );
- this.viewportTransform[4] = x - wh.x/2;
- this.viewportTransform[5] = y - wh.y/2;
- this.renderAll();
- return this;
- },
-
- /**
- * Centers viewport of this canvas instance
- * @return {fabric.Canvas} instance
- * @chainable true
- */
- centerViewport: function () {
- return this.setViewportCenter(this.getWidth()/2, this.getHeight()/2);
- },
-
/**
* Returns <canvas> element corresponding to this instance
* @return {HTMLCanvasElement}
@@ -9071,14 +7793,8 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */
*/
_onObjectAdded: function(obj) {
this.stateful && obj.setupState();
- obj.canvas = this;
obj.setCoords();
- if (obj._objects) {
- for (var i = 0, len = obj._objects.length; i < len; i++) {
- obj._objects[i].canvas = this;
- obj._objects[i].setCoords();
- }
- }
+ obj.canvas = this;
this.fire('object:added', { target: obj });
obj.fire('added');
},
@@ -9088,18 +7804,17 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */
* @param {fabric.Object} obj Object that was removed
*/
_onObjectRemoved: function(obj) {
+ // removing active object should fire "selection:cleared" events
+ if (this.getActiveObject() === obj) {
+ this.fire('before:selection:cleared', { target: obj });
+ this._discardActiveObject();
+ this.fire('selection:cleared');
+ }
+
this.fire('object:removed', { target: obj });
obj.fire('removed');
},
- /**
- * Returns an array of objects this instance has
- * @return {Array}
- */
- getObjects: function () {
- return this._objects;
- },
-
/**
* Clears specified context of canvas element
* @param {CanvasRenderingContext2D} ctx Context to clear
@@ -9150,6 +7865,7 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */
renderAll: function (allOnTop) {
var canvasToDrawOn = this[(allOnTop === true && this.interactive) ? 'contextTop' : 'contextContainer'];
+ var activeGroup = this.getActiveGroup();
if (this.contextTop && this.selection && !this._groupSelector) {
this.clearContext(this.contextTop);
@@ -9165,50 +7881,15 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */
fabric.util.clipContext(this, canvasToDrawOn);
}
- if (this.backgroundColor) {
- canvasToDrawOn.fillStyle = this.backgroundColor.toLive
- ? this.backgroundColor.toLive(canvasToDrawOn)
- : this.backgroundColor;
-
- canvasToDrawOn.fillRect(
- this.backgroundColor.offsetX || 0,
- this.backgroundColor.offsetY || 0,
- this.width,
- this.height);
- }
-
- if (typeof this.backgroundImage === 'object') {
- this._drawBackroundImage(canvasToDrawOn);
- }
-
- var activeGroup = this.getActiveGroup();
- for (var i = 0, length = this._objects.length; i < length; ++i) {
- if (!activeGroup ||
- (activeGroup && this._objects[i] && !activeGroup.contains(this._objects[i]))) {
- this._draw(canvasToDrawOn, this._objects[i]);
- }
- }
-
- // delegate rendering to group selection (if one exists)
- if (activeGroup) {
- //Store objects in group preserving order, then replace
- var sortedObjects = [];
- this.forEachObject(function (object) {
- if (activeGroup.contains(object)) {
- sortedObjects.push(object);
- }
- });
- activeGroup._set('objects', sortedObjects);
- this._draw(canvasToDrawOn, activeGroup);
- }
+ this._renderBackground(canvasToDrawOn);
+ this._renderObjects(canvasToDrawOn, activeGroup);
+ this._renderActiveGroup(canvasToDrawOn, activeGroup);
if (this.clipTo) {
canvasToDrawOn.restore();
}
- if (this.overlayImage) {
- canvasToDrawOn.drawImage(this.overlayImage, this.overlayImageLeft, this.overlayImageTop);
- }
+ this._renderOverlay(canvasToDrawOn);
if (this.controlsAboveOverlay && this.interactive) {
this.drawControls(canvasToDrawOn);
@@ -9221,19 +7902,80 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */
/**
* @private
- * @param {CanvasRenderingContext2D} canvasToDrawOn Context to render on
+ * @param {CanvasRenderingContext2D} ctx Context to render on
+ * @param {fabric.Group} activeGroup
*/
- _drawBackroundImage: function(canvasToDrawOn) {
- canvasToDrawOn.save();
- canvasToDrawOn.globalAlpha = this.backgroundImageOpacity;
+ _renderObjects: function(ctx, activeGroup) {
+ for (var i = 0, length = this._objects.length; i < length; ++i) {
+ if (!activeGroup ||
+ (activeGroup && this._objects[i] && !activeGroup.contains(this._objects[i]))) {
+ this._draw(ctx, this._objects[i]);
+ }
+ }
+ },
- if (this.backgroundImageStretch) {
- canvasToDrawOn.drawImage(this.backgroundImage, 0, 0, this.width, this.height);
+ /**
+ * @private
+ * @param {CanvasRenderingContext2D} ctx Context to render on
+ * @param {fabric.Group} activeGroup
+ */
+ _renderActiveGroup: function(ctx, activeGroup) {
+
+ // delegate rendering to group selection (if one exists)
+ if (activeGroup) {
+
+ //Store objects in group preserving order, then replace
+ var sortedObjects = [];
+ this.forEachObject(function (object) {
+ if (activeGroup.contains(object)) {
+ sortedObjects.push(object);
+ }
+ });
+ activeGroup._set('objects', sortedObjects);
+ this._draw(ctx, activeGroup);
}
- else {
- canvasToDrawOn.drawImage(this.backgroundImage, 0, 0);
+ },
+
+ /**
+ * @private
+ * @param {CanvasRenderingContext2D} ctx Context to render on
+ */
+ _renderBackground: function(ctx) {
+ if (this.backgroundColor) {
+ ctx.fillStyle = this.backgroundColor.toLive
+ ? this.backgroundColor.toLive(ctx)
+ : this.backgroundColor;
+
+ ctx.fillRect(
+ this.backgroundColor.offsetX || 0,
+ this.backgroundColor.offsetY || 0,
+ this.width,
+ this.height);
+ }
+ if (this.backgroundImage) {
+ this.backgroundImage.render(ctx);
+ }
+ },
+
+ /**
+ * @private
+ * @param {CanvasRenderingContext2D} ctx Context to render on
+ */
+ _renderOverlay: function(ctx) {
+ if (this.overlayColor) {
+ ctx.fillStyle = this.overlayColor.toLive
+ ? this.overlayColor.toLive(ctx)
+ : this.overlayColor;
+
+ ctx.fillRect(
+ this.overlayColor.offsetX || 0,
+ this.overlayColor.offsetY || 0,
+ this.width,
+ this.height);
+ }
+ if (this.overlayImage) {
+ this.overlayImage.render(ctx);
}
- canvasToDrawOn.restore();
},
/**
@@ -9282,11 +8024,11 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */
/**
* Centers object horizontally.
* You might need to call `setCoords` on an object after centering, to update controls area.
- * @param {fabric.Object} object Object to center
+ * @param {fabric.Object} object Object to center horizontally
* @return {fabric.Canvas} thisArg
*/
centerObjectH: function (object) {
- object.set('left', this.getCenter().left);
+ this._centerObject(object, new fabric.Point(this.getCenter().left, object.getCenterPoint().y));
this.renderAll();
return this;
},
@@ -9294,12 +8036,12 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */
/**
* Centers object vertically.
* You might need to call `setCoords` on an object after centering, to update controls area.
- * @param {fabric.Object} object Object to center
+ * @param {fabric.Object} object Object to center vertically
* @return {fabric.Canvas} thisArg
* @chainable
*/
centerObjectV: function (object) {
- object.set('top', this.getCenter().top);
+ this._centerObject(object, new fabric.Point(object.getCenterPoint().x, this.getCenter().top));
this.renderAll();
return this;
},
@@ -9307,12 +8049,28 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */
/**
* Centers object vertically and horizontally.
* You might need to call `setCoords` on an object after centering, to update controls area.
- * @param {fabric.Object} object Object to center
+ * @param {fabric.Object} object Object to center vertically and horizontally
* @return {fabric.Canvas} thisArg
* @chainable
*/
- centerObject: function (object) {
- return this.centerObjectH(object).centerObjectV(object);
+ centerObject: function(object) {
+ var center = this.getCenter();
+
+ this._centerObject(object, new fabric.Point(center.left, center.top));
+ this.renderAll();
+ return this;
+ },
+
+ /**
+ * @private
+ * @param {fabric.Object} object Object to center
+ * @param {fabric.Point} center Center point
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ */
+ _centerObject: function(object, center) {
+ object.setPositionByOrigin(center, 'center', 'center');
+ return this;
},
/**
@@ -9351,42 +8109,75 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */
if (activeGroup) {
this.discardActiveGroup();
}
+
var data = {
- objects: this.getObjects().map(function (instance) {
- // TODO (kangax): figure out how to clean this up
- var originalValue;
- if (!this.includeDefaultValues) {
- originalValue = instance.includeDefaultValues;
- instance.includeDefaultValues = false;
- }
- var object = instance[methodName](propertiesToInclude);
- if (!this.includeDefaultValues) {
- instance.includeDefaultValues = originalValue;
- }
- return object;
- }, this),
- background: (this.backgroundColor && this.backgroundColor.toObject)
- ? this.backgroundColor.toObject()
- : this.backgroundColor
+ objects: this._toObjects(methodName, propertiesToInclude)
};
- if (this.backgroundImage) {
- data.backgroundImage = this.backgroundImage.src;
- data.backgroundImageOpacity = this.backgroundImageOpacity;
- data.backgroundImageStretch = this.backgroundImageStretch;
- }
- if (this.overlayImage) {
- data.overlayImage = this.overlayImage.src;
- data.overlayImageLeft = this.overlayImageLeft;
- data.overlayImageTop = this.overlayImageTop;
- }
+
+ extend(data, this.__serializeBgOverlay());
+
fabric.util.populateWithProperties(this, data, propertiesToInclude);
+
if (activeGroup) {
this.setActiveGroup(new fabric.Group(activeGroup.getObjects()));
- activeGroup.forEachObject(function(o) { o.set('active', true) });
+ activeGroup.forEachObject(function(o) {
+ o.set('active', true);
+ });
}
return data;
},
+ /**
+ * @private
+ */
+ _toObjects: function(methodName, propertiesToInclude) {
+ return this.getObjects().map(function(instance) {
+ return this._toObject(instance, methodName, propertiesToInclude);
+ }, this);
+ },
+
+ /**
+ * @private
+ */
+ _toObject: function(instance, methodName, propertiesToInclude) {
+ var originalValue;
+
+ if (!this.includeDefaultValues) {
+ originalValue = instance.includeDefaultValues;
+ instance.includeDefaultValues = false;
+ }
+ var object = instance[methodName](propertiesToInclude);
+ if (!this.includeDefaultValues) {
+ instance.includeDefaultValues = originalValue;
+ }
+ return object;
+ },
+
+ /**
+ * @private
+ */
+ __serializeBgOverlay: function() {
+ var data = {
+ background: (this.backgroundColor && this.backgroundColor.toObject)
+ ? this.backgroundColor.toObject()
+ : this.backgroundColor
+ };
+
+ if (this.overlayColor) {
+ data.overlay = this.overlayColor.toObject
+ ? this.overlayColor.toObject()
+ : this.overlayColor;
+ }
+ if (this.backgroundImage) {
+ data.backgroundImage = this.backgroundImage.toObject();
+ }
+ if (this.overlayImage) {
+ data.overlayImage = this.overlayImage.toObject();
+ }
+
+ return data;
+ },
+
/* _TO_SVG_START_ */
/**
* Returns SVG representation of canvas
@@ -9425,8 +8216,29 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */
*/
toSVG: function(options, reviver) {
options || (options = { });
+
var markup = [];
+ this._setSVGPreamble(markup, options);
+ this._setSVGHeader(markup, options);
+
+ this._setSVGBgOverlayColor(markup, 'backgroundColor');
+ this._setSVGBgOverlayImage(markup, 'backgroundImage');
+
+ this._setSVGObjects(markup, reviver);
+
+ this._setSVGBgOverlayColor(markup, 'overlayColor');
+ this._setSVGBgOverlayImage(markup, 'overlayImage');
+
+ markup.push('