diff --git a/.gitignore b/.gitignore deleted file mode 100644 index c97269f3..00000000 --- a/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -node_modules -.DS_Store -before_commit -dist/fabric.js diff --git a/.jscs.json b/.jscs.json new file mode 100644 index 00000000..e33dbf0f --- /dev/null +++ b/.jscs.json @@ -0,0 +1,37 @@ +{ + "requireCurlyBraces": [ "if", "else", "for", "while", "do", "switch", "return", "try", "catch"], + "requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch"], + "requireSpaceBeforeBinaryOperators": ["+", "-", "*", "=", "==", "===", "!=", "!=="], + "requireSpaceAfterBinaryOperators": ["+", "-", "*", "=", "==", "===", "!=", "!=="], + + "requireParenthesesAroundIIFE": true, + "requireSpacesInsideObjectBrackets": "all", + "requireCommaBeforeLineBreak": true, + "requireRightStickedOperators": ["!"], + "requireLeftStickedOperators": [","], + "requireCamelCaseOrUpperCaseIdentifiers": true, + "requireKeywordsOnNewLine": ["else"], + "requireLineFeedAtFileEnd": true, + "requireCapitalizedConstructors": true, + "requireDotNotation": true, + "requireMultipleVarDecl": true, + + "disallowEmptyBlocks": true, + "disallowQuotedKeysInObjects": "allButReserved", + "disallowSpaceAfterObjectKeys": true, + + "disallowSpaceBeforePostfixUnaryOperators": ["++", "--"], + "disallowSpaceAfterPrefixUnaryOperators": ["++", "--"], + + "disallowKeywords": ["with"], + "disallowMultipleLineStrings": true, + "disallowMultipleLineBreaks": true, + "disallowMixedSpacesAndTabs": true, + "disallowTrailingWhitespace": true, + + "validateLineBreaks": "LF", + "validateQuoteMarks": "'", + "validateIndentation": 2, + + "safeContextKeyword": "_this" +} diff --git a/.jshintrc b/.jshintrc index bc589f4d..c05b47de 100644 --- a/.jshintrc +++ b/.jshintrc @@ -1,49 +1,41 @@ { "globals": { + "ActiveXObject": true, + "Cufon": true, "define": true, + "Event": true, "exports": true, "fabric": true, - "Cufon": true, - "Event": true, - "G_vmlCanvasManager": true, - "ActiveXObject": true + "G_vmlCanvasManager": true }, - "node": true, - "es5": false, "browser": true, - - "boss": false, - "curly": false, - "debug": false, - "devel": false, "eqeqeq": true, "eqnull": true, "evil": true, "expr": true, "forin": false, "immed": true, + "lastsemic": true, "laxbreak": true, "loopfunc": true, "multistr": true, "newcap": true, "noarg": true, + "node": true, "noempty": false, - "nonew": false, "nomen": false, + "nonew": false, "onevar": false, "plusplus": false, - "regexp": false, - "undef": true, - "sub": true, "strict": false, - "white": false, + "sub": true, + "undef": true, "unused": true, - "lastsemic": true, - // "maxparams": 4 // "maxcomplexity": 7 - // "maxlen": 100 "maxdepth": 4, + // "maxlen": 100 + // "maxparams": 4 "maxstatements": 30 } diff --git a/.npmignore b/.npmignore index 9d0a93ac..a8c29772 100644 --- a/.npmignore +++ b/.npmignore @@ -2,6 +2,8 @@ src/ lib/ dist/all.min.js dist/all.min.js.gz +dist/fabric.min.js +dist/fabric.min.js.gz .DS_Store HEADER.js -build.js \ No newline at end of file +build.js diff --git a/.sublime-project b/.sublime-project deleted file mode 100644 index 5d274606..00000000 --- a/.sublime-project +++ /dev/null @@ -1,13 +0,0 @@ -{ - "folders": - [ - { - "path": ".", - "folder_exclude_patterns": ["tmp", "log", "node_modules", "docs", "jsdoc-toolkit"], - "file_exclude_patterns": ["*.min.js", "*.require.js", "*.gz", "*.sublime-workspace"] - } - ], - "settings": { - "tab_size": 2 - } -} diff --git a/.travis.yml b/.travis.yml index db1324d2..372ce792 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,10 @@ language: node_js node_js: - - 0.6 - - 0.8 - - 0.10 + - "0.8" + - "0.10" + - "0.11" script: 'npm run-script build && npm test' before_install: - sudo apt-get update -qq - sudo apt-get install -qq libgif-dev libpng-dev libjpeg8-dev libpango1.0-dev libcairo2-dev + - '[ "${TRAVIS_NODE_VERSION}" = "0.6" ] && npm conf set strict-ssl false || true' diff --git a/CHANGELOG.md b/CHANGELOG.md index c1c482f0..c85f7f27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ **Edge** +- [BACK_INCOMPAT] `fabric.Collection#remove` doesn't return removed object -> returns `this` (chainable) + +- Add "mouse:over" and "mouse:out" canvas events (and corresponding "mouseover", "mouseout" object events) +- Add support for passing options to `fabric.createCanvasForNode` + +- Various iText fixes and performance improvements +- Fix `overlayImage` / `overlayColor` during selection mode +- Fix double callback in loadFromJSON when there's no objects +- Fix paths parsing when number has negative exponent +- Fix background offset in iText +- Fix style object deletion in iText +- Fix typo in `_initCanvasHandlers` +- Fix `transformMatrix` not affecting fabric.Text +- Fix `setAngle` for different originX/originY (!= 'center') +- Change default/init noise/brightness value for `fabric.Image.filters.Noise` and `fabric.Image.filters.Brightness` from 100 to 0 +- Add `fabric.Canvas#imageSmoothingEnabled` + **Version 1.4.0** - [BACK_INCOMPAT] JSON and Cufon are no longer included in default build diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 650c3ab0..bc8e9f2f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,7 +34,7 @@ If you are sure that it's a bug in Fabric.js or a suggestion, open a new [issue] perfect, but even a simple script demonstrating the error would suffice. You could use [this jsfiddle template](http://jsfiddle.net/fabricjs/Da7SP/) as a starting point. -- **Fabric.js version:** Make sure to specify which version of Fabric.js you are using. The version can be found in [all.js file](https://github.com/kangax/fabric.js/blob/master/dist/all.js#L5) or just by executing `fabric.version` in the browser console. +- **Fabric.js version:** Make sure to specify which version of Fabric.js you are using. The version can be found in [fabric.js file](https://github.com/kangax/fabric.js/blob/master/dist/fabric.js#L5) or just by executing `fabric.version` in the browser console. ## Pull requests diff --git a/HEADER.js b/HEADER.js index a9ca765d..a7206fe5 100644 --- a/HEADER.js +++ b/HEADER.js @@ -1,6 +1,6 @@ -/*! Fabric.js Copyright 2008-2013, Printio (Juriy Zaytsev, Maxim Chernyak) */ +/*! Fabric.js Copyright 2008-2014, Printio (Juriy Zaytsev, Maxim Chernyak) */ -var fabric = fabric || { version: "1.4.0" }; +var fabric = fabric || { version: "1.4.6" }; if (typeof exports !== 'undefined') { exports.fabric = fabric; } @@ -36,6 +36,7 @@ fabric.isLikelyNode = typeof Buffer !== 'undefined' && * @type array */ fabric.SHARED_ATTRIBUTES = [ + "display", "transform", "fill", "fill-opacity", "fill-rule", "opacity", diff --git a/LICENSE b/LICENSE index cdd6c9d3..efa45ab9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2008-2013 Printio (Juriy Zaytsev, Maxim Chernyak) +Copyright (c) 2008-2014 Printio (Juriy Zaytsev, Maxim Chernyak) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -13,4 +13,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/README.md b/README.md index 11b4bc86..fea7bd8a 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,15 @@ - - - - ### Fabric + [![Build Status](https://secure.travis-ci.org/kangax/fabric.js.png?branch=master)](http://travis-ci.org/#!/kangax/fabric.js) [![Code Climate](https://codeclimate.com/repos/526a0ed089af7e6cf2001389/badges/d1c922dd1511ffa8a72f/gpa.png)](https://codeclimate.com/repos/526a0ed089af7e6cf2001389/feed) [![Coverage Status](https://coveralls.io/repos/kangax/fabric.js/badge.png?branch=master)](https://coveralls.io/r/kangax/fabric.js?branch=master) - +[![NPM version](https://badge.fury.io/js/fabric.png)](http://badge.fury.io/js/fabric) +[![Bower version](https://badge.fury.io/bo/fabric.png)](http://badge.fury.io/bo/fabric) + + +[![Dependency Status](https://david-dm.org/kangax/fabric.js.png?theme=shields.io)](https://david-dm.org/kangax/fabric.js) +[![devDependency Status](https://david-dm.org/kangax/fabric.js/dev-status.png?theme=shields.io)](https://david-dm.org/kangax/fabric.js#info=devDependencies) + **Fabric.js** is a framework that makes it easy to work with HTML5 canvas element. It is an **interactive object model** on top of canvas element. It is also an **SVG-to-canvas parser**. @@ -18,7 +21,7 @@ Using Fabric.js, you can create and populate objects on canvas; objects like sim ### Goals -- Unit tested (2300+ tests at the moment) +- Unit tested (2400+ tests at the moment) - Modular (~60 small ["classes", modules, mixins](http://fabricjs.com/docs/)) - Cross-browser - [Fast](https://github.com/kangax/fabric.js/wiki/Focus-on-speed) @@ -104,7 +107,15 @@ Fabric.js started as a foundation for design editor on [printio.ru](http://print If you use google closure compiler you have to add `sourceMappingURL` manually at the end of the minified file all.min.js (see issue https://code.google.com/p/closure-compiler/issues/detail?id=941). - //# sourceMappingURL=all.min.js.map + //# sourceMappingURL=fabric.min.js.map + +6. Lint source code (prerequisite: `npm -g install jshint`) + + $ jshint src + +7. Ensure code guidelines are met (prerequisite: `npm -g install jscs`) + + $ jscs src ### Demos @@ -112,6 +123,8 @@ Fabric.js started as a foundation for design editor on [printio.ru](http://print - [Kitchensink demo](http://fabricjs.com/kitchensink/) - [Benchmarks](http://fabricjs.com/benchmarks/) +[Who's using Fabric?](http://trends.builtwith.com/javascript/FabricJS) + ### Documentation Documentation is always available at [http://fabricjs.com/docs/](http://fabricjs.com/docs/). @@ -137,11 +150,11 @@ These are the optional modules that could be specified for inclusion, when build Additional flags for build script are: -- **requirejs** — Makes fabric requirejs AMD-compatible in `dist/all.js`. *Note:* an unminified, requirejs-compatible version is always created in `dist/all.require.js` +- **requirejs** — Makes fabric requirejs AMD-compatible in `dist/fabric.js`. *Note:* an unminified, requirejs-compatible version is always created in `dist/fabric.require.js` - **no-strict** — Strips "use strict" directives from source - **no-svg-export** — Removes svg exporting functionality - **no-es5-compat** - Removes ES5 compat methods (Array.prototype.*, String.prototype.*, Function.prototype.*) -- **sourcemap** - Generates a sourceMap file and adds the `sourceMappingURL` (only if uglifyjs is used) to `dist/all.min.js` +- **sourcemap** - Generates a sourceMap file and adds the `sourceMappingURL` (only if uglifyjs is used) to `dist/fabric.min.js` For example: @@ -151,19 +164,36 @@ For example: #### Adding red rectangle to canvas +```html + + + + + - ... - var canvas = new fabric.Canvas('canvas'); - var rect = new fabric.Rect({ - top: 100, - left: 100, - width: 60, - height: 70, - fill: 'red' - }); + + + + +``` + +### Helping Fabric + +- [Fabric on Bountysource](https://www.bountysource.com/trackers/23217-fabric-js) +- [Fabric on CodeTriage](http://www.codetriage.com/kangax/fabric.js) ### Staying in touch @@ -175,6 +205,8 @@ See [Fabric questions on Stackoverflow](stackoverflow.com/questions/tagged/fabri Fabric snippets on [jsfiddle](http://jsfiddle.net/user/fabricjs/fiddles/) or [codepen.io](http://codepen.io/tag/fabricjs). +Fabric on [LibKnot](http://libknot.ohmztech.com/). + Get help in Fabric's IRC channel — irc://irc.freenode.net/#fabric.js ### Credits @@ -187,7 +219,7 @@ Get help in Fabric's IRC channel — irc://irc.freenode.net/#fabric.js ### MIT License -Copyright (c) 2008-2013 Printio (Juriy Zaytsev, Maxim Chernyak) +Copyright (c) 2008-2014 Printio (Juriy Zaytsev, Maxim Chernyak) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -203,3 +235,6 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/kangax/fabric.js/trend.png)](https://bitdeli.com/free "Bitdeli Badge") diff --git a/bower.json b/bower.json new file mode 100644 index 00000000..b9833d9e --- /dev/null +++ b/bower.json @@ -0,0 +1,28 @@ +{ + "name": "fabric.js", + "version": "1.4.6", + "homepage": "http://fabricjs.com", + "authors": [ + "kangax", "Kienz" + ], + "description": "Object model for HTML5 canvas, and SVG-to-canvas parser. Backed by jsdom and node-canvas.", + "main": "./dist/fabric.min.js", + "ignore": [ + "lib", + "src", + "test", + "*.*", + ".*" + ], + "keywords": [ + "canvas", + "graphic", + "graphics", + "SVG", + "node-canvas", + "parser", + "HTML5", + "object model" + ], + "license": "MIT" +} diff --git a/build.js b/build.js index 15e4b4a4..3959b3a2 100644 --- a/build.js +++ b/build.js @@ -3,8 +3,7 @@ var fs = require('fs'), var buildArgs = process.argv.slice(2), buildArgsAsObject = { }, - rootPath = process.cwd(), - distributionPath = 'dist/'; + rootPath = process.cwd(); buildArgs.forEach(function(arg) { var key = arg.split('=')[0], @@ -16,6 +15,7 @@ buildArgs.forEach(function(arg) { var modulesToInclude = buildArgsAsObject.modules ? buildArgsAsObject.modules.split(',') : [ ]; var modulesToExclude = buildArgsAsObject.exclude ? buildArgsAsObject.exclude.split(',') : [ ]; +var distributionPath = buildArgsAsObject.dest || 'dist/'; var minifier = buildArgsAsObject.minifier || 'uglifyjs'; var mininfierCmd; @@ -43,17 +43,17 @@ if (sourceMap) { console.log('[notice]: sourceMap support requires uglifyjs or google closure compiler as minifier; changed minifier to uglifyjs.'); minifier = 'uglifyjs'; } - sourceMapFlags = minifier === 'uglifyjs' ? ' --source-map all.min.js.map' : ' --create_source_map all.min.js.map --source_map_format=V3'; + sourceMapFlags = minifier === 'uglifyjs' ? ' --source-map fabric.min.js.map' : ' --create_source_map fabric.min.js.map --source_map_format=V3'; } if (minifier === 'yui') { - mininfierCmd = 'java -jar ' + rootPath + '/lib/yuicompressor-2.4.6.jar all.js -o all.min.js'; + mininfierCmd = 'java -jar ' + rootPath + '/lib/yuicompressor-2.4.6.jar fabric.js -o fabric.min.js'; } else if (minifier === 'closure') { - mininfierCmd = 'java -jar ' + rootPath + '/lib/google_closure_compiler.jar --js all.js --js_output_file all.min.js' + sourceMapFlags; + mininfierCmd = 'java -jar ' + rootPath + '/lib/google_closure_compiler.jar --js fabric.js --js_output_file fabric.min.js' + sourceMapFlags; } else if (minifier === 'uglifyjs') { - mininfierCmd = 'uglifyjs ' + amdUglifyFlags + ' --output all.min.js all.js' + sourceMapFlags; + mininfierCmd = 'uglifyjs ' + amdUglifyFlags + ' --output fabric.min.js fabric.js' + sourceMapFlags; } var buildSh = 'build-sh' in buildArgsAsObject; @@ -233,6 +233,7 @@ var filesToInclude = [ ifSpecifiedInclude('image_filters', 'src/filters/sepia_filter.class.js'), ifSpecifiedInclude('image_filters', 'src/filters/sepia2_filter.class.js'), ifSpecifiedInclude('image_filters', 'src/filters/tint_filter.class.js'), + ifSpecifiedInclude('image_filters', 'src/filters/multiply_filter.class.js'), ifSpecifiedInclude('text', 'src/shapes/text.class.js'), ifSpecifiedInclude('cufon', 'src/shapes/text.cufon.js'), @@ -285,7 +286,7 @@ else { process.chdir(distributionPath); appendFileContents(filesToInclude, function() { - fs.writeFile('all.js', distFileContents, function (err) { + fs.writeFile('fabric.js', distFileContents, function (err) { if (err) { console.log(err); throw err; @@ -293,13 +294,13 @@ else { // add js wrapping in AMD closure for requirejs if necessary if (amdLib !== false) { - exec('uglifyjs all.js ' + amdUglifyFlags + ' -b --output all.js'); + exec('uglifyjs fabric.js ' + amdUglifyFlags + ' -b --output fabric.js'); } if (amdLib !== false) { - console.log('Built distribution to ' + distributionPath + 'all.js (' + amdLib + '-compatible)'); + console.log('Built distribution to ' + distributionPath + 'fabric.js (' + amdLib + '-compatible)'); } else { - console.log('Built distribution to ' + distributionPath + 'all.js'); + console.log('Built distribution to ' + distributionPath + 'fabric.js'); } exec(mininfierCmd, function (error, output) { @@ -307,18 +308,18 @@ else { console.error('Minification failed using', minifier, 'with', mininfierCmd); process.exit(1); } - console.log('Minified using', minifier, 'to ' + distributionPath + 'all.min.js'); + console.log('Minified using', minifier, 'to ' + distributionPath + 'fabric.min.js'); if (sourceMapFlags) { - console.log('Built sourceMap to ' + distributionPath + 'all.min.js.map'); + console.log('Built sourceMap to ' + distributionPath + 'fabric.min.js.map'); } - exec('gzip -c all.min.js > all.min.js.gz', function (error, output) { - console.log('Gzipped to ' + distributionPath + 'all.min.js.gz'); + exec('gzip -c fabric.min.js > fabric.min.js.gz', function (error, output) { + console.log('Gzipped to ' + distributionPath + 'fabric.min.js.gz'); }); }); - // Always build requirejs AMD module in all.require.js + // Always build requirejs AMD module in fabric.require.js // add necessary requirejs footer code to filesToInclude if we haven't before if (amdLib === false) { amdLib = "requirejs"; @@ -326,13 +327,13 @@ else { } appendFileContents(filesToInclude, function() { - fs.writeFile('all.require.js', distFileContents, function (err) { + fs.writeFile('fabric.require.js', distFileContents, function (err) { if (err) { console.log(err); throw err; } - exec('uglifyjs all.require.js ' + amdUglifyFlags + ' -b --output all.require.js'); - console.log('Built distribution to ' + distributionPath + 'all.require.js (requirejs-compatible)'); + exec('uglifyjs fabric.require.js ' + amdUglifyFlags + ' -b --output fabric.require.js'); + console.log('Built distribution to ' + distributionPath + 'fabric.require.js (requirejs-compatible)'); }); }); diff --git a/component.json b/component.json deleted file mode 100644 index a0b73bbf..00000000 --- a/component.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "fabric.js", - "repo": "kangax/fabric.js", - "description": "Object model for HTML5 canvas, and SVG-to-canvas parser. Backed by jsdom and node-canvas.", - "version": "1.4.0", - "keywords": ["canvas", "graphic", "graphics", "SVG", "node-canvas", "parser", "HTML5", "object model"], - "dependencies": {}, - "development": {}, - "license": "MIT", - "scripts": [ - "./dist/all.js" - ] -} diff --git a/dist/all.min.js b/dist/all.min.js deleted file mode 100644 index 80028d34..00000000 --- a/dist/all.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/* build: `node build.js modules=ALL exclude=gestures,cufon,json minifier=uglifyjs` *//*! Fabric.js Copyright 2008-2013, Printio (Juriy Zaytsev, Maxim Chernyak) */var fabric=fabric||{version:"1.4.0"};typeof exports!="undefined"&&(exports.fabric=fabric),typeof document!="undefined"&&typeof window!="undefined"?(fabric.document=document,fabric.window=window):(fabric.document=require("jsdom").jsdom(""),fabric.window=fabric.document.createWindow()),fabric.isTouchSupported="ontouchstart"in fabric.document.documentElement,fabric.isLikelyNode=typeof Buffer!="undefined"&&typeof window=="undefined",fabric.SHARED_ATTRIBUTES=["transform","fill","fill-opacity","fill-rule","opacity","stroke","stroke-dasharray","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke-width"],function(){function e(e,t){if(!this.__eventListeners[e])return;t?fabric.util.removeFromArray(this.__eventListeners[e],t):this.__eventListeners[e].length=0}function t(e,t){this.__eventListeners||(this.__eventListeners={});if(arguments.length===1)for(var n in e)this.on(n,e[n]);else this.__eventListeners[e]||(this.__eventListeners[e]=[]),this.__eventListeners[e].push(t);return this}function n(t,n){if(!this.__eventListeners)return;if(arguments.length===0)this.__eventListeners={};else if(arguments.length===1&&typeof arguments[0]=="object")for(var r in t)e.call(this,r,t[r]);else e.call(this,t,n);return this}function r(e,t){if(!this.__eventListeners)return;var n=this.__eventListeners[e];if(!n)return;for(var r=0,i=n.length;r-1},complexity:function(){return this.getObjects().reduce(function(e,t){return e+=t.complexity?t.complexity():0,e},0)}},function(e){var t=Math.sqrt,n=Math.atan2,r=Math.PI/180;fabric.util={removeFromArray:function(e,t){var n=e.indexOf(t);return n!==-1&&e.splice(n,1),e},getRandomInt:function(e,t){return Math.floor(Math.random()*(t-e+1))+e},degreesToRadians:function(e){return e*r},radiansToDegrees:function(e){return e/r},rotatePoint:function(e,t,n){var r=Math.sin(n),i=Math.cos(n);e.subtractEquals(t);var s=e.x*i-e.y*r,o=e.x*r+e.y*i;return(new fabric.Point(s,o)).addEquals(t)},toFixed:function(e,t){return parseFloat(Number(e).toFixed(t))},falseFunction:function(){return!1},getKlass:function(e,t){return e=fabric.util.string.camelize(e.charAt(0).toUpperCase()+e.slice(1)),fabric.util.resolveNamespace(t)[e]},resolveNamespace:function(t){if(!t)return fabric;var n=t.split("."),r=n.length,i=e||fabric.window;for(var s=0;s1?r=new fabric.PathGroup(e,t):r=e[0],typeof n!="undefined"&&r.setSourcePath(n),r},populateWithProperties:function(e,t,n){if(n&&Object.prototype.toString.call(n)==="[object Array]")for(var r=0,i=n.length;rr)r+=u[p++%h],r>l&&(r=l),e[d?"lineTo":"moveTo"](r,0),d=!d;e.restore()},createCanvasElement:function(e){return e||(e=fabric.document.createElement("canvas")),!e.getContext&&typeof G_vmlCanvasManager!="undefined"&&G_vmlCanvasManager.initElement(e),e},createImage:function(){return fabric.isLikelyNode?new(require("canvas").Image):fabric.document.createElement("img")},createAccessors:function(e){var t=e.prototype;for(var n=t.stateProperties.length;n--;){var r=t.stateProperties[n],i=r.charAt(0).toUpperCase()+r.slice(1),s="set"+i,o="get"+i;t[o]||(t[o]=function(e){return new Function('return this.get("'+e+'")')}(r)),t[s]||(t[s]=function(e){return new Function("value",'return this.set("'+e+'", value)')}(r))}},clipContext:function(e,t){t.save(),t.beginPath(),e.clipTo(t),t.clip()},multiplyTransformMatrices:function(e,t){var n=[[e[0],e[2],e[4]],[e[1],e[3],e[5]],[0,0,1]],r=[[t[0],t[2],t[4]],[t[1],t[3],t[5]],[0,0,1]],i=[];for(var s=0;s<3;s++){i[s]=[];for(var o=0;o<3;o++){var u=0;for(var a=0;a<3;a++)u+=n[s][a]*r[a][o];i[s][o]=u}}return[i[0][0],i[1][0],i[0][1],i[1][1],i[0][2],i[1][2]]},getFunctionBody:function(e){return(String(e).match(/function[^{]*\{([\s\S]*)\}/)||{})[1]},normalizePoints:function(e,t){var n=fabric.util.array.min(e,"x"),r=fabric.util.array.min(e,"y");n=n<0?n:0,r=n<0?r:0;for(var i=0,s=e.length;i0&&(t>r?t-=r:t=0,n>r?n-=r:n=0);var i=!0,s=e.getImageData(t,n,r*2||1,r*2||1);for(var o=3,u=s.data.length;o0&&f===0&&(E-=2*Math.PI);var S=Math.ceil(Math.abs(E/(Math.PI*.5+.001))),x=[];for(var T=0;T1&&(h=Math.sqrt(h),t*=h,n*=h);var p=f/t,d=a/t,v=-a/n,m=f/n;return{x0:p*r+d*i,y0:v*r+m*i,x1:p*s+d*o,y1:v*s+m*o,sin_th:a,cos_th:f}}function o(e,i,s,o,u,a,f,l){r=n.call(arguments);if(t[r])return t[r];var c=l*u,h=-f*a,p=f*u,d=l*a,v=.5*(o-s),m=8/3*Math.sin(v*.5)*Math.sin(v*.5)/Math.sin(v),g=e+Math.cos(s)-m*Math.sin(s),y=i+Math.sin(s)+m*Math.cos(s),b=e+Math.cos(o),w=i+Math.sin(o),E=b+m*Math.sin(o),S=w-m*Math.cos(o);return t[r]=[c*g+h*y,p*g+d*y,c*E+h*S,p*E+d*S,c*b+h*w,p*b+d*w],t[r]}var e={},t={},n=Array.prototype.join,r;fabric.util.drawArc=function(e,t,n,r){var s=r[0],u=r[1],a=r[2],f=r[3],l=r[4],c=r[5],h=r[6],p=i(c,h,s,u,f,l,a,t,n);for(var d=0;d=t})}function r(e,t){return i(e,t,function(e,t){return e>>0;if(n===0)return-1;var r=0;arguments.length>0&&(r=Number(arguments[1]),r!==r?r=0:r!==0&&r!==Number.POSITIVE_INFINITY&&r!==Number.NEGATIVE_INFINITY&&(r=(r>0||-1)*Math.floor(Math.abs(r))));if(r>=n)return-1;var i=r>=0?r:Math.max(n-Math.abs(r),0);for(;i>>0;n>>0;r>>0;n>>0;n>>0;i>>0,n=0,r;if(arguments.length>1)r=arguments[1];else do{if(n in this){r=this[n++];break}if(++n>=t)throw new TypeError}while(!0);for(;n/g,">")}String.prototype.trim||(String.prototype.trim=function(){return this.replace(/^[\s\xA0]+/,"").replace(/[\s\xA0]+$/,"")}),fabric.util.string={camelize:e,capitalize:t,escapeXml:n}}(),function(){var e=Array.prototype.slice,t=Function.prototype.apply,n=function(){};Function.prototype.bind||(Function.prototype.bind=function(r){var i=this,s=e.call(arguments,1),o;return s.length?o=function(){return t.call(i,this instanceof n?this:r,s.concat(e.call(arguments)))}:o=function(){return t.call(i,this instanceof n?this:r,arguments)},n.prototype=this.prototype,o.prototype=new n,o})}(),function(){function i(){}function s(t){var n=this.constructor.superclass.prototype[t];return arguments.length>1?n.apply(this,e.call(arguments,1)):n.call(this)}function o(){function u(){this.initialize.apply(this,arguments)}var n=null,o=e.call(arguments,0);typeof o[0]=="function"&&(n=o.shift()),u.superclass=n,u.subclasses=[],n&&(i.prototype=n.prototype,u.prototype=new i,n.subclasses.push(u));for(var a=0,f=o.length;a-1?e.prototype[i]=function(e){return function(){var n=this.constructor.superclass;this.constructor.superclass=r;var i=t[e].apply(this,arguments);this.constructor.superclass=n;if(e!=="initialize")return i}}(i):e.prototype[i]=t[i],n&&(t.toString!==Object.prototype.toString&&(e.prototype.toString=t.toString),t.valueOf!==Object.prototype.valueOf&&(e.prototype.valueOf=t.valueOf))};fabric.util.createClass=o}(),function(){function t(e){var t=Array.prototype.slice.call(arguments,1),n,r,i=t.length;for(r=0;r-1?s(e,t.match(/opacity:\s*(\d?\.?\d*)/)[1]):e;for(var r in t)if(r==="opacity")s(e,t[r]);else{var i=r==="float"||r==="cssFloat"?typeof n.styleFloat=="undefined"?"cssFloat":"styleFloat":r;n[i]=t[r]}return e}var t=fabric.document.createElement("div"),n=typeof t.style.opacity=="string",r=typeof t.style.filter=="string",i=/alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/,s=function(e){return e};n?s=function(e,t){return e.style.opacity=t,e}:r&&(s=function(e,t){var n=e.style;return e.currentStyle&&!e.currentStyle.hasLayout&&(n.zoom=1),i.test(n.filter)?(t=t>=.9999?"":"alpha(opacity="+t*100+")",n.filter=n.filter.replace(i,t)):n.filter+=" alpha(opacity="+t*100+")",e}),fabric.util.setStyle=e}(),function(){function t(e){return typeof e=="string"?fabric.document.getElementById(e):e}function s(e,t){var n=fabric.document.createElement(e);for(var r in t)r==="class"?n.className=t[r]:r==="for"?n.htmlFor=t[r]:n.setAttribute(r,t[r]);return n}function o(e,t){(" "+e.className+" ").indexOf(" "+t+" ")===-1&&(e.className+=(e.className?" ":"")+t)}function u(e,t,n){return typeof t=="string"&&(t=s(t,n)),e.parentNode&&e.parentNode.replaceChild(t,e),t.appendChild(e),t}function a(e,t){var n,r,i=0,s=0,o=fabric.document.documentElement,u=fabric.document.body||{scrollLeft:0,scrollTop:0};r=e;while(e&&e.parentNode&&!n)e=e.parentNode,e!==fabric.document&&fabric.util.getElementStyle(e,"position")==="fixed"&&(n=e),e!==fabric.document&&r!==t&&fabric.util.getElementStyle(e,"position")==="absolute"?(i=0,s=0):e===fabric.document?(i=u.scrollLeft||o.scrollLeft||0,s=u.scrollTop||o.scrollTop||0):(i+=e.scrollLeft||0,s+=e.scrollTop||0);return{left:i,top:s}}function f(e){var t,n={left:0,top:0},r=e&&e.ownerDocument,i={left:0,top:0},s,o={borderLeftWidth:"left",borderTopWidth:"top",paddingLeft:"left",paddingTop:"top"};if(!r)return{left:0,top:0};for(var u in o)i[o[u]]+=parseInt(l(e,u),10)||0;return t=r.documentElement,typeof e.getBoundingClientRect!="undefined"&&(n=e.getBoundingClientRect()),s=fabric.util.getScrollLeftTop(e,null),{left:n.left+s.left-(t.clientLeft||0)+i.left,top:n.top+s.top-(t.clientTop||0)+i.top}}function l(e,t){e.style||(e.style={});if(fabric.document.defaultView&&fabric.document.defaultView.getComputedStyle)return fabric.document.defaultView.getComputedStyle(e,null)[t];var n=e.style[t];return!n&&e.currentStyle&&(n=e.currentStyle[t]),n}var e=Array.prototype.slice,n=function(t){return e.call(t,0)},r;try{r=n(fabric.document.childNodes)instanceof Array}catch(i){}r||(n=function(e){var t=new Array(e.length),n=e.length;while(n--)t[n]=e[n];return t}),function(){function n(e){return typeof e.onselectstart!="undefined"&&(e.onselectstart=fabric.util.falseFunction),t?e.style[t]="none":typeof e.unselectable=="string"&&(e.unselectable="on"),e}function r(e){return typeof e.onselectstart!="undefined"&&(e.onselectstart=null),t?e.style[t]="":typeof e.unselectable=="string"&&(e.unselectable=""),e}var e=fabric.document.documentElement.style,t="userSelect"in e?"userSelect":"MozUserSelect"in e?"MozUserSelect":"WebkitUserSelect"in e?"WebkitUserSelect":"KhtmlUserSelect"in e?"KhtmlUserSelect":"";fabric.util.makeElementUnselectable=n,fabric.util.makeElementSelectable=r}(),function(){function e(e,t){var n=fabric.document.getElementsByTagName("head")[0],r=fabric.document.createElement("script"),i=!0;r.onload=r.onreadystatechange=function(e){if(i){if(typeof this.readyState=="string"&&this.readyState!=="loaded"&&this.readyState!=="complete")return;i=!1,t(e||fabric.window.event),r=r.onload=r.onreadystatechange=null}},r.src=e,n.appendChild(r)}fabric.util.getScript=e}(),fabric.util.getById=t,fabric.util.toArray=n,fabric.util.makeElement=s,fabric.util.addClass=o,fabric.util.wrapElement=u,fabric.util.getScrollLeftTop=a,fabric.util.getElementOffset=f,fabric.util.getElementStyle=l}(),function(){function e(e,t){return e+(/\?/.test(e)?"&":"?")+t}function n(){}function r(r,i){i||(i={});var s=i.method?i.method.toUpperCase():"GET",o=i.onComplete||function(){},u=t(),a;return u.onreadystatechange=function(){u.readyState===4&&(o(u),u.onreadystatechange=n)},s==="GET"&&(a=null,typeof i.parameters=="string"&&(r=e(r,i.parameters))),u.open(s,r,!0),(s==="POST"||s==="PUT")&&u.setRequestHeader("Content-Type","application/x-www-form-urlencoded"),u.send(a),u}var t=function(){var e=[function(){return new ActiveXObject("Microsoft.XMLHTTP")},function(){return new ActiveXObject("Msxml2.XMLHTTP")},function(){return new ActiveXObject("Msxml2.XMLHTTP.3.0")},function(){return new XMLHttpRequest}];for(var t=e.length;t--;)try{var n=e[t]();if(n)return e[t]}catch(r){}}();fabric.util.request=r}(),fabric.log=function(){},fabric.warn=function(){},typeof console!="undefined"&&["log","warn"].forEach(function(e){typeof console[e]!="undefined"&&console[e].apply&&(fabric[e]=function(){return console[e].apply(console,arguments)})}),function(){function e(e){n(function(t){e||(e={});var r=t||+(new Date),i=e.duration||500,s=r+i,o,u=e.onChange||function(){},a=e.abort||function(){return!1},f=e.easing||function(e,t,n,r){return-n*Math.cos(e/r*(Math.PI/2))+n+t},l="startValue"in e?e.startValue:0,c="endValue"in e?e.endValue:100,h=e.byValue||c-l;e.onStart&&e.onStart(),function p(t){o=t||+(new Date);var c=o>s?i:o-r;if(a()){e.onComplete&&e.onComplete();return}u(f(c,l,h,i));if(o>s){e.onComplete&&e.onComplete();return}n(p)}(r)})}var t=fabric.window.requestAnimationFrame||fabric.window.webkitRequestAnimationFrame||fabric.window.mozRequestAnimationFrame||fabric.window.oRequestAnimationFrame||fabric.window.msRequestAnimationFrame||function(e){fabric.window.setTimeout(e,1e3/60)},n=function(){return t.apply(fabric.window,arguments)};fabric.util.animate=e,fabric.util.requestAnimFrame=n}(),function(){function e(e,t,n,r){return e','')}var t=e.fabric||(e.fabric={}),n=t.util.object.extend,r=t.util.string.capitalize,i=t.util.object.clone,s=t.util.toFixed,o=t.util.multiplyTransformMatrices,u={"fill-opacity":"fillOpacity","fill-rule":"fillRule","font-family":"fontFamily","font-size":"fontSize","font-style":"fontStyle","font-weight":"fontWeight",cx:"left",x:"left",r:"radius","stroke-dasharray":"strokeDashArray","stroke-linecap":"strokeLineCap","stroke-linejoin":"strokeLineJoin","stroke-miterlimit":"strokeMiterLimit","stroke-opacity":"strokeOpacity","stroke-width":"strokeWidth","text-decoration":"textDecoration",cy:"top",y:"top",transform:"transformMatrix"},a={stroke:"strokeOpacity",fill:"fillOpacity"};t.parseTransformAttribute=function(){function e(e,t){var n=t[0];e[0]=Math.cos(n),e[1]=Math.sin(n),e[2]=-Math.sin(n),e[3]=Math.cos(n)}function n(e,t){var n=t[0],r=t.length===2?t[1]:t[0];e[0]=n,e[3]=r}function r(e,t){e[2]=t[0]}function i(e,t){e[1]=t[0]}function s(e,t){e[4]=t[0],t.length===2&&(e[5]=t[1])}var o=[1,0,0,1,0,0],u="(?:[-+]?\\d+(?:\\.\\d+)?(?:e[-+]?\\d+)?)",a="(?:\\s+,?\\s*|,\\s*)",f="(?:(skewX)\\s*\\(\\s*("+u+")\\s*\\))",l="(?:(skewY)\\s*\\(\\s*("+u+")\\s*\\))",c="(?:(rotate)\\s*\\(\\s*("+u+")(?:"+a+"("+u+")"+a+"("+u+"))?\\s*\\))",h="(?:(scale)\\s*\\(\\s*("+u+")(?:"+a+"("+u+"))?\\s*\\))",p="(?:(translate)\\s*\\(\\s*("+u+")(?:"+a+"("+u+"))?\\s*\\))",d="(?:(matrix)\\s*\\(\\s*("+u+")"+a+"("+u+")"+a+"("+u+")"+a+"("+u+")"+a+"("+u+")"+a+"("+u+")"+"\\s*\\))",v="(?:"+d+"|"+p+"|"+h+"|"+c+"|"+f+"|"+l+")",m="(?:"+v+"(?:"+a+v+")*"+")",g="^\\s*(?:"+m+"?)\\s*$",y=new RegExp(g),b=new RegExp(v,"g");return function(u){var a=o.concat(),f=[];if(!u||u&&!y.test(u))return a;u.replace(b,function(t){var u=(new RegExp(v)).exec(t).filter(function(e){return e!==""&&e!=null}),l=u[1],c=u.slice(2).map(parseFloat);switch(l){case"translate":s(a,c);break;case"rotate":e(a,c);break;case"scale":n(a,c);break;case"skewX":r(a,c);break;case"skewY":i(a,c);break;case"matrix":a=c}f.push(a.concat()),a=o.concat()});var l=f[0];while(f.length>1)f.shift(),l=t.util.multiplyTransformMatrices(l,f[0]);return l}}(),t.parseSVGDocument=function(){function s(e,t){while(e&&(e=e.parentNode))if(t.test(e.nodeName))return!0;return!1}var e=/^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/,n="(?:[-+]?\\d+(?:\\.\\d+)?(?:e[-+]?\\d+)?)",r=new RegExp("^\\s*("+n+"+)\\s*,?"+"\\s*("+n+"+)\\s*,?"+"\\s*("+n+"+)\\s*,?"+"\\s*("+n+"+)\\s*"+"$");return function(n,o,u){if(!n)return;var a=new Date,f=t.util.toArray(n.getElementsByTagName("*"));if(f.length===0){f=n.selectNodes("//*[name(.)!='svg']");var l=[];for(var c=0,h=f.length;c-1;e=e.split(/\s+/);var n=[],r,i;if(t){r=0,i=e.length;for(;r/i,"")));if(!s.documentElement)return;t.parseSVGDocument(s.documentElement,function(r,i){m.set(e,{objects:t.util.array.invoke(r,"toObject"),options:i}),n(r,i)},r)}e=e.replace(/^\n\s*/,"").trim(),m.has(e,function(r){r?m.get(e,function(e){var t=g(e);n(t.objects,t.options)}):new t.util.request(e,{method:"get",onComplete:i})})},loadSVGFromString:function(e,n,r){e=e.trim();var i;if(typeof DOMParser!="undefined"){var s=new DOMParser;s&&s.parseFromString&&(i=s.parseFromString(e,"text/xml"))}else t.window.ActiveXObject&&(i=new ActiveXObject("Microsoft.XMLDOM"),i.async="false",i.loadXML(e.replace(//i,"")));t.parseSVGDocument(i.documentElement,function(e,t){n(e,t)},r)},createSVGFontFacesMarkup:function(e){var t="";for(var n=0,r=e.length;n',"",""].join("")),t},createSVGRefElementsMarkup:function(e){var t=[];return y(t,e,"backgroundColor"),y(t,e,"overlayColor"),t.join("")}})}(typeof exports!="undefined"?exports:this),fabric.ElementsParser={parse:function(e,t,n,r){this.elements=e,this.callback=t,this.options=n,this.reviver=r,this.instances=new Array(e.length),this.numElements=e.length,this.createObjects()},createObjects:function(){for(var e=0,t=this.elements.length;ee.x&&this.y>e.y},gte:function(e){return this.x>=e.x&&this.y>=e.y},lerp:function(e,t){return new n(this.x+(e.x-this.x)*t,this.y+(e.y-this.y)*t)},distanceFrom:function(e){var t=this.x-e.x,n=this.y-e.y;return Math.sqrt(t*t+n*n)},midPointFrom:function(e){return new n(this.x+(e.x-this.x)/2,this.y+(e.y-this.y)/2)},min:function(e){return new n(Math.min(this.x,e.x),Math.min(this.y,e.y))},max:function(e){return new n(Math.max(this.x,e.x),Math.max(this.y,e.y))},toString:function(){return this.x+","+this.y},setXY:function(e,t){this.x=e,this.y=t},setFromPoint:function(e){this.x=e.x,this.y=e.y},swap:function(e){var t=this.x,n=this.y;this.x=e.x,this.y=e.y,e.x=t,e.y=n}}}(typeof exports!="undefined"?exports:this),function(e){"use strict";function n(e){this.status=e,this.points=[]}var t=e.fabric||(e.fabric={});if(t.Intersection){t.warn("fabric.Intersection is already defined");return}t.Intersection=n,t.Intersection.prototype={appendPoint:function(e){this.points.push(e)},appendPoints:function(e){this.points=this.points.concat(e)}},t.Intersection.intersectLineLine=function(e,r,i,s){var o,u=(s.x-i.x)*(e.y-i.y)-(s.y-i.y)*(e.x-i.x),a=(r.x-e.x)*(e.y-i.y)-(r.y-e.y)*(e.x-i.x),f=(s.y-i.y)*(r.x-e.x)-(s.x-i.x)*(r.y-e.y);if(f!==0){var l=u/f,c=a/f;0<=l&&l<=1&&0<=c&&c<=1?(o=new n("Intersection"),o.points.push(new t.Point(e.x+l*(r.x-e.x),e.y+l*(r.y-e.y)))):o=new n}else u===0||a===0?o=new n("Coincident"):o=new n("Parallel");return o},t.Intersection.intersectLinePolygon=function(e,t,r){var i=new n,s=r.length;for(var o=0;o0&&(i.status="Intersection"),i},t.Intersection.intersectPolygonPolygon=function(e,t){var r=new n,i=e.length;for(var s=0;s0&&(r.status="Intersection"),r},t.Intersection.intersectPolygonRectangle=function(e,r,i){var s=r.min(i),o=r.max(i),u=new t.Point(o.x,s.y),a=new t.Point(s.x,o.y),f=n.intersectLinePolygon(s,u,e),l=n.intersectLinePolygon(u,o,e),c=n.intersectLinePolygon(o,a,e),h=n.intersectLinePolygon(a,s,e),p=new n;return p.appendPoints(f.points),p.appendPoints(l.points),p.appendPoints(c.points),p.appendPoints(h.points),p.points.length>0&&(p.status="Intersection"),p}}(typeof exports!="undefined"?exports:this),function(e){"use strict";function n(e){e?this._tryParsingColor(e):this.setSource([0,0,0,1])}function r(e,t,n){return n<0&&(n+=1),n>1&&(n-=1),n<1/6?e+(t-e)*6*n:n<.5?t:n<2/3?e+(t-e)*(2/3-n)*6:e}var t=e.fabric||(e.fabric={});if(t.Color){t.warn("fabric.Color is already defined.");return}t.Color=n,t.Color.prototype={_tryParsingColor:function(e){var t;e in n.colorNameMap&&(e=n.colorNameMap[e]),t=n.sourceFromHex(e),t||(t=n.sourceFromRgb(e)),t||(t=n.sourceFromHsl(e)),t&&this.setSource(t)},_rgbToHsl:function(e,n,r){e/=255,n/=255,r/=255;var i,s,o,u=t.util.array.max([e,n,r]),a=t.util.array.min([e,n,r]);o=(u+a)/2;if(u===a)i=s=0;else{var f=u-a;s=o>.5?f/(2-u-a):f/(u+a);switch(u){case e:i=(n-r)/f+(n']:this.type==="radial"&&(r=["']);for(var i=0;i');return r.push(this.type==="linear"?"":""),r.join("")},toLive:function(e){var t;if(!this.type)return;this.type==="linear"?t=e.createLinearGradient(this.coords.x1,this.coords.y1,this.coords.x2,this.coords.y2):this.type==="radial"&&(t=e.createRadialGradient(this.coords.x1,this.coords.y1,this.coords.r1,this.coords.x2,this.coords.y2,this.coords.r2));for(var n=0,r=this.colorStops.length;n'+''+""},toLive:function(e){var t=typeof this.source=="function"?this.source():this.source;if(typeof t.src!="undefined"){if(!t.complete)return"";if(t.naturalWidth===0||t.naturalHeight===0)return""}return e.createPattern(t,this.repeat)}}),function(e){"use strict";var t=e.fabric||(e.fabric={});if(t.Shadow){t.warn("fabric.Shadow is already defined.");return}t.Shadow=t.util.createClass({color:"rgb(0,0,0)",blur:0,offsetX:0,offsetY:0,affectStroke:!1,includeDefaultValues:!0,initialize:function(e){typeof e=="string"&&(e=this._parseShadow(e));for(var n in e)this[n]=e[n];this.id=t.Object.__uid++},_parseShadow:function(e){var n=e.trim(),r=t.Shadow.reOffsetsAndBlur.exec(n)||[],i=n.replace(t.Shadow.reOffsetsAndBlur,"")||"rgb(0,0,0)";return{color:i.trim(),offsetX:parseInt(r[1],10)||0,offsetY:parseInt(r[2],10)||0,blur:parseInt(r[3],10)||0}},toString:function(){return[this.offsetX,this.offsetY,this.blur,this.color].join("px ")},toSVG:function(e){var t="SourceAlpha";return e&&(e.fill===this.color||e.stroke===this.color)&&(t="SourceGraphic"),''+''+''+""+""+''+""+""},toObject:function(){if(this.includeDefaultValues)return{color:this.color,blur:this.blur,offsetX:this.offsetX,offsetY:this.offsetY};var e={},n=t.Shadow.prototype;return this.color!==n.color&&(e.color=this.color),this.blur!==n.blur&&(e.blur=this.blur),this.offsetX!==n.offsetX&&(e.offsetX=this.offsetX),this.offsetY!==n.offsetY&&(e.offsetY=this.offsetY),e}}),t.Shadow.reOffsetsAndBlur=/(?:\s|^)(-?\d+(?:px)?(?:\s?|$))?(-?\d+(?:px)?(?:\s?|$))?(\d+(?:px)?)?(?:\s?|$)(?:$|\s)/}(typeof exports!="undefined"?exports:this),function(){"use strict";if(fabric.StaticCanvas){fabric.warn("fabric.StaticCanvas is already defined.");return}var e=fabric.util.object.extend,t=fabric.util.getElementOffset,n=fabric.util.removeFromArray,r=new Error("Could not initialize `canvas` element");fabric.StaticCanvas=fabric.util.createClass({initialize:function(e,t){t||(t={}),this._initStatic(e,t),fabric.StaticCanvas.activeInstance=this},backgroundColor:"",backgroundImage:null,overlayColor:"",overlayImage:null,includeDefaultValues:!0,stateful:!0,renderOnAddRemove:!0,clipTo:null,controlsAboveOverlay:!1,allowTouchScrolling:!1,onBeforeScaleRotate:function(){},_initStatic:function(e,t){this._objects=[],this._createLowerCanvas(e),this._initOptions(t),t.overlayImage&&this.setOverlayImage(t.overlayImage,this.renderAll.bind(this)),t.backgroundImage&&this.setBackgroundImage(t.backgroundImage,this.renderAll.bind(this)),t.backgroundColor&&this.setBackgroundColor(t.backgroundColor,this.renderAll.bind(this)),t.overlayColor&&this.setOverlayColor(t.overlayColor,this.renderAll.bind(this)),this.calcOffset()},calcOffset:function(){return this._offset=t(this.lowerCanvasEl),this},setOverlayImage:function(e,t,n){return this.__setBgOverlayImage("overlayImage",e,t,n)},setBackgroundImage:function(e,t,n){return this.__setBgOverlayImage("backgroundImage",e,t,n)},setOverlayColor:function(e,t){return this.__setBgOverlayColor("overlayColor",e,t)},setBackgroundColor:function(e,t){return this.__setBgOverlayColor("backgroundColor",e,t)},__setBgOverlayImage:function(e,t,n,r){return typeof t=="string"?fabric.util.loadImage(t,function(t){this[e]=new fabric.Image(t,r),n&&n()},this):(this[e]=t,n&&n()),this},__setBgOverlayColor:function(e,t,n){if(t.source){var r=this;fabric.util.loadImage(t.source,function(i){r[e]=new fabric.Pattern({source:i,repeat:t.repeat,offsetX:t.offsetX,offsetY:t.offsetY}),n&&n()})}else this[e]=t,n&&n();return this},_createCanvasElement:function(){var e=fabric.document.createElement("canvas");e.style||(e.style={});if(!e)throw r;return this._initCanvasElement(e),e},_initCanvasElement:function(e){fabric.util.createCanvasElement(e);if(typeof e.getContext=="undefined")throw r},_initOptions:function(e){for(var t in e)this[t]=e[t];this.width=parseInt(this.lowerCanvasEl.width,10)||0,this.height=parseInt(this.lowerCanvasEl.height,10)||0;if(!this.lowerCanvasEl.style)return;this.lowerCanvasEl.style.width=this.width+"px",this.lowerCanvasEl.style.height=this.height+"px"},_createLowerCanvas:function(e){this.lowerCanvasEl=fabric.util.getById(e)||this._createCanvasElement(),this._initCanvasElement(this.lowerCanvasEl),fabric.util.addClass(this.lowerCanvasEl,"lower-canvas"),this.interactive&&this._applyCanvasStyle(this.lowerCanvasEl),this.contextContainer=this.lowerCanvasEl.getContext("2d")},getWidth:function(){return this.width},getHeight:function(){return this.height},setWidth:function(e){return this._setDimension("width",e)},setHeight:function(e){return this._setDimension("height",e)},setDimensions:function(e){for(var t in e)this._setDimension(t,e[t]);return this},_setDimension:function(e,t){return this.lowerCanvasEl[e]=t,this.lowerCanvasEl.style[e]=t+"px",this.upperCanvasEl&&(this.upperCanvasEl[e]=t,this.upperCanvasEl.style[e]=t+"px"),this.cacheCanvasEl&&(this.cacheCanvasEl[e]=t),this.wrapperEl&&(this.wrapperEl.style[e]=t+"px"),this[e]=t,this.calcOffset(),this.renderAll(),this},getElement:function(){return this.lowerCanvasEl},getActiveObject:function(){return null},getActiveGroup:function(){return null},_draw:function(e,t){if(!t)return;if(this.controlsAboveOverlay){var n=t.hasBorders,r=t.hasControls;t.hasBorders=t.hasControls=!1,t.render(e),t.hasBorders=n,t.hasControls=r}else t.render(e)},_onObjectAdded:function(e){this.stateful&&e.setupState(),e.setCoords(),e.canvas=this,this.fire("object:added",{target:e}),e.fire("added")},_onObjectRemoved:function(e){this.getActiveObject()===e&&(this.fire("before:selection:cleared",{target:e}),this._discardActiveObject(),this.fire("selection:cleared")),this.fire("object:removed",{target:e}),e.fire("removed")},clearContext:function(e){return e.clearRect(0,0,this.width,this.height),this},getContext:function(){return this.contextContainer},clear:function(){return this._objects.length=0,this.discardActiveGroup&&this.discardActiveGroup(),this.discardActiveObject&&this.discardActiveObject(),this.clearContext(this.contextContainer),this.contextTop&&this.clearContext(this.contextTop),this.fire("canvas:cleared"),this.renderAll(),this},renderAll:function(e){var t=this[e===!0&&this.interactive?"contextTop":"contextContainer"],n=this.getActiveGroup();return this.contextTop&&this.selection&&!this._groupSelector&&this.clearContext(this.contextTop),e||this.clearContext(t),this.fire("before:render"),this.clipTo&&fabric.util.clipContext(this,t),this._renderBackground(t),this._renderObjects(t,n),this._renderActiveGroup(t,n),this.clipTo&&t.restore(),this._renderOverlay(t),this.controlsAboveOverlay&&this.interactive&&this.drawControls(t),this.fire("after:render"),this},_renderObjects:function(e,t){for(var n=0,r=this._objects.length;n"),n.join("")},_setSVGPreamble:function(e,t){t.suppressPreamble||e.push('','\n')},_setSVGHeader:function(e,t){e.push("',"Created with Fabric.js ",fabric.version,"","",fabric.createSVGFontFacesMarkup(this.getObjects()),fabric.createSVGRefElementsMarkup(this),"")},_setSVGObjects:function(e,t){var n=this.getActiveGroup();n&&this.discardActiveGroup();for(var r=0,i=this.getObjects(),s=i.length;r"):this[t]&&t==="overlayColor"&&e.push('")},sendToBack:function(e){return n(this._objects,e),this._objects.unshift(e),this.renderAll&&this.renderAll()},bringToFront:function(e){return n(this._objects,e),this._objects.push(e),this.renderAll&&this.renderAll()},sendBackwards:function(e,t){var r=this._objects.indexOf(e);if(r!==0){var i=this._findNewLowerIndex(e,r,t);n(this._objects,e),this._objects.splice(i,0,e),this.renderAll&&this.renderAll()}return this},_findNewLowerIndex:function(e,t,n){var r;if(n){r=t;for(var i=t-1;i>=0;--i){var s=e.intersectsWithObject(this._objects[i])||e.isContainedWithinObject(this._objects[i])||this._objects[i].isContainedWithinObject(e);if(s){r=i;break}}}else r=t-1;return r},bringForward:function(e,t){var r=this._objects.indexOf(e);if(r!==this._objects.length-1){var i=this._findNewUpperIndex(e,r,t);n(this._objects,e),this._objects.splice(i,0,e),this.renderAll&&this.renderAll()}return this},_findNewUpperIndex:function(e,t,n){var r;if(n){r=t;for(var i=t+1;i"}}),e(fabric.StaticCanvas.prototype,fabric.Observable),e(fabric.StaticCanvas.prototype,fabric.Collection),e(fabric.StaticCanvas.prototype,fabric.DataURLExporter),e(fabric.StaticCanvas,{EMPTY_JSON:'{"objects": [], "background": "white"}',supports:function(e){var t=fabric.util.createCanvasElement();if(!t||!t.getContext)return null;var n=t.getContext("2d");if(!n)return null;switch(e){case"getImageData":return typeof n.getImageData!="undefined";case"setLineDash":return typeof n.setLineDash!="undefined";case"toDataURL":return typeof t.toDataURL!="undefined";case"toDataURLWithQuality":try{return t.toDataURL("image/jpeg",0),!0}catch(r){}return!1;default:return null}}}),fabric.StaticCanvas.prototype.toJSON=fabric.StaticCanvas.prototype.toObject}(),fabric.BaseBrush=fabric.util.createClass({color:"rgb(0, 0, 0)",width:1,shadow:null,strokeLineCap:"round",strokeLineJoin:"round",setShadow:function(e){return this.shadow=new fabric.Shadow(e),this},_setBrushStyles:function(){var e=this.canvas.contextTop;e.strokeStyle=this.color,e.lineWidth=this.width,e.lineCap=this.strokeLineCap,e.lineJoin=this.strokeLineJoin},_setShadow:function(){if(!this.shadow)return;var e=this.canvas.contextTop;e.shadowColor=this.shadow.color,e.shadowBlur=this.shadow.blur,e.shadowOffsetX=this.shadow.offsetX,e.shadowOffsetY=this.shadow.offsetY},_resetShadow:function(){var e=this.canvas.contextTop;e.shadowColor="",e.shadowBlur=e.shadowOffsetX=e.shadowOffsetY=0}}),function(){var e=fabric.util.array.min,t=fabric.util.array.max;fabric.PencilBrush=fabric.util.createClass(fabric.BaseBrush,{initialize:function(e){this.canvas=e,this._points=[]},onMouseDown:function(e){this._prepareForDrawing(e),this._captureDrawingPath(e),this._render()},onMouseMove:function(e){this._captureDrawingPath(e),this.canvas.clearContext(this.canvas.contextTop),this._render()},onMouseUp:function(){this._finalizeAndAddPath()},_prepareForDrawing:function(e){var t=new fabric.Point(e.x,e.y);this._reset(),this._addPoint(t),this.canvas.contextTop.moveTo(t.x,t.y)},_addPoint:function(e){this._points.push(e)},_reset:function(){this._points.length=0,this._setBrushStyles(),this._setShadow()},_captureDrawingPath:function(e){var t=new fabric.Point(e.x,e.y);this._addPoint(t)},_render:function(){var e=this.canvas.contextTop;e.beginPath();var t=this._points[0],n=this._points[1];this._points.length===2&&t.x===n.x&&t.y===n.y&&(t.x-=.5,n.x+=.5),e.moveTo(t.x,t.y);for(var r=1,i=this._points.length;rn.padding?e.x<0?e.x+=n.padding:e.x-=n.padding:e.x=0,i(e.y)>n.padding?e.y<0?e.y+=n.padding:e.y-=n.padding:e.y=0},_rotateObject:function(e,t){var i=this._currentTransform,s=this._offset;if(i.target.get("lockRotation"))return;var o=r(i.ey-i.top-s.top,i.ex-i.left-s.left),u=r(t-i.top-s.top,e-i.left-s.left),a=n(u-o+i.theta);a<0&&(a=360+a),i.target.angle=a},_setCursor:function(e){this.upperCanvasEl.style.cursor=e},_resetObjectTransform:function(e){e.scaleX=1,e.scaleY=1,e.setAngle(0)},_drawSelection:function(){var e=this.contextTop,t=this._groupSelector,n=t.left,r=t.top,o=i(n),u=i(r);e.fillStyle=this.selectionColor,e.fillRect(t.ex-(n>0?0:-n),t.ey-(r>0?0:-r),o,u),e.lineWidth=this.selectionLineWidth,e.strokeStyle=this.selectionBorderColor;if(this.selectionDashArray.length>1){var a=t.ex+s-(n>0?0:o),f=t.ey+s-(r>0?0:u);e.beginPath(),fabric.util.drawDashedLine(e,a,f,a+o,f,this.selectionDashArray),fabric.util.drawDashedLine(e,a,f+u-1,a+o,f+u-1,this.selectionDashArray),fabric.util.drawDashedLine(e,a,f,a,f+u,this.selectionDashArray),fabric.util.drawDashedLine(e,a+o-1,f,a+o-1,f+u,this.selectionDashArray),e.closePath(),e.stroke()}else e.strokeRect(t.ex+s-(n>0?0:o),t.ey+s-(r>0?0:u),o,u)},_isLastRenderedObject:function(e){return this.controlsAboveOverlay&&this.lastRenderedObjectWithControlsAboveOverlay&&this.lastRenderedObjectWithControlsAboveOverlay.visible&&this.containsPoint(e,this.lastRenderedObjectWithControlsAboveOverlay)&&this.lastRenderedObjectWithControlsAboveOverlay._findTargetCorner(e,this._offset)},findTarget:function(e,t){if(this.skipTargetFind)return;if(this._isLastRenderedObject(e))return this.lastRenderedObjectWithControlsAboveOverlay;var n=this.getActiveGroup();return n&&!t&&this.containsPoint(e,n)?n:this._searchPossibleTargets(e)},_searchPossibleTargets:function(e){var t=[],n,r=this.getPointer(e);for(var i=this._objects.length;i--;)if(this._objects[i]&&this._objects[i].visible&&this._objects[i].evented&&this.containsPoint(e,this._objects[i])){if(!this.perPixelTargetFind&&!this._objects[i].perPixelTargetFind){n=this._objects[i],this.relatedTarget=n;break}t[t.length]=this._objects[i]}for(var s=0,o=t.length;s1&&(t=new fabric.Group(t.reverse(),{originX:"center",originY:"center"}),this.setActiveGroup(t,e),t.saveCoords(),this.fire("selection:created",{target:t}),this.renderAll())},_collectObjects:function(){var n=[],r,i=this._groupSelector.ex,s=this._groupSelector.ey,o=i+this._groupSelector.left,u=s+this._groupSelector.top,a=new fabric.Point(e(i,o),e(s,u)),f=new fabric.Point(t(i,o),t(s,u)),l=i===o&&s===u;for(var c=this._objects.length;c--;){r=this._objects[c];if(!r||!r.selectable||!r.visible)continue;if(r.intersectsWithRect(a,f)||r.isContainedWithinRect(a,f)||r.containsPoint(a)||r.containsPoint(f)){r.set("active",!0),n.push(r);if(l)break}}return n},_maybeGroupObjects:function(e){this.selection&&this._groupSelector&&this._groupSelectedObjects(e);var t=this.getActiveGroup();t&&(t.setObjectsCoords().setCoords(),t.isMoving=!1,this._setCursor(this.defaultCursor)),this._groupSelector=null,this._currentTransform=null}})}(),fabric.util.object.extend(fabric.StaticCanvas.prototype,{toDataURL:function(e){e||(e={});var t=e.format||"png",n=e.quality||1,r=e.multiplier||1,i={left:e.left,top:e.top,width:e.width,height:e.height};return r!==1?this.__toDataURLWithMultiplier(t,n,i,r):this.__toDataURL(t,n,i)},__toDataURL:function(e,t,n){this.renderAll(!0);var r=this.upperCanvasEl||this.lowerCanvasEl,i=this.__getCroppedCanvas(r,n);e==="jpg"&&(e="jpeg");var s=fabric.StaticCanvas.supports("toDataURLWithQuality")?(i||r).toDataURL("image/"+e,t):(i||r).toDataURL("image/"+e);return this.contextTop&&this.clearContext(this.contextTop),this.renderAll(),i&&(i=null),s},__getCroppedCanvas:function(e,t){var n,r,i="left"in t||"top"in t||"width"in t||"height"in t;return i&&(n=fabric.util.createCanvasElement(),r=n.getContext("2d"),n.width=t.width||this.width,n.height=t.height||this.height,r.drawImage(e,-t.left||0,-t.top||0)),n},__toDataURLWithMultiplier:function(e,t,n,r){var i=this.getWidth(),s=this.getHeight(),o=i*r,u=s*r,a=this.getActiveObject(),f=this.getActiveGroup(),l=this.contextTop||this.contextContainer;this.setWidth(o).setHeight(u),l.scale(r,r),n.left&&(n.left*=r),n.top&&(n.top*=r),n.width&&(n.width*=r),n.height&&(n.height*=r),f?this._tempRemoveBordersControlsFromGroup(f):a&&this.deactivateAll&&this.deactivateAll(),this.renderAll(!0);var c=this.__toDataURL(e,t,n);return this.width=i,this.height=s,l.scale(1/r,1/r),this.setWidth(i).setHeight(s),f?this._restoreBordersControlsOnGroup(f):a&&this.setActiveObject&&this.setActiveObject(a),this.contextTop&&this.clearContext(this.contextTop),this.renderAll(),c},toDataURLWithMultiplier:function(e,t,n){return this.toDataURL({format:e,multiplier:t,quality:n})},_tempRemoveBordersControlsFromGroup:function(e){e.origHasControls=e.hasControls,e.origBorderColor=e.borderColor,e.hasControls=!0,e.borderColor="rgba(0,0,0,0)",e.forEachObject(function(e){e.origBorderColor=e.borderColor,e.borderColor="rgba(0,0,0,0)"})},_restoreBordersControlsOnGroup:function(e){e.hideControls=e.origHideControls,e.borderColor=e.origBorderColor,e.forEachObject(function(e){e.borderColor=e.origBorderColor,delete e.origBorderColor})}}),fabric.util.object.extend(fabric.StaticCanvas.prototype,{loadFromDatalessJSON:function(e,t,n){return this.loadFromJSON(e,t,n)},loadFromJSON:function(e,t,n){if(!e)return;var r=typeof e=="string"?JSON.parse(e):e;this.clear();var i=this;return this._enlivenObjects(r.objects,function(){i._setBgOverlay(r,t)},n),this},_setBgOverlay:function(e,t){var n=this,r={backgroundColor:!1,overlayColor:!1,backgroundImage:!1,overlayImage:!1};if(!e.backgroundImage&&!e.overlayImage&&!e.background&&!e.overlay){t&&t();return}var i=function(){r.backgroundImage&&r.overlayImage&&r.backgroundColor&&r.overlayColor&&(n.renderAll(),t&&t())};this.__setBgOverlay("backgroundImage",e.backgroundImage,r,i),this.__setBgOverlay("overlayImage",e.overlayImage,r,i),this.__setBgOverlay("backgroundColor",e.background,r,i),this.__setBgOverlay("overlayColor",e.overlay,r,i),i()},__setBgOverlay:function(e,t,n,r){var i=this;if(!t){n[e]=!0;return}e==="backgroundImage"||e==="overlayImage"?fabric.Image.fromObject(t,function(t){i[e]=t,n[e]=!0,r&&r()}):this["set"+fabric.util.string.capitalize(e,!0)](t,function(){n[e]=!0,r&&r()})},_enlivenObjects:function(e,t,n){var r=this;e.length===0&&t&&t();var i=this.renderOnAddRemove;this.renderOnAddRemove=!1,fabric.util.enlivenObjects(e,function(e){e.forEach(function(e,t){r.insertAt(e,t,!0)}),r.renderOnAddRemove=i,t&&t()},null,n)},_toDataURL:function(e,t){this.clone(function(n){t(n.toDataURL(e))})},_toDataURLWithMultiplier:function(e,t,n){this.clone(function(r){n(r.toDataURLWithMultiplier(e,t))})},clone:function(e,t){var n=JSON.stringify(this.toJSON(t));this.cloneWithoutData(function(t){t.loadFromJSON(n,function(){e&&e(t)})})},cloneWithoutData:function(e){var t=fabric.document.createElement("canvas");t.width=this.getWidth(),t.height=this.getHeight();var n=new fabric.Canvas(t);n.clipTo=this.clipTo,this.backgroundImage?(n.setBackgroundImage(this.backgroundImage.src,function(){n.renderAll(),e&&e(n)}),n.backgroundImageOpacity=this.backgroundImageOpacity,n.backgroundImageStretch=this.backgroundImageStretch):e&&e(n)}}),function(e){"use strict";var t=e.fabric||(e.fabric={}),n=t.util.object.extend,r=t.util.toFixed,i=t.util.string.capitalize,s=t.util.degreesToRadians,o=t.StaticCanvas.supports("setLineDash");if(t.Object)return;t.Object=t.util.createClass({type:"object",originX:"left",originY:"top",top:0,left:0,width:0,height:0,scaleX:1,scaleY:1,flipX:!1,flipY:!1,opacity:1,angle:0,cornerSize:12,transparentCorners:!0,hoverCursor:null,padding:0,borderColor:"rgba(102,153,255,0.75)",cornerColor:"rgba(102,153,255,0.5)",centeredScaling:!1,centeredRotation:!0,fill:"rgb(0,0,0)",fillRule:"source-over",backgroundColor:"",stroke:null,strokeWidth:1,strokeDashArray:null,strokeLineCap:"butt",strokeLineJoin:"miter",strokeMiterLimit:10,shadow:null,borderOpacityWhenMoving:.4,borderScaleFactor:1,transformMatrix:null,minScaleLimit:.01,selectable:!0,evented:!0,visible:!0,hasControls:!0,hasBorders:!0,hasRotatingPoint:!0,rotatingPointOffset:40,perPixelTargetFind:!1,includeDefaultValues:!0,clipTo:null,lockMovementX:!1,lockMovementY:!1,lockRotation:!1,lockScalingX:!1,lockScalingY:!1,lockUniScaling:!1,stateProperties:"top left width height scaleX scaleY flipX flipY originX originY transformMatrix stroke strokeWidth strokeDashArray strokeLineCap strokeLineJoin strokeMiterLimit angle opacity fill fillRule shadow clipTo visible backgroundColor".split(" "),initialize:function(e){e&&this.setOptions(e)},_initGradient:function(e){e.fill&&e.fill.colorStops&&!(e.fill instanceof t.Gradient)&&this.set("fill",new t.Gradient(e.fill))},_initPattern:function(e){e.fill&&e.fill.source&&!(e.fill instanceof t.Pattern)&&this.set("fill",new t.Pattern(e.fill)),e.stroke&&e.stroke.source&&!(e.stroke instanceof t.Pattern)&&this.set("stroke",new t.Pattern(e.stroke))},_initClipping:function(e){if(!e.clipTo||typeof e.clipTo!="string")return;var n=t.util.getFunctionBody(e.clipTo);typeof n!="undefined"&&(this.clipTo=new Function("ctx",n))},setOptions:function(e){for(var t in e)this.set(t,e[t]);this._initGradient(e),this._initPattern(e),this._initClipping(e)},transform:function(e,t){e.globalAlpha=this.opacity;var n=t?this._getLeftTopCoords():this.getCenterPoint();e.translate(n.x,n.y),e.rotate(s(this.angle)),e.scale(this.scaleX*(this.flipX?-1:1),this.scaleY*(this.flipY?-1:1))},toObject:function(e){var n=t.Object.NUM_FRACTION_DIGITS,i={type:this.type,originX:this.originX,originY:this.originY,left:r(this.left,n),top:r(this.top,n),width:r(this.width,n),height:r(this.height,n),fill:this.fill&&this.fill.toObject?this.fill.toObject():this.fill,stroke:this.stroke&&this.stroke.toObject?this.stroke.toObject():this.stroke,strokeWidth:r(this.strokeWidth,n),strokeDashArray:this.strokeDashArray,strokeLineCap:this.strokeLineCap,strokeLineJoin:this.strokeLineJoin,strokeMiterLimit:r(this.strokeMiterLimit,n),scaleX:r(this.scaleX,n),scaleY:r(this.scaleY,n),angle:r(this.getAngle(),n),flipX:this.flipX,flipY:this.flipY,opacity:r(this.opacity,n),shadow:this.shadow&&this.shadow.toObject?this.shadow.toObject():this.shadow,visible:this.visible,clipTo:this.clipTo&&String(this.clipTo),backgroundColor:this.backgroundColor};return this.includeDefaultValues||(i=this._removeDefaultValues(i)),t.util.populateWithProperties(this,i,e),i},toDatalessObject:function(e){return this.toObject(e)},_removeDefaultValues:function(e){var n=t.util.getKlass(e.type).prototype,r=n.stateProperties;return r.forEach(function(t){e[t]===n[t]&&delete e[t]}),e},toString:function(){return"#"},get:function(e){return this[e]},set:function(e,t){if(typeof e=="object")for(var n in e)this._set(n,e[n]);else typeof t=="function"&&e!=="clipTo"?this._set(e,t(this.get(e))):this._set(e,t);return this},_set:function(e,n){var i=e==="scaleX"||e==="scaleY";return i&&(n=this._constrainScale(n)),e==="scaleX"&&n<0?(this.flipX=!this.flipX,n*=-1):e==="scaleY"&&n<0?(this.flipY=!this.flipY,n*=-1):e==="width"||e==="height"?this.minScaleLimit=r(Math.min(.1,1/Math.max(this.width,this.height)),2):e==="shadow"&&n&&!(n instanceof t.Shadow -)&&(n=new t.Shadow(n)),this[e]=n,this},toggle:function(e){var t=this.get(e);return typeof t=="boolean"&&this.set(e,!t),this},setSourcePath:function(e){return this.sourcePath=e,this},render:function(e,n){if(this.width===0||this.height===0||!this.visible)return;e.save(),this._transform(e,n),this._setStrokeStyles(e),this._setFillStyles(e);var r=this.transformMatrix;r&&this.group&&(e.translate(-this.group.width/2,-this.group.height/2),e.transform(r[0],r[1],r[2],r[3],r[4],r[5])),this._setShadow(e),this.clipTo&&t.util.clipContext(this,e),this._render(e,n),this.clipTo&&e.restore(),this._removeShadow(e),this.active&&!n&&(this.drawBorders(e),this.drawControls(e)),e.restore()},_transform:function(e,t){var n=this.transformMatrix;n&&!this.group&&e.setTransform(n[0],n[1],n[2],n[3],n[4],n[5]),t||this.transform(e)},_setStrokeStyles:function(e){this.stroke&&(e.lineWidth=this.strokeWidth,e.lineCap=this.strokeLineCap,e.lineJoin=this.strokeLineJoin,e.miterLimit=this.strokeMiterLimit,e.strokeStyle=this.stroke.toLive?this.stroke.toLive(e):this.stroke)},_setFillStyles:function(e){this.fill&&(e.fillStyle=this.fill.toLive?this.fill.toLive(e):this.fill)},_setShadow:function(e){if(!this.shadow)return;e.shadowColor=this.shadow.color,e.shadowBlur=this.shadow.blur,e.shadowOffsetX=this.shadow.offsetX,e.shadowOffsetY=this.shadow.offsetY},_removeShadow:function(e){e.shadowColor="",e.shadowBlur=e.shadowOffsetX=e.shadowOffsetY=0},_renderFill:function(e){if(!this.fill)return;this.fill.toLive&&(e.save(),e.translate(-this.width/2+this.fill.offsetX||0,-this.height/2+this.fill.offsetY||0)),e.fill(),this.fill.toLive&&e.restore(),this.shadow&&!this.shadow.affectStroke&&this._removeShadow(e)},_renderStroke:function(e){if(!this.stroke)return;e.save(),this.strokeDashArray?(1&this.strokeDashArray.length&&this.strokeDashArray.push.apply(this.strokeDashArray,this.strokeDashArray),o?(e.setLineDash(this.strokeDashArray),this._stroke&&this._stroke(e)):this._renderDashedStroke&&this._renderDashedStroke(e),e.stroke()):this._stroke?this._stroke(e):e.stroke(),this._removeShadow(e),e.restore()},clone:function(e,n){return this.constructor.fromObject?this.constructor.fromObject(this.toObject(n),e):new t.Object(this.toObject(n))},cloneAsImage:function(e){var n=this.toDataURL();return t.util.loadImage(n,function(n){e&&e(new t.Image(n))}),this},toDataURL:function(e){e||(e={});var n=t.util.createCanvasElement(),r=this.getBoundingRect();n.width=r.width,n.height=r.height,t.util.wrapElement(n,"div");var i=new t.Canvas(n);e.format==="jpg"&&(e.format="jpeg"),e.format==="jpeg"&&(i.backgroundColor="#fff");var s={active:this.get("active"),left:this.getLeft(),top:this.getTop()};this.set("active",!1),this.setPositionByOrigin(new t.Point(n.width/2,n.height/2),"center","center");var o=this.canvas;i.add(this);var u=i.toDataURL(e);return this.set(s).setCoords(),this.canvas=o,i.dispose(),i=null,u},isType:function(e){return this.type===e},complexity:function(){return 0},toJSON:function(e){return this.toObject(e)},setGradient:function(e,n){n||(n={});var r={colorStops:[]};r.type=n.type||(n.r1||n.r2?"radial":"linear"),r.coords={x1:n.x1,y1:n.y1,x2:n.x2,y2:n.y2};if(n.r1||n.r2)r.coords.r1=n.r1,r.coords.r2=n.r2;for(var i in n.colorStops){var s=new t.Color(n.colorStops[i]);r.colorStops.push({offset:i,color:s.toRgb(),opacity:s.getAlpha()})}return this.set(e,t.Gradient.forObject(this,r))},setPatternFill:function(e){return this.set("fill",new t.Pattern(e))},setShadow:function(e){return this.set("shadow",new t.Shadow(e))},setColor:function(e){return this.set("fill",e),this},centerH:function(){return this.canvas.centerObjectH(this),this},centerV:function(){return this.canvas.centerObjectV(this),this},center:function(){return this.canvas.centerObject(this),this},remove:function(){return this.canvas.remove(this)},getLocalPointer:function(e,t){t=t||this.canvas.getPointer(e);var n=this.translateToOriginPoint(this.getCenterPoint(),"left","top");return{x:t.x-n.x,y:t.y-n.y}}}),t.util.createAccessors(t.Object),t.Object.prototype.rotate=t.Object.prototype.setAngle,n(t.Object.prototype,t.Observable),t.Object.NUM_FRACTION_DIGITS=2,t.Object.__uid=0}(typeof exports!="undefined"?exports:this),function(){var e=fabric.util.degreesToRadians;fabric.util.object.extend(fabric.Object.prototype,{translateToCenterPoint:function(t,n,r){var i=t.x,s=t.y,o=this.stroke?this.strokeWidth:0;return n==="left"?i=t.x+(this.getWidth()+o*this.scaleX)/2:n==="right"&&(i=t.x-(this.getWidth()+o*this.scaleX)/2),r==="top"?s=t.y+(this.getHeight()+o*this.scaleY)/2:r==="bottom"&&(s=t.y-(this.getHeight()+o*this.scaleY)/2),fabric.util.rotatePoint(new fabric.Point(i,s),t,e(this.angle))},translateToOriginPoint:function(t,n,r){var i=t.x,s=t.y,o=this.stroke?this.strokeWidth:0;return n==="left"?i=t.x-(this.getWidth()+o*this.scaleX)/2:n==="right"&&(i=t.x+(this.getWidth()+o*this.scaleX)/2),r==="top"?s=t.y-(this.getHeight()+o*this.scaleY)/2:r==="bottom"&&(s=t.y+(this.getHeight()+o*this.scaleY)/2),fabric.util.rotatePoint(new fabric.Point(i,s),t,e(this.angle))},getCenterPoint:function(){var e=new fabric.Point(this.left,this.top);return this.translateToCenterPoint(e,this.originX,this.originY)},getPointByOrigin:function(e,t){var n=this.getCenterPoint();return this.translateToOriginPoint(n,e,t)},toLocalPoint:function(t,n,r){var i=this.getCenterPoint(),s=this.stroke?this.strokeWidth:0,o,u;return n&&r?(n==="left"?o=i.x-(this.getWidth()+s*this.scaleX)/2:n==="right"?o=i.x+(this.getWidth()+s*this.scaleX)/2:o=i.x,r==="top"?u=i.y-(this.getHeight()+s*this.scaleY)/2:r==="bottom"?u=i.y+(this.getHeight()+s*this.scaleY)/2:u=i.y):(o=this.left,u=this.top),fabric.util.rotatePoint(new fabric.Point(t.x,t.y),i,-e(this.angle)).subtractEquals(new fabric.Point(o,u))},setPositionByOrigin:function(e,t,n){var r=this.translateToCenterPoint(e,t,n),i=this.translateToOriginPoint(r,this.originX,this.originY);this.set("left",i.x),this.set("top",i.y)},adjustPosition:function(t){var n=e(this.angle),r=this.getWidth()/2,i=Math.cos(n)*r,s=Math.sin(n)*r,o=this.getWidth(),u=Math.cos(n)*o,a=Math.sin(n)*o;this.originX==="center"&&t==="left"||this.originX==="right"&&t==="center"?(this.left-=i,this.top-=s):this.originX==="left"&&t==="center"||this.originX==="center"&&t==="right"?(this.left+=i,this.top+=s):this.originX==="left"&&t==="right"?(this.left+=u,this.top+=a):this.originX==="right"&&t==="left"&&(this.left-=u,this.top-=a),this.setCoords(),this.originX=t},_getLeftTopCoords:function(){return this.translateToOriginPoint(this.getCenterPoint(),"left","center")}})}(),function(){var e=fabric.util.degreesToRadians;fabric.util.object.extend(fabric.Object.prototype,{oCoords:null,intersectsWithRect:function(e,t){var n=this.oCoords,r=new fabric.Point(n.tl.x,n.tl.y),i=new fabric.Point(n.tr.x,n.tr.y),s=new fabric.Point(n.bl.x,n.bl.y),o=new fabric.Point(n.br.x,n.br.y),u=fabric.Intersection.intersectPolygonRectangle([r,i,o,s],e,t);return u.status==="Intersection"},intersectsWithObject:function(e){function t(e){return{tl:new fabric.Point(e.tl.x,e.tl.y),tr:new fabric.Point(e.tr.x,e.tr.y),bl:new fabric.Point(e.bl.x,e.bl.y),br:new fabric.Point(e.br.x,e.br.y)}}var n=t(this.oCoords),r=t(e.oCoords),i=fabric.Intersection.intersectPolygonPolygon([n.tl,n.tr,n.br,n.bl],[r.tl,r.tr,r.br,r.bl]);return i.status==="Intersection"},isContainedWithinObject:function(e){var t=e.getBoundingRect(),n=new fabric.Point(t.left,t.top),r=new fabric.Point(t.left+t.width,t.top+t.height);return this.isContainedWithinRect(n,r)},isContainedWithinRect:function(e,t){var n=this.getBoundingRect();return n.left>e.x&&n.left+n.widthe.y&&n.top+n.height=e.y&&f.d.y>=e.y)continue;f.o.x===f.d.x&&f.o.x>=e.x?(o=f.o.x,u=e.y):(n=0,r=(f.d.y-f.o.y)/(f.d.x-f.o.x),i=e.y-n*e.x,s=f.o.y-r*f.o.x,o=-(i-s)/(n-r),u=i+n*o),o>=e.x&&(a+=1);if(a===2)break}return a},getBoundingRectWidth:function(){return this.getBoundingRect().width},getBoundingRectHeight:function(){return this.getBoundingRect().height},getBoundingRect:function(){this.oCoords||this.setCoords();var e=[this.oCoords.tl.x,this.oCoords.tr.x,this.oCoords.br.x,this.oCoords.bl.x],t=fabric.util.array.min(e),n=fabric.util.array.max(e),r=Math.abs(t-n),i=[this.oCoords.tl.y,this.oCoords.tr.y,this.oCoords.br.y,this.oCoords.bl.y],s=fabric.util.array.min(i),o=fabric.util.array.max(i),u=Math.abs(s-o);return{left:t,top:s,width:r,height:u}},getWidth:function(){return this.width*this.scaleX},getHeight:function(){return this.height*this.scaleY},_constrainScale:function(e){return Math.abs(e)1?this.strokeWidth:0,n=this.padding,r=e(this.angle);this.currentWidth=(this.width+t)*this.scaleX+n*2,this.currentHeight=(this.height+t)*this.scaleY+n*2,this.currentWidth<0&&(this.currentWidth=Math.abs(this.currentWidth));var i=Math.sqrt(Math.pow(this.currentWidth/2,2)+Math.pow(this.currentHeight/2,2)),s=Math.atan(isFinite(this.currentHeight/this.currentWidth)?this.currentHeight/this.currentWidth:0),o=Math.cos(s+r)*i,u=Math.sin(s+r)*i,a=Math.sin(r),f=Math.cos(r),l=this.getCenterPoint(),c={x:l.x-o,y:l.y-u},h={x:c.x+this.currentWidth*f,y:c.y+this.currentWidth*a},p={x:h.x-this.currentHeight*a,y:h.y+this.currentHeight*f},d={x:c.x-this.currentHeight*a,y:c.y+this.currentHeight*f},v={x:c.x-this.currentHeight/2*a,y:c.y+this.currentHeight/2*f},m={x:c.x+this.currentWidth/2*f,y:c.y+this.currentWidth/2*a},g={x:h.x-this.currentHeight/2*a,y:h.y+this.currentHeight/2*f},y={x:d.x+this.currentWidth/2*f,y:d.y+this.currentWidth/2*a},b={x:m.x,y:m.y};return this.oCoords={tl:c,tr:h,br:p,bl:d,ml:v,mt:m,mr:g,mb:y,mtr:b},this._setCornerCoords&&this._setCornerCoords(),this}})}(),fabric.util.object.extend(fabric.Object.prototype,{sendToBack:function(){return this.group?fabric.StaticCanvas.prototype.sendToBack.call(this.group,this):this.canvas.sendToBack(this),this},bringToFront:function(){return this.group?fabric.StaticCanvas.prototype.bringToFront.call(this.group,this):this.canvas.bringToFront(this),this},sendBackwards:function(e){return this.group?fabric.StaticCanvas.prototype.sendBackwards.call(this.group,this,e):this.canvas.sendBackwards(this,e),this},bringForward:function(e){return this.group?fabric.StaticCanvas.prototype.bringForward.call(this.group,this,e):this.canvas.bringForward(this,e),this},moveTo:function(e){return this.group?fabric.StaticCanvas.prototype.moveTo.call(this.group,this,e):this.canvas.moveTo(this,e),this}}),fabric.util.object.extend(fabric.Object.prototype,{getSvgStyles:function(){var e=this.fill?this.fill.toLive?"url(#SVGID_"+this.fill.id+")":this.fill:"none",t=this.stroke?this.stroke.toLive?"url(#SVGID_"+this.stroke.id+")":this.stroke:"none",n=this.strokeWidth?this.strokeWidth:"0",r=this.strokeDashArray?this.strokeDashArray.join(" "):"",i=this.strokeLineCap?this.strokeLineCap:"butt",s=this.strokeLineJoin?this.strokeLineJoin:"miter",o=this.strokeMiterLimit?this.strokeMiterLimit:"4",u=typeof this.opacity!="undefined"?this.opacity:"1",a=this.visible?"":" visibility: hidden;",f=this.shadow&&this.type!=="text"?"filter: url(#SVGID_"+this.shadow.id+");":"";return["stroke: ",t,"; ","stroke-width: ",n,"; ","stroke-dasharray: ",r,"; ","stroke-linecap: ",i,"; ","stroke-linejoin: ",s,"; ","stroke-miterlimit: ",o,"; ","fill: ",e,"; ","opacity: ",u,";",f,a].join("")},getSvgTransform:function(){var e=fabric.util.toFixed,t=this.getAngle(),n=this.getCenterPoint(),r=fabric.Object.NUM_FRACTION_DIGITS,i="translate("+e(n.x,r)+" "+e(n.y,r)+")",s=t!==0?" rotate("+e(t,r)+")":"",o=this.scaleX===1&&this.scaleY===1?"":" scale("+e(this.scaleX,r)+" "+e(this.scaleY,r)+")",u=this.flipX?"matrix(-1 0 0 1 0 0) ":"",a=this.flipY?"matrix(1 0 0 -1 0 0)":"";return[i,s,o,u,a].join("")},_createBaseSVGMarkup:function(){var e=[];return this.fill&&this.fill.toLive&&e.push(this.fill.toSVG(this,!1)),this.stroke&&this.stroke.toLive&&e.push(this.stroke.toSVG(this,!1)),this.shadow&&e.push(this.shadow.toSVG(this)),e}}),fabric.util.object.extend(fabric.Object.prototype,{hasStateChanged:function(){return this.stateProperties.some(function(e){return this.get(e)!==this.originalState[e]},this)},saveState:function(e){return this.stateProperties.forEach(function(e){this.originalState[e]=this.get(e)},this),e&&e.stateProperties&&e.stateProperties.forEach(function(e){this.originalState[e]=this.get(e)},this),this},setupState:function(){return this.originalState={},this.saveState(),this}}),function(){var e=fabric.util.getPointer,t=fabric.util.degreesToRadians,n=typeof G_vmlCanvasManager!="undefined";fabric.util.object.extend(fabric.Object.prototype,{_controlsVisibility:null,_findTargetCorner:function(t,n){if(!this.hasControls||!this.active)return!1;var r=e(t,this.canvas.upperCanvasEl),i=r.x-n.left,s=r.y-n.top,o,u;for(var a in this.oCoords){if(!this.isControlVisible(a))continue;if(a==="mtr"&&!this.hasRotatingPoint)continue;if(!(!this.get("lockUniScaling")||a!=="mt"&&a!=="mr"&&a!=="mb"&&a!=="ml"))continue;u=this._getImageLines(this.oCoords[a].corner),o=this._findCrossPoints({x:i,y:s},u);if(o!==0&&o%2===1)return this.__corner=a,a}return!1},_setCornerCoords:function(){var e=this.oCoords,n=t(this.angle),r=t(45-this.angle),i=Math.sqrt(2*Math.pow(this.cornerSize,2))/2,s=i*Math.cos(r),o=i*Math.sin(r),u=Math.sin(n),a=Math.cos(n);e.tl.corner={tl:{x:e.tl.x-o,y:e.tl.y-s},tr:{x:e.tl.x+s,y:e.tl.y-o},bl:{x:e.tl.x-s,y:e.tl.y+o},br:{x:e.tl.x+o,y:e.tl.y+s}},e.tr.corner={tl:{x:e.tr.x-o,y:e.tr.y-s},tr:{x:e.tr.x+s,y:e.tr.y-o},br:{x:e.tr.x+o,y:e.tr.y+s},bl:{x:e.tr.x-s,y:e.tr.y+o}},e.bl.corner={tl:{x:e.bl.x-o,y:e.bl.y-s},bl:{x:e.bl.x-s,y:e.bl.y+o},br:{x:e.bl.x+o,y:e.bl.y+s},tr:{x:e.bl.x+s,y:e.bl.y-o}},e.br.corner={tr:{x:e.br.x+s,y:e.br.y-o},bl:{x:e.br.x-s,y:e.br.y+o},br:{x:e.br.x+o,y:e.br.y+s},tl:{x:e.br.x-o,y:e.br.y-s}},e.ml.corner={tl:{x:e.ml.x-o,y:e.ml.y-s},tr:{x:e.ml.x+s,y:e.ml.y-o},bl:{x:e.ml.x-s,y:e.ml.y+o},br:{x:e.ml.x+o,y:e.ml.y+s}},e.mt.corner={tl:{x:e.mt.x-o,y:e.mt.y-s},tr:{x:e.mt.x+s,y:e.mt.y-o},bl:{x:e.mt.x-s,y:e.mt.y+o},br:{x:e.mt.x+o,y:e.mt.y+s}},e.mr.corner={tl:{x:e.mr.x-o,y:e.mr.y-s},tr:{x:e.mr.x+s,y:e.mr.y-o},bl:{x:e.mr.x-s,y:e.mr.y+o},br:{x:e.mr.x+o,y:e.mr.y+s}},e.mb.corner={tl:{x:e.mb.x-o,y:e.mb.y-s},tr:{x:e.mb.x+s,y:e.mb.y-o},bl:{x:e.mb.x-s,y:e.mb.y+o},br:{x:e.mb.x+o,y:e.mb.y+s}},e.mtr.corner={tl:{x:e.mtr.x-o+u*this.rotatingPointOffset,y:e.mtr.y-s-a*this.rotatingPointOffset},tr:{x:e.mtr.x+s+u*this.rotatingPointOffset,y:e.mtr.y-o-a*this.rotatingPointOffset},bl:{x:e.mtr.x-s+u*this.rotatingPointOffset,y:e.mtr.y+o-a*this.rotatingPointOffset},br:{x:e.mtr.x+o+u*this.rotatingPointOffset,y:e.mtr.y+s-a*this.rotatingPointOffset}}},drawBorders:function(e){if(!this.hasBorders)return this;var t=this.padding,n=t*2,r=~~(this.strokeWidth/2)*2;e.save(),e.globalAlpha=this.isMoving?this.borderOpacityWhenMoving:1,e.strokeStyle=this.borderColor;var i=1/this._constrainScale(this.scaleX),s=1/this._constrainScale(this.scaleY);e.lineWidth=1/this.borderScaleFactor,e.scale(i,s);var o=this.getWidth(),u=this.getHeight();e.strokeRect(~~(-(o/2)-t-r/2*this.scaleX)-.5,~~(-(u/2)-t-r/2*this.scaleY)-.5,~~(o+n+r*this.scaleX)+1,~~(u+n+r*this.scaleY)+1);if(this.hasRotatingPoint&&this.isControlVisible("mtr")&&!this.get("lockRotation")&&this.hasControls){var a=(this.flipY?u+r*this.scaleY+t*2:-u-r*this.scaleY-t*2)/2;e.beginPath(),e.moveTo(0,a),e.lineTo(0,a+(this.flipY?this.rotatingPointOffset:-this.rotatingPointOffset)),e.closePath(),e.stroke()}return e.restore(),this},drawControls:function(e){if(!this.hasControls)return this;var t=this.cornerSize,n=t/2,r=~~(this.strokeWidth/2),i=-(this.width/2),s=-(this.height/2),o=this.padding/this.scaleX,u=this.padding/this.scaleY,a=n/this.scaleY,f=n/this.scaleX,l=(n-t)/this.scaleX,c=(n-t)/this.scaleY,h=this.height,p=this.width,d=this.transparentCorners?"strokeRect":"fillRect";return e.save(),e.lineWidth=1/Math.max(this.scaleX,this.scaleY),e.globalAlpha=this.isMoving?this.borderOpacityWhenMoving:1,e.strokeStyle=e.fillStyle=this.cornerColor,this._drawControl("tl",e,d,i-f-r-o,s-a-r-u),this._drawControl("tr",e,d,i+p-f+r+o,s-a-r-u),this._drawControl("tr",e,d,i-f-r-o,s+h+c+r+u),this._drawControl("br",e,d,i+p+l+r+o,s+h+c+r+u),this.get("lockUniScaling")||(this._drawControl("mt",e,d,i+p/2-f,s-a-r-u),this._drawControl("mb",e,d,i+p/2-f,s+h+c+r+u),this._drawControl("mb",e,d,i+p+l+r+o,s+h/2-a),this._drawControl("ml",e,d,i-f-r-o,s+h/2-a)),this.hasRotatingPoint&&this._drawControl("mtr",e,d,i+p/2-f,this.flipY?s+h+this.rotatingPointOffset/this.scaleY-this.cornerSize/this.scaleX/2+r+u:s-this.rotatingPointOffset/this.scaleY-this.cornerSize/this.scaleY/2-r-u),e.restore(),this},_drawControl:function(e,t,r,i,s){var o=this.cornerSize/this.scaleX,u=this.cornerSize/this.scaleY;this.isControlVisible(e)&&(n||this.transparentCorners||t.clearRect(i,s,o,u),t[r](i,s,o,u))},isControlVisible:function(e){return this._getControlsVisibility()[e]},setControlVisible:function(e,t){return this._getControlsVisibility()[e]=t,this},setControlsVisibility:function(e){e||(e={});for(var t in e)this.setControlVisible(t,e[t]);return this},_getControlsVisibility:function(){return this._controlsVisibility||(this._controlsVisibility={tl:!0,tr:!0,br:!0,bl:!0,ml:!0,mt:!0,mr:!0,mb:!0,mtr:!0}),this._controlsVisibility}})}(),fabric.util.object.extend(fabric.StaticCanvas.prototype,{FX_DURATION:500,fxCenterObjectH:function(e,t){t=t||{};var n=function(){},r=t.onComplete||n,i=t.onChange||n,s=this;return fabric.util.animate({startValue:e.get("left"),endValue:this.getCenter().left,duration:this.FX_DURATION,onChange:function(t){e.set("left",t),s.renderAll(),i()},onComplete:function(){e.setCoords(),r()}}),this},fxCenterObjectV:function(e,t){t=t||{};var n=function(){},r=t.onComplete||n,i=t.onChange||n,s=this;return fabric.util.animate({startValue:e.get("top"),endValue:this.getCenter().top,duration:this.FX_DURATION,onChange:function(t){e.set("top",t),s.renderAll(),i()},onComplete:function(){e.setCoords(),r()}}),this},fxRemove:function(e,t){t=t||{};var n=function(){},r=t.onComplete||n,i=t.onChange||n,s=this;return fabric.util.animate({startValue:e.get("opacity"),endValue:0,duration:this.FX_DURATION,onStart:function(){e.set("active",!1)},onChange:function(t){e.set("opacity",t),s.renderAll(),i()},onComplete:function(){s.remove(e),r()}}),this}}),fabric.util.object.extend(fabric.Object.prototype,{animate:function(){if(arguments[0]&&typeof arguments[0]=="object"){var e=[],t,n;for(t in arguments[0])e.push(t);for(var r=0,i=e.length;r'),e?e(t.join("")):t.join("")},complexity:function(){return 1}}),t.Line.ATTRIBUTE_NAMES=t.SHARED_ATTRIBUTES.concat("x1 y1 x2 y2".split(" ")),t.Line.fromElement=function(e,r){var i=t.parseAttributes(e,t.Line.ATTRIBUTE_NAMES),s=[i.x1||0,i.y1||0,i.x2||0,i.y2||0];return new t.Line(s,n(i,r))},t.Line.fromObject=function(e){var n=[e.x1,e.y1,e.x2,e.y2];return new t.Line(n,e)}}(typeof exports!="undefined"?exports:this),function(e){"use strict";function i(e){return"radius"in e&&e.radius>0}var t=e.fabric||(e.fabric={}),n=Math.PI*2,r=t.util.object.extend;if(t.Circle){t.warn("fabric.Circle is already defined.");return}t.Circle=t.util.createClass(t.Object,{type:"circle",initialize:function(e){e=e||{},this.set("radius",e.radius||0),this.callSuper("initialize",e)},_set:function(e,t){return this.callSuper("_set",e,t),e==="radius"&&this.setRadius(t),this},toObject:function(e){return r(this.callSuper("toObject",e),{radius:this.get("radius")})},toSVG:function(e){var t=this._createBaseSVGMarkup();return t.push("'),e?e(t.join("")):t.join("")},_render:function(e,t){e.beginPath(),e.globalAlpha=this.group?e.globalAlpha*this.opacity:this.opacity,e.arc(t?this.left:0,t?this.top:0,this.radius,0,n,!1),e.closePath(),this._renderFill(e),this._renderStroke(e)},getRadiusX:function(){return this.get("radius")*this.get("scaleX")},getRadiusY:function(){return this.get("radius")*this.get("scaleY")},setRadius:function(e){this.radius=e,this.set("width",e*2).set("height",e*2)},complexity:function(){return 1}}),t.Circle.ATTRIBUTE_NAMES=t.SHARED_ATTRIBUTES.concat("cx cy r".split(" ")),t.Circle.fromElement=function(e,n){n||(n={});var s=t.parseAttributes(e,t.Circle.ATTRIBUTE_NAMES);if(!i(s))throw new Error("value of `r` attribute is required and can not be negative");"left"in s&&(s.left-=n.width/2||0),"top"in s&&(s.top-=n.height/2||0);var o=new t.Circle(r(s,n));return o.cx=parseFloat(e.getAttribute("cx"))||0,o.cy=parseFloat(e.getAttribute("cy"))||0,o},t.Circle.fromObject=function(e){return new t.Circle(e)}}(typeof exports!="undefined"?exports:this),function(e){"use strict";var t=e.fabric||(e.fabric={});if(t.Triangle){t.warn("fabric.Triangle is already defined");return}t.Triangle=t.util.createClass(t.Object,{type:"triangle",initialize:function(e){e=e||{},this.callSuper("initialize",e),this.set("width",e.width||100).set("height",e.height||100)},_render:function(e){var t=this.width/2,n=this.height/2;e.beginPath(),e.moveTo(-t,n),e.lineTo(0,-n),e.lineTo(t,n),e.closePath(),this._renderFill(e),this._renderStroke(e)},_renderDashedStroke:function(e){var n=this.width/2,r=this.height/2;e.beginPath(),t.util.drawDashedLine(e,-n,r,0,-r,this.strokeDashArray),t.util.drawDashedLine(e,0,-r,n,r,this.strokeDashArray),t.util.drawDashedLine(e,n,r,-n,r,this.strokeDashArray),e.closePath()},toSVG:function(e){var t=this._createBaseSVGMarkup(),n=this.width/2,r=this.height/2,i=[-n+" "+r,"0 "+ -r,n+" "+r].join(",");return t.push("'),e?e(t.join("")):t.join("")},complexity:function(){return 1}}),t.Triangle.fromObject=function(e){return new t.Triangle(e)}}(typeof exports!="undefined"?exports:this),function(e){"use strict";var t=e.fabric||(e.fabric={}),n=Math.PI*2,r=t.util.object.extend;if(t.Ellipse){t.warn("fabric.Ellipse is already defined.");return}t.Ellipse=t.util.createClass(t.Object,{type:"ellipse",rx:0,ry:0,initialize:function(e){e=e||{},this.callSuper("initialize",e),this.set("rx",e.rx||0),this.set("ry",e.ry||0),this.set("width",this.get("rx")*2),this.set("height",this.get("ry")*2)},toObject:function(e){return r(this.callSuper("toObject",e),{rx:this.get("rx"),ry:this.get("ry")})},toSVG:function(e){var t=this._createBaseSVGMarkup();return t.push("'),e?e(t.join("")):t.join("")},render:function(e,t){if(this.rx===0||this.ry===0)return;return this.callSuper("render",e,t)},_render:function(e,t){e.beginPath(),e.save(),e.globalAlpha=this.group?e.globalAlpha*this.opacity:this.opacity,this.transformMatrix&&this.group&&e.translate(this.cx,this.cy),e.transform(1,0,0,this.ry/this.rx,0,0),e.arc(t?this.left:0,t?this.top:0,this.rx,0,n,!1),this._renderFill(e),this._renderStroke(e),e.restore()},complexity:function(){return 1}}),t.Ellipse.ATTRIBUTE_NAMES=t.SHARED_ATTRIBUTES.concat("cx cy rx ry".split(" ")),t.Ellipse.fromElement=function(e,n){n||(n={});var i=t.parseAttributes(e,t.Ellipse.ATTRIBUTE_NAMES),s=i.left,o=i.top;"left"in i&&(i.left-=n.width/2||0),"top"in i&&(i.top-=n.height/2||0);var u=new t.Ellipse(r(i,n));return u.cx=s||0,u.cy=o||0,u},t.Ellipse.fromObject=function(e){return new t.Ellipse(e)}}(typeof exports!="undefined"?exports:this),function(e){"use strict";function i(e){return e.left=e.left||0,e.top=e.top||0,e}var t=e.fabric||(e.fabric={}),n=t.util.object.extend;if(t.Rect){console.warn("fabric.Rect is already defined");return}var r=t.Object.prototype.stateProperties.concat();r.push("rx","ry","x","y"),t.Rect=t.util.createClass(t.Object,{stateProperties:r,type:"rect",rx:0,ry:0,x:0,y:0,strokeDashArray:null,initialize:function(e){e=e||{},this.callSuper("initialize",e),this._initRxRy(),this.x=e.x||0,this.y=e.y||0},_initRxRy:function(){this.rx&&!this.ry?this.ry=this.rx:this.ry&&!this.rx&&(this.rx=this.ry)},_render:function(e){var t=this.rx||0,n=this.ry||0,r=-this.width/2,i=-this.height/2,s=this.width,o=this.height,u=this.group&&this.group.type==="path-group";e.beginPath(),e.globalAlpha=u?e.globalAlpha*this.opacity:this.opacity,this.transformMatrix&&u&&e.translate(this.width/2+this.x,this.height/2+this.y),!this.transformMatrix&&u&&e.translate(-this.group.width/2+this.width/2+this.x,-this.group.height/2+this.height/2+this.y);var a=t!==0||n!==0;e.moveTo(r+t,i),e.lineTo(r+s-t,i),a&&e.quadraticCurveTo(r+s,i,r+s,i+n,r+s,i+n),e.lineTo(r+s,i+o-n),a&&e.quadraticCurveTo(r+s,i+o,r+s-t,i+o,r+s-t,i+o),e.lineTo(r+t,i+o),a&&e.quadraticCurveTo(r,i+o,r,i+o-n,r,i+o-n),e.lineTo(r,i+n),a&&e.quadraticCurveTo(r,i,r+t,i,r+t,i),e.closePath(),this._renderFill(e),this._renderStroke(e)},_renderDashedStroke:function(e){var n=-this.width/2,r=-this.height/2,i=this.width,s=this.height;e.beginPath(),t.util.drawDashedLine(e,n,r,n+i,r,this.strokeDashArray),t.util.drawDashedLine(e,n+i,r,n+i,r+s,this.strokeDashArray),t.util.drawDashedLine(e,n+i,r+s,n,r+s,this.strokeDashArray),t.util.drawDashedLine(e,n,r+s,n,r,this.strokeDashArray),e.closePath()},_normalizeLeftTopProperties:function(e){return"left"in e&&this.set("left",e.left+this.getWidth()/2),this.set("x",e.left||0),"top"in e&&this.set("top",e.top+this.getHeight()/2),this.set("y",e.top||0),this},toObject:function(e){var t=n(this.callSuper("toObject",e),{rx:this.get("rx")||0,ry:this.get("ry")||0,x:this.get("x"),y:this.get("y")});return this.includeDefaultValues||this._removeDefaultValues(t),t},toSVG:function(e){var t=this._createBaseSVGMarkup();return t.push("'),e?e(t.join("")):t.join("")},complexity:function(){return 1}}),t.Rect.ATTRIBUTE_NAMES=t.SHARED_ATTRIBUTES.concat("x y rx ry width height".split(" ")),t.Rect.fromElement=function(e,r){if(!e)return null;var s=t.parseAttributes(e,t.Rect.ATTRIBUTE_NAMES);s=i(s);var o=new t.Rect(n(r?t.util.object.clone(r):{},s));return o._normalizeLeftTopProperties(s),o},t.Rect.fromObject=function(e){return new t.Rect(e)}}(typeof exports!="undefined"?exports:this),function(e){"use strict";var t=e.fabric||(e.fabric={}),n=t.util.toFixed;if(t.Polyline){t.warn("fabric.Polyline is already defined");return}t.Polyline=t.util.createClass(t.Object,{type:"polyline",initialize:function(e,t,n){t=t||{},this.set("points",e),this.callSuper("initialize",t),this._calcDimensions(n)},_calcDimensions:function(e){return t.Polygon.prototype._calcDimensions.call(this,e)},toObject:function(e){return t.Polygon.prototype.toObject.call(this,e)},toSVG:function(e){var t=[],r=this._createBaseSVGMarkup();for(var i=0,s=this.points.length;i'),e?e(r.join("")):r.join("")},_render:function(e){var t;e.beginPath(),e.moveTo(this.points[0].x,this.points[0].y);for(var n=0,r=this.points.length;n'),e?e(n.join("")):n.join("")},_render:function(e){var t;e.beginPath(),e.moveTo(this.points[0].x,this.points[0].y);for(var n=0,r=this.points.length;n"},toObject:function(e){var t=s(this.callSuper("toObject",e),{path:this.path,pathOffset:this.pathOffset});return this.sourcePath&&(t.sourcePath=this.sourcePath),this.transformMatrix&&(t.transformMatrix=this.transformMatrix),t},toDatalessObject:function(e){var t=this.toObject(e);return this.sourcePath&&(t.path=this.sourcePath),delete t.sourcePath,t},toSVG:function(e){var t=[],n=this._createBaseSVGMarkup();for(var r=0,i=this.path.length;r',"",""),e?e(n.join("")):n.join("")},complexity:function(){return this.path.length},_parsePath:function(){var e=[],n=[],r,i,s=/(-?\.\d+)|(-?\d+(\.\d+)?)/g,o,u;for(var a=0,f,l=this.path.length;ad)for(var v=1,m=f.length;v"];for(var r=0,i=t.length;r"),e?e(n.join("")):n.join("")},toString:function(){return"#"},isSameColor:function(){var e=this.getObjects()[0].get("fill");return this.getObjects().every(function(t){return t.get("fill")===e})},complexity:function(){return this.paths.reduce(function(e,t){return e+(t&&t.complexity?t.complexity():0)},0)},getObjects:function(){return this.paths}}),t.PathGroup.fromObject=function(e,n){typeof e.paths=="string"?t.loadSVGFromURL(e.paths,function(r){var i=e.paths;delete e.paths;var s=t.util.groupSVGElements(r,e,i);n(s)}):t.util.enlivenObjects(e.paths,function(r){delete e.paths,n(new t.PathGroup(r,e))})},t.PathGroup.async=!0}(typeof exports!="undefined"?exports:this),function(e){"use strict";var t=e.fabric||(e.fabric={}),n=t.util.object.extend,r=t.util.array.min,i=t.util.array.max,s=t.util.array.invoke;if(t.Group)return;var o={lockMovementX:!0,lockMovementY:!0,lockRotation:!0,lockScalingX:!0,lockScalingY:!0,lockUniScaling:!0};t.Group=t.util.createClass(t.Object,t.Collection,{type:"group",initialize:function(e,t){t=t||{},this._objects=e||[];for(var r=this._objects.length;r--;)this._objects[r].group=this;this.originalState={},this.callSuper("initialize"),this._calcBounds(),this._updateObjectsCoords(),t&&n(this,t),this._setOpacityIfSame(),this.setCoords(!0),this.saveCoords()},_updateObjectsCoords:function(){this.forEachObject(this._updateObjectCoords,this)},_updateObjectCoords:function(e){var t=e.getLeft(),n=e.getTop();e.set({originalLeft:t,originalTop:n,left:t-this.left,top:n-this.top}),e.setCoords(),e.__origHasControls=e.hasControls,e.hasControls=!1},toString:function(){return"#"},addWithUpdate:function(e){return this._restoreObjectsState(),this._objects.push(e),e.group=this,this.forEachObject(this._setObjectActive,this),this._calcBounds(),this._updateObjectsCoords(),this},_setObjectActive:function(e){e.set("active",!0),e.group=this},removeWithUpdate:function(e){return this._moveFlippedObject(e),this._restoreObjectsState(),this.forEachObject(this._setObjectActive,this),this.remove(e),this._calcBounds(),this._updateObjectsCoords(),this},_onObjectAdded:function(e){e.group=this},_onObjectRemoved:function(e){delete e.group,e.set("active",!1)},delegatedProperties:{fill:!0,opacity:!0,fontFamily:!0,fontWeight:!0,fontSize:!0,fontStyle:!0,lineHeight:!0,textDecoration:!0,textAlign:!0,backgroundColor:!0},_set:function(e,t){if(e in this.delegatedProperties){var n=this._objects.length;this[e]=t;while(n--)this._objects[n].set(e,t)}else this[e]=t},toObject:function(e){return n(this.callSuper("toObject",e),{objects:s(this._objects,"toObject",e)})},render:function(e,n){if(!this.visible)return;e.save(),this.transform(e),this.clipTo&&t.util.clipContext(this,e);for(var r=0,i=this._objects.length;r'];for(var n=0,r=this._objects.length;n"),e?e(t.join("")):t.join("")},get:function(e){if(e in o){if(this[e])return this[e];for(var t=0,n=this._objects.length;t','");if(this.stroke||this.strokeDashArray){var n=this.fill;this.fill=null,t.push("'),this.fill=n}return t.push(""),e?e(t.join("")):t.join("")},getSrc:function(){return this.getElement().src||this.getElement()._src},toString:function(){return'#'},clone:function(e,t){this.constructor.fromObject(this.toObject(t),e)},applyFilters:function(e){if(this.filters.length===0){this._element=this._originalElement,e&&e();return}var t=this._originalElement,n=fabric.util.createCanvasElement(),r=fabric.util.createImage(),i=this;return n.width=t.width,n.height=t.height,n.getContext("2d").drawImage(t,0,0,t.width,t.height),this.filters.forEach(function(e){e&&e.applyTo(n)}),r.width=t.width,r.height=t.height,fabric.isLikelyNode?(r.src=n.toBuffer(undefined,fabric.Image.pngCompression),i._element=r,e&&e()):(r.onload=function(){i._element=r,e&&e(),r.onload=n=t=null},r.src=n.toDataURL("image/png")),this},_render:function(e){e.drawImage(this._element,-this.width/2,-this.height/2,this.width,this.height)},_resetWidthHeight:function(){var e=this.getElement();this.set("width",e.width),this.set("height",e.height)},_initElement:function(e){this.setElement(fabric.util.getById(e)),fabric.util.addClass(this.getElement(),fabric.Image.CSS_CANVAS)},_initConfig:function(e){e||(e={}),this.setOptions(e),this._setWidthHeight(e),this._element.crossOrigin=this.crossOrigin},_initFilters:function(e,t){e.filters&&e.filters.length?fabric.util.enlivenObjects(e.filters,function(e){t&&t(e)},"fabric.Image.filters"):t&&t()},_setWidthHeight:function(e){this.width="width"in e?e.width:this.getElement().width||0,this.height="height"in e?e.height:this.getElement().height||0},complexity:function(){return 1}}),fabric.Image.CSS_CANVAS="canvas-img",fabric.Image.prototype.getSvgSrc=fabric.Image.prototype.getSrc,fabric.Image.fromObject=function(e,t){fabric.util.loadImage(e.src,function(n){fabric.Image.prototype._initFilters.call(e,e,function(r){e.filters=r||[];var i=new fabric.Image(n,e);t&&t(i)})},null,e.crossOrigin)},fabric.Image.fromURL=function(e,t,n){fabric.util.loadImage(e,function(e){t(new fabric.Image(e,n))},null,n&&n.crossOrigin)},fabric.Image.ATTRIBUTE_NAMES=fabric.SHARED_ATTRIBUTES.concat("x y width height xlink:href".split(" ")),fabric.Image.fromElement=function(e,n,r){var i=fabric.parseAttributes(e,fabric.Image.ATTRIBUTE_NAMES);fabric.Image.fromURL(i["xlink:href"],n,t(r?fabric.util.object.clone(r):{},i))},fabric.Image.async=!0,fabric.Image.pngCompression=1}(typeof exports!="undefined"?exports:this),fabric.util.object.extend(fabric.Object.prototype,{_getAngleValueForStraighten:function(){var e=this.getAngle()%360;return e>0?Math.round((e-1)/90)*90:Math.round(e/90)*90},straighten:function(){return this.setAngle(this._getAngleValueForStraighten()),this},fxStraighten:function(e){e=e||{};var t=function(){},n=e.onComplete||t,r=e.onChange||t,i=this;return fabric.util.animate({startValue:this.get("angle"),endValue:this._getAngleValueForStraighten(),duration:this.FX_DURATION,onChange:function(e){i.setAngle(e),r()},onComplete:function(){i.setCoords(),n()},onStart:function(){i.set("active",!1)}}),this}}),fabric.util.object.extend(fabric.StaticCanvas.prototype,{straightenObject:function(e){return e.straighten(),this.renderAll(),this},fxStraightenObject:function(e){return e.fxStraighten({onChange:this.renderAll.bind(this)}),this}}),fabric.Image.filters=fabric.Image.filters||{},fabric.Image.filters.BaseFilter=fabric.util.createClass({type:"BaseFilter",toObject:function(){return{type:this.type}},toJSON:function(){return this.toObject()}}),function(e){"use strict";var t=e.fabric||(e.fabric={}),n=t.util.object.extend;t.Image.filters.Brightness=t.util.createClass(t.Image.filters.BaseFilter,{type:"Brightness",initialize:function(e){e=e||{},this.brightness=e.brightness||100},applyTo:function(e){var t=e.getContext("2d"),n=t.getImageData(0,0,e.width,e.height),r=n.data,i=this.brightness;for(var s=0,o=r.length;sa||C<0||C>u)continue;var k=(N*u+C)*4,L=t[x*i+T];b+=o[k]*L,w+=o[k+1]*L,E+=o[k+2]*L,S+=o[k+3]*L}h[y]=b,h[y+1]=w,h[y+2]=E,h[y+3]=S+p*(255-S)}n.putImageData(c,0,0)},toObject:function(){return n(this.callSuper("toObject"),{opaque:this.opaque,matrix:this.matrix})}}),t.Image.filters.Convolute.fromObject=function(e){return new t.Image.filters.Convolute(e)}}(typeof exports!="undefined"?exports:this),function(e){"use strict";var t=e.fabric||(e.fabric={}),n=t.util.object.extend;t.Image.filters.GradientTransparency=t.util.createClass(t.Image.filters.BaseFilter,{type:"GradientTransparency",initialize:function(e){e=e||{},this.threshold=e.threshold||100},applyTo:function(e){var t=e.getContext("2d"),n=t.getImageData(0,0,e.width,e.height),r=n.data,i=this.threshold,s=r.length;for(var o=0,u=r.length;o-1?e.channel:0},applyTo:function(e){if(!this.mask)return;var n=e.getContext("2d"),r=n.getImageData(0,0,e.width,e.height),i=r.data,s=this.mask.getElement(),o=t.util.createCanvasElement(),u=this.channel,a,f=r.width*r.height*4;o.width=s.width,o.height=s.height,o.getContext("2d").drawImage(s,0,0,s.width,s.height);var l=o.getContext("2d").getImageData(0,0,s.width,s.height),c=l.data;for(a=0;ao&&f>o&&l>o&&u(a-f)'},_render:function(e){var t=this.group&&this.group.type==="path-group";t&&!this.transformMatrix?e.translate(-this.group.width/2+this.left,-this.group.height/2+this.top):t&&this.transformMatrix&&e.translate(-this.group.width/2,-this.group.height/2),typeof Cufon=="undefined"||this.useNative===!0?this._renderViaNative(e):this._renderViaCufon(e)},_renderViaNative:function(e){var n=this.text.split(this._reNewline);this.transform(e,t.isLikelyNode),this._setTextStyles(e),this.width=this._getTextWidth(e,n),this.height=this._getTextHeight(e,n),this.clipTo&&t.util.clipContext(this,e),this._renderTextBackground(e,n),this._translateForTextAlign(e),this._renderText(e,n),this.textAlign!=="left"&&this.textAlign!=="justify"&&e.restore(),this._renderTextDecoration(e,n),this.clipTo&&e.restore(),this._setBoundaries(e,n),this._totalLineHeight=0},_renderText:function(e,t){e.save(),this._setShadow(e),this._renderTextFill(e,t),this._renderTextStroke(e,t),this._removeShadow(e),e.restore()},_translateForTextAlign:function(e){this.textAlign!=="left"&&this.textAlign!=="justify"&&(e.save(),e.translate(this.textAlign==="center"?this.width/2:this.width,0))},_setBoundaries:function(e,t){this._boundaries=[];for(var n=0,r=t.length;nn&&(n=s)}return n},_renderChars:function(e,t,n,r,i){t[e](n,r,i)},_renderTextLine:function(e,t,n,r,i,s){i-=this.fontSize/4;if(this.textAlign!=="justify"){this._renderChars(e,t,n,r,i,s);return}var o=t.measureText(n).width,u=this.width;if(u>o){var a=n.split(/\s+/),f=t.measureText(n.replace(/\s+/g,"")).width,l=u-f,c=a.length-1,h=l/c,p=0;for(var d=0,v=a.length;d-1&&i(this.fontSize*this.lineHeight),this.textDecoration.indexOf("line-through")>-1&&i(this.fontSize*this.lineHeight-this.fontSize/2),this.textDecoration.indexOf("overline")>-1&&i(this.fontSize*this.lineHeight-this.fontSize)},_getFontDeclaration:function(){return[t.isLikelyNode?this.fontWeight:this.fontStyle,t.isLikelyNode?this.fontStyle:this.fontWeight,this.fontSize+"px",t.isLikelyNode?'"'+this.fontFamily+'"':this.fontFamily].join(" ")},render:function(e,t){if(!this.visible)return;e.save(),this._render(e),!t&&this.active&&(this.drawBorders(e),this.drawControls(e)),e.restore()},toObject:function(e){var t=n(this.callSuper("toObject",e),{text:this.text,fontSize:this.fontSize,fontWeight:this.fontWeight,fontFamily:this.fontFamily,fontStyle:this.fontStyle,lineHeight:this.lineHeight,textDecoration:this.textDecoration,textAlign:this.textAlign,path:this.path,textBackgroundColor:this.textBackgroundColor,useNative:this.useNative});return this.includeDefaultValues||this._removeDefaultValues(t),t},toSVG:function(e){var t=[],n=this.text.split(this._reNewline),r=this._getSVGLeftTopOffsets(n),i=this._getSVGTextAndBg(r.lineTop,r.textLeft,n),s=this._getSVGShadows(r.lineTop,n);return r.textTop+=this._fontAscent?this._fontAscent/5*this.lineHeight:0,this._wrapSVGTextAndBg(t,i,s,r),e?e(t.join("")):t.join("")},_getSVGLeftTopOffsets:function(e){var t=this.useNative?this.fontSize*this.lineHeight:-this._fontAscent-this._fontAscent/5*this.lineHeight,n=-(this.width/2),r=this.useNative?this.fontSize-1:this.height/2-e.length*this.fontSize-this._totalLineHeight;return{textLeft:n,textTop:r,lineTop:t}},_wrapSVGTextAndBg:function(e,t,n,r){e.push('',t.textBgRects.join(""),"',n.join(""),t.textSpans.join(""),"","")},_getSVGShadows:function(e,n){var r=[],s,o,u=1;if(!this.shadow||!this._boundaries)return r;for(s=0,o=n.length;s",t.util.string.escapeXml(n[s]),""),u=1}else u++;return r},_getSVGTextAndBg:function(e,t,n){var r=[],i=[],s=1;this._setSVGBg(i);for(var o=0,u=n.length;o",t.util.string.escapeXml(e),"")},_setSVGTextLineBg:function(e,t,n,r){e.push("')},_setSVGBg:function(e){this.backgroundColor&&this._boundaries&&e.push("')},_getFillAttributes:function(e){var n=e&&typeof e=="string"?new t.Color(e):"";return!n||!n.getSource()||n.getAlpha()===1?'fill="'+e+'"':'opacity="'+n.getAlpha()+'" fill="'+n.setAlpha(1).toRgb()+'"'},_set:function(e,t){e==="fontFamily"&&this.path&&(this.path=this.path.replace(/(.*?)([^\/]*)(\.font\.js)/,"$1"+t+"$3")),this.callSuper("_set",e,t),e in this._dimensionAffectingProps&&(this._initDimensions(),this.setCoords())},complexity:function(){return 1}}),t.Text.ATTRIBUTE_NAMES=t.SHARED_ATTRIBUTES.concat("x y font-family font-style font-weight font-size text-decoration".split(" ")),t.Text.fromElement=function(e,n){if(!e)return null;var r=t.parseAttributes(e,t.Text.ATTRIBUTE_NAMES);n=t.util.object.extend(n?t.util.object.clone(n):{},r);var i=new t.Text(e.textContent,n);return i.set({left:i.getLeft()+i.getWidth()/2,top:i.getTop()-i.getHeight()/2}),i},t.Text.fromObject=function(e){return new t.Text(e.text,r(e))},t.util.createAccessors(t.Text)}(typeof exports!="undefined"?exports:this),function(){var e=fabric.util.object.clone;fabric.IText=fabric.util.createClass(fabric.Text,fabric.Observable,{type:"i-text",selectionStart:0,selectionEnd:0,selectionColor:"rgba(17,119,255,0.3)",isEditing:!1,editable:!0,editingBorderColor:"rgba(102,153,255,0.25)",cursorWidth:2,cursorColor:"#333",cursorDelay:1e3,cursorDuration:600,styles:null,caching:!0,_skipFillStrokeCheck:!0,_reSpace:/\s|\n/,_fontSizeFraction:4,_currentCursorOpacity:0,_selectionDirection:null,_abortCursorAnimation:!1,_charWidthsCache:{},initialize:function(e,t){this.styles=t?t.styles||{}:{},this.callSuper("initialize",e,t),this.initBehavior(),fabric.IText.instances.push(this),this.__lineWidths={},this.__lineHeights={},this.__lineOffsets={}},isEmptyStyles:function(){if(!this.styles)return!0;var e=this.styles;for(var t in e)for(var n in e[t])for(var r in e[t][n])return!1;return!0},setSelectionStart:function(e){this.selectionStart=e,this.hiddenTextarea&&(this.hiddenTextarea.selectionStart=e)},setSelectionEnd:function(e){this.selectionEnd=e,this.hiddenTextarea&&(this.hiddenTextarea.selectionEnd=e)},getSelectionStyles:function(e,t){if(arguments.length===2){var n=[];for(var r=e;r-1&&this._renderCharDecorationAtOffset(e,n,r+this.fontSize/this._fontSizeFraction,i,0,this.fontSize/20),u.indexOf("line-through")>-1&&this._renderCharDecorationAtOffset(e,n,r+this.fontSize/this._fontSizeFraction,i,o/2,a/20),u.indexOf("overline")>-1&&this._renderCharDecorationAtOffset(e,n,r,i,s-this.fontSize/this._fontSizeFraction,this.fontSize/20)},_renderCharDecorationAtOffset:function(e,t,n,r,i,s){e.fillRect(t,n-i,r,s)},_renderTextLine:function(e,t,n,r,i,s){i+=this.fontSize/4,this.callSuper("_renderTextLine",e,t,n,r,i,s)},_renderTextDecoration:function(e,t){if(this.isEmptyStyles())return this.callSuper("_renderTextDecoration",e,t)},_renderTextLinesBackground:function(e,t){if(!this.textBackgroundColor&&!this.styles)return;e.save(),this.textBackgroundColor&&(e.fillStyle=this.textBackgroundColor);var n=0,r=this.fontSize/this._fontSizeFraction;for(var i=0,s=t.length;in&&(n=s)}return n},_getHeightOfLine:function(e,t,n){n=n||this.text.split(this._reNewline);var r=this._getHeightOfChar(e,n[t][0],t,0),i=n[t],s=i.split("");for(var o=1,u=s.length;or&&(r=a)}return r*this.lineHeight},_getTextHeight:function(e,t){var n=0;for(var r=0,i=t.length;r-1)t++,n--;return e-t},findWordBoundaryRight:function(e){var t=0,n=e;if(this._reSpace.test(this.text.charAt(n)))while(this._reSpace.test(this.text.charAt(n)))t++,n++;while(/\S/.test(this.text.charAt(n))&&n-1)t++,n--;return e-t},findLineBoundaryRight:function(e){var t=0,n=e;while(!/\n/.test(this.text.charAt(n))&&n0&&nr;s?this.removeStyleObject(s,n+1):this.removeStyleObject(this.get2DCursorLocation(n).charIndex===0,n)}this.text=this.text.slice(0,e)+this.text.slice(t)},insertChars:function(e){var t=this.text.slice(this.selectionStart,this.selectionStart+1)==="\n";this.text=this.text.slice(0,this.selectionStart)+e+this.text.slice(this.selectionEnd),this.selectionStart===this.selectionEnd?this.insertStyleObjects(e,t,this.copiedStyles):this.selectionEnd-this.selectionStart>1&&console.log("replacing MORE than 1 char"),this.selectionStart+=e.length,this.selectionEnd=this.selectionStart,this.canvas&&this.canvas.renderAll().renderAll(),this.setCoords(),this.fire("text:changed")},insertNewlineStyleObject:function(t,n,r){this.shiftLineStyles(t,1),this.styles[t+1]||(this.styles[t+1]={});var i=this.styles[t][n-1],s={};if(r)s[0]=e(i),this.styles[t+1]=s;else{for(var o in this.styles[t])parseInt(o,10)>=n&&(s[parseInt(o,10)-n]=this.styles[t][o],delete this.styles[t][o]);this.styles[t+1]=s}},insertCharStyleObject:function(t,n,r){var i=this.styles[t],s=e(i);n===0&&!r&&(n=1);for(var o in s){var u=parseInt(o,10);u>=n&&(i[u+1]=s[u])}this.styles[t][n]=r||e(i[n-1])},insertStyleObjects:function(e,t,n){if(this.isEmptyStyles())return;var r=this.get2DCursorLocation(),i=r.lineIndex,s=r.charIndex;this.styles[i]||(this.styles[i]={}),e==="\n"?this.insertNewlineStyleObject(i,s,t):n?this._insertStyles(n):this.insertCharStyleObject(i,s)},_insertStyles:function(e){for(var t=0,n=e.length;tt&&(this.styles[s+n]=r[s])}},removeStyleObject:function(t,n){var r=this.get2DCursorLocation(n),i=r.lineIndex,s=r.charIndex;if(t){var o=this.text.split(this._reNewline),u=o[i-1],a=u.length;this.styles[i-1]||(this.styles[i-1]={});for(s in this.styles[i])this.styles[i-1][parseInt(s,10)+a]=this.styles[i][s];this.shiftLineStyles(i,-1)}else{var f=this.styles[i];if(f){var l=this.selectionStart===this.selectionEnd?-1:0;delete f[s+l]}var c=e(f);for(var h in c){var p=parseInt(h,10);p>=s&&p!==0&&(f[p-1]=c[p],delete f[p])}}},insertNewline:function(){this.insertChars("\n")}})}(),fabric.util.object.extend(fabric.IText.prototype,{initDoubleClickSimulation:function(){this.__lastClickTime=+(new Date),this.__lastLastClickTime=+(new Date),this.lastPointer={},this.on("mousedown",this.onMouseDown.bind(this))},onMouseDown:function(e){this.__newClickTime=+(new Date);var t=this.canvas.getPointer(e.e);this.isTripleClick(t)?(this.fire("tripleclick",e),this._stopEvent(e.e)):this.isDoubleClick(t)&&(this.fire("dblclick",e),this._stopEvent(e.e)),this.__lastLastClickTime=this.__lastClickTime,this.__lastClickTime=this.__newClickTime,this.__lastPointer=t},isDoubleClick:function(e){return this.__newClickTime-this.__lastClickTime<500&&this.__lastPointer.x===e.x&&this.__lastPointer.y===e.y},isTripleClick:function(e){return this.__newClickTime-this.__lastClickTime<500&&this.__lastClickTime-this.__lastLastClickTime<500&&this.__lastPointer.x===e.x&&this.__lastPointer.y===e.y},_stopEvent:function(e){e.preventDefault&&e.preventDefault(),e.stopPropagation&&e.stopPropagation()},initCursorSelectionHandlers:function(){this.initSelectedHandler(),this.initMousedownHandler(),this.initMousemoveHandler(),this.initMouseupHandler(),this.initClicks()},initClicks:function(){this.on("dblclick",function(e){this.selectWord(this.getSelectionStartFromPointer(e.e))}),this.on("tripleclick",function(e){this.selectLine(this.getSelectionStartFromPointer(e.e))})},initMousedownHandler:function(){this.on("mousedown",function(e){var t=this.canvas.getPointer(e.e);this.__mousedownX=t.x,this.__mousedownY=t.y,this.__isMousedown=!0,this.hiddenTextarea&&this.canvas&&this.canvas.wrapperEl.appendChild(this.hiddenTextarea),this.isEditing&&(this.setCursorByClick(e.e),this.__selectionStartOnMouseDown=this.selectionStart)})},initMousemoveHandler:function(){this.on("mousemove",function(e){if(!this.__isMousedown||!this.isEditing)return;var t=this.getSelectionStartFromPointer(e.e);t>=this.__selectionStartOnMouseDown?(this.setSelectionStart(this.__selectionStartOnMouseDown),this.setSelectionEnd(t)):(this.setSelectionStart(t),this.setSelectionEnd(this.__selectionStartOnMouseDown))})},_isObjectMoved:function(e){var t=this.canvas.getPointer(e);return this.__mousedownX!==t.x||this.__mousedownY!==t.y},initMouseupHandler:function(){this.on("mouseup",function(e){this.__isMousedown=!1;if(this._isObjectMoved(e.e))return;this.selected&&this.enterEditing()})},setCursorByClick:function(e){var t=this.getSelectionStartFromPointer(e);e.shiftKey?ts?0:1,a=r+u;return this.flipX&&(a=i-a),a>this.text.length&&(a=this.text.length),a}}),fabric.util.object.extend(fabric.IText.prototype,{initKeyHandlers:function(){fabric.util.addListener(fabric.document,"keydown",this.onKeyDown.bind(this)),fabric.util.addListener(fabric.document,"keypress",this.onKeyPress.bind(this))},initHiddenTextarea:function(){this.hiddenTextarea=fabric.document.createElement("textarea"),this.hiddenTextarea.setAttribute("autocapitalize","off"),this.hiddenTextarea.style.cssText="position: absolute; top: 0; left: -9999px",fabric.document.body.appendChild(this.hiddenTextarea)},_keysMap:{8:"removeChars",13:"insertNewline",37:"moveCursorLeft",38:"moveCursorUp",39:"moveCursorRight",40:"moveCursorDown",46:"forwardDelete"},_ctrlKeysMap:{65:"selectAll",67:"copy",86:"paste",88:"cut"},onKeyDown:function(e){if(!this.isEditing)return;if(e.keyCode in this._keysMap)this[this._keysMap[e.keyCode]](e);else{if(!(e.keyCode in this._ctrlKeysMap&&(e.ctrlKey||e.metaKey)))return;this[this._ctrlKeysMap[e.keyCode]](e)}e.preventDefault(),e.stopPropagation(),this.canvas&&this.canvas.renderAll()},forwardDelete:function(e){this.selectionStart===this.selectionEnd&&this.moveCursorRight(e),this.removeChars(e)},copy:function(){var e=this.getSelectedText();this.copiedText=e,this.copiedStyles=this.getSelectionStyles(this.selectionStart,this.selectionEnd)},paste:function(){this.copiedText&&this.insertChars(this.copiedText)},cut:function(e){this.copy(),this.removeChars(e)},onKeyPress:function(e){if(!this.isEditing||e.metaKey||e.ctrlKey||e.keyCode===8||e.keyCode===13)return;this.insertChars(String.fromCharCode(e.which)),e.preventDefault(),e.stopPropagation()},getDownCursorOffset:function(e,t){var n=t?this.selectionEnd:this.selectionStart,r=this.text.split(this._reNewline),i,s,o=this.text.slice(0,n),u=this.text.slice(n),a=o.slice(o.lastIndexOf("\n")+1),f=u.match(/(.*)\n?/)[1],l=(u.match(/.*\n(.*)\n?/)||{})[1]||"",c=this.get2DCursorLocation(n);if(c.lineIndex===r.length-1||e.metaKey)return this.text.length-n;var h=this._getWidthOfLine(this.ctx,c.lineIndex,r);s=this._getLineLeftOffset(h);var p=s,d=c.lineIndex;for(var v=0,m=a.length;vn){f=!0;var d=u-p,v=u,m=Math.abs(d-n),g=Math.abs(v-n);a=gthis.text.length&&(this.selectionStart=this.text.length),this.selectionEnd=this.selectionStart},moveCursorDownWithShift:function(e){if(this._selectionDirection==="left"&&this.selectionStart!==this.selectionEnd){this.selectionStart+=e,this._selectionDirection="left";return}this._selectionDirection="right",this.selectionEnd+=e,this.selectionEnd>this.text.length&&(this.selectionEnd=this.text.length)},getUpCursorOffset:function(e,t){var n=t?this.selectionEnd:this.selectionStart,r=this.get2DCursorLocation(n);if(r.lineIndex===0||e.metaKey)return n;var i=this.text.slice(0,n),s=i.slice(i.lastIndexOf("\n")+1),o=(i.match(/\n?(.*)\n.*$/)||{})[1]||"",u=this.text.split(this._reNewline),a,f,l=this._getWidthOfLine(this.ctx,r.lineIndex,u);f=this._getLineLeftOffset(l);var c=f,h=r.lineIndex;for(var p=0,d=s.length;pn){f=!0;var d=u-p,v=u,m=Math.abs(d-n),g=Math.abs(v-n);a=g=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 deleted file mode 100644 index a486530a..00000000 Binary files a/dist/all.min.js.gz and /dev/null differ diff --git a/dist/all.require.js b/dist/fabric.js similarity index 90% rename from dist/all.require.js rename to dist/fabric.js index b6812cd8..03e32a6a 100644 --- a/dist/all.require.js +++ b/dist/fabric.js @@ -1,7 +1,7 @@ /* build: `node build.js modules=ALL minifier=uglifyjs` */ -/*! Fabric.js Copyright 2008-2013, Printio (Juriy Zaytsev, Maxim Chernyak) */ +/*! Fabric.js Copyright 2008-2014, Printio (Juriy Zaytsev, Maxim Chernyak) */ -var fabric = fabric || { version: "1.4.0" }; +var fabric = fabric || { version: "1.4.6" }; if (typeof exports !== 'undefined') { exports.fabric = fabric; } @@ -37,6 +37,7 @@ fabric.isLikelyNode = typeof Buffer !== 'undefined' && * @type array */ fabric.SHARED_ATTRIBUTES = [ + "display", "transform", "fill", "fill-opacity", "fill-rule", "opacity", @@ -1276,7 +1277,7 @@ if (typeof exports != 'undefined') { /* json2.js - 2011-10-19 + 2014-02-04 Public Domain. @@ -1435,8 +1436,7 @@ if (typeof exports != 'undefined') { // 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) { +if (typeof JSON !== 'object') { JSON = {}; } @@ -1450,8 +1450,7 @@ if (!JSON) { if (typeof Date.prototype.toJSON !== 'function') { - /** @ignore */ - Date.prototype.toJSON = function (key) { + Date.prototype.toJSON = function () { return isFinite(this.valueOf()) ? this.getUTCFullYear() + '-' + @@ -1465,25 +1464,16 @@ if (!JSON) { String.prototype.toJSON = Number.prototype.toJSON = - /** @ignore */ - Boolean.prototype.toJSON = function (key) { + Boolean.prototype.toJSON = function () { 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, + var cx, + escapable, gap, indent, - meta = { // table of character substitutions - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '"' : '\\"', - '\\': '\\\\' - }, + meta, rep; @@ -1635,7 +1625,16 @@ if (!JSON) { // If the JSON object does not yet have a stringify method, give it one. if (typeof JSON.stringify !== 'function') { - /** @ignore */ + escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }; JSON.stringify = function (value, replacer, space) { // The stringify method takes a value and an optional replacer, and an optional @@ -1683,7 +1682,7 @@ if (!JSON) { // If the JSON object does not yet have a parse method, give it one. if (typeof JSON.parse !== 'function') { - /** @ignore */ + cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; JSON.parse = function (text, reviver) { // The parse method takes a text and an optional reviver function, and returns @@ -1766,6 +1765,7 @@ if (!JSON) { } }()); + /* ---------------------------------------------------- Event.js : 1.1.3 : 2013/07/17 : MIT License @@ -1795,10 +1795,10 @@ if (typeof(eventjs) === "undefined") var eventjs = Event; (function(root) { "use strict"; // Add custom *EventListener commands to HTMLElements (set false to prevent funkiness). -root.modifyEventListener = true; +root.modifyEventListener = false; // Add bulk *EventListener commands on NodeLists from querySelectorAll and others (set false to prevent funkiness). -root.modifySelectors = true; +root.modifySelectors = false; // Event maintenance. root.add = function(target, type, listener, configure) { @@ -3721,12 +3721,12 @@ 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 + * @param {...fabric.Object} object Zero or more fabric instances * @return {Self} thisArg */ add: function () { this._objects.push.apply(this._objects, arguments); - for (var i = arguments.length; i--; ) { + for (var i = 0, length = arguments.length; i < length; i++) { this._onObjectAdded(arguments[i]); } this.renderOnAddRemove && this.renderAll(); @@ -3740,6 +3740,7 @@ fabric.Collection = { * @param {Number} index Index to insert object at * @param {Boolean} nonSplicing When `true`, no splicing (shifting) of objects occurs * @return {Self} thisArg + * @chainable */ insertAt: function (object, index, nonSplicing) { var objects = this.getObjects(); @@ -3755,22 +3756,27 @@ fabric.Collection = { }, /** - * Removes an object from a collection, then renders canvas (if `renderOnAddRemove` is not `false`) - * @param {Object} object Object to remove + * Removes objects from a collection, then renders canvas (if `renderOnAddRemove` is not `false`) + * @param {...fabric.Object} object Zero or more fabric instances * @return {Self} thisArg + * @chainable */ - remove: function(object) { + remove: function() { var objects = this.getObjects(), - index = objects.indexOf(object); + index; - // only call onObjectRemoved if an object was actually removed - if (index !== -1) { - objects.splice(index, 1); - this._onObjectRemoved(object); + for (var i = 0, length = arguments.length; i < length; i++) { + index = objects.indexOf(arguments[i]); + + // only call onObjectRemoved if an object was actually removed + if (index !== -1) { + objects.splice(index, 1); + this._onObjectRemoved(arguments[i]); + } } this.renderOnAddRemove && this.renderAll(); - return object; + return this; }, /** @@ -4020,7 +4026,9 @@ fabric.Collection = { * @return {Object} Object for given namespace (default fabric) */ resolveNamespace: function(namespace) { - if (!namespace) return fabric; + if (!namespace) { + return fabric; + } var parts = namespace.split('.'), len = parts.length, @@ -4182,7 +4190,7 @@ fabric.Collection = { drawDashedLine: function(ctx, x, y, x2, y2, da) { var dx = x2 - x, dy = y2 - y, - len = sqrt(dx*dx + dy*dy), + len = sqrt(dx * dx + dy * dy), rot = atan2(dy, dx), dc = da.length, di = 0, @@ -4290,22 +4298,23 @@ fabric.Collection = { var a = [ [matrixA[0], matrixA[2], matrixA[4]], [matrixA[1], matrixA[3], matrixA[5]], - [0 , 0 , 1 ] - ]; + [0, 0, 1 ] + ], - var b = [ + b = [ [matrixB[0], matrixB[2], matrixB[4]], [matrixB[1], matrixB[3], matrixB[5]], - [0 , 0 , 1 ] - ]; + [0, 0, 1 ] + ], - var result = []; - for (var r=0; r<3; r++) { + result = []; + + for (var r = 0; r < 3; r++) { result[r] = []; - for (var c=0; c<3; c++) { + for (var c = 0; c < 3; c++) { var sum = 0; - for (var k=0; k<3; k++) { - sum += a[r][k]*b[k][c]; + for (var k = 0; k < 3; k++) { + sum += a[r][k] * b[k][c]; } result[r][c] = sum; @@ -4378,9 +4387,8 @@ fabric.Collection = { } } - var _isTransparent = true; - var imageData = ctx.getImageData( - x, y, (tolerance * 2) || 1, (tolerance * 2) || 1); + var _isTransparent = true, + imageData = ctx.getImageData(x, y, (tolerance * 2) || 1, (tolerance * 2) || 1); // Split image data - for tolerance > 1, pixelDataSize = 4; for (var i = 3, l = imageData.data.length; i < l; i += 4) { @@ -4414,37 +4422,43 @@ fabric.Collection = { return arcToSegmentsCache[argsString]; } - var coords = getXYCoords(rotateX, rx, ry, ox, oy, x, y); + var coords = getXYCoords(rotateX, rx, ry, ox, oy, x, y), - var d = (coords.x1-coords.x0) * (coords.x1-coords.x0) + - (coords.y1-coords.y0) * (coords.y1-coords.y0); + 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; + sfactorSq = 1 / d - 0.25; - 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; + if (sfactorSq < 0) { + sfactorSq = 0; } - var segments = Math.ceil(Math.abs(th_arc / (Math.PI * 0.5 + 0.001))); - var result = []; - for (var i=0; i 0 && sweep === 0) { + thArc -= 2 * Math.PI; + } + + var segments = Math.ceil(Math.abs(thArc / (Math.PI * 0.5 + 0.001))), + result = []; + + for (var i = 0; i < segments; i++) { + var th2 = th0 + i * thArc / segments, + th3 = th0 + (i + 1) * thArc / segments; + + result[i] = [xc, yc, th2, th3, rx, ry, coords.sinTh, coords.cosTh]; } arcToSegmentsCache[argsString] = result; @@ -4453,56 +4467,59 @@ fabric.Collection = { function getXYCoords(rotateX, rx, ry, ox, oy, x, y) { - var th = rotateX * (Math.PI/180); - var sin_th = Math.sin(th); - var cos_th = Math.cos(th); + var th = rotateX * (Math.PI / 180), + sinTh = Math.sin(th), + cosTh = Math.cos(th); + rx = Math.abs(rx); ry = Math.abs(ry); - var px = cos_th * (ox - x) * 0.5 + sin_th * (oy - y) * 0.5; - var py = cos_th * (oy - y) * 0.5 - sin_th * (ox - x) * 0.5; - var pl = (px*px) / (rx*rx) + (py*py) / (ry*ry); + + var px = cosTh * (ox - x) * 0.5 + sinTh * (oy - y) * 0.5, + py = cosTh * (oy - y) * 0.5 - sinTh * (ox - x) * 0.5, + pl = (px * px) / (rx * rx) + (py * py) / (ry * ry); + if (pl > 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 a00 = cosTh / rx, + a01 = sinTh / rx, + a10 = (-sinTh) / ry, + a11 = (cosTh) / ry; 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 + sinTh: sinTh, + cosTh: cosTh }; } - function segmentToBezier(cx, cy, th0, th1, rx, ry, sin_th, cos_th) { + function segmentToBezier(cx, cy, th0, th1, rx, ry, sinTh, cosTh) { argsString = _join.call(arguments); + if (segmentToBezierCache[argsString]) { return segmentToBezierCache[argsString]; } - var a00 = cos_th * rx; - var a01 = -sin_th * ry; - var a10 = sin_th * rx; - var a11 = cos_th * ry; + var a00 = cosTh * rx, + a01 = -sinTh * ry, + a10 = sinTh * rx, + a11 = cosTh * ry, + thHalf = 0.5 * (th1 - th0), + t = (8 / 3) * Math.sin(thHalf * 0.5) * + Math.sin(thHalf * 0.5) / Math.sin(thHalf), - 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); + x1 = cx + Math.cos(th0) - t * Math.sin(th0), + y1 = cy + Math.sin(th0) + t * Math.cos(th0), + x3 = cx + Math.cos(th1), + y3 = cy + Math.sin(th1), + x2 = x3 + t * Math.sin(th1), + y2 = y3 - t * Math.cos(th1); segmentToBezierCache[argsString] = [ a00 * x1 + a01 * y1, a10 * x1 + a11 * y1, @@ -4521,17 +4538,18 @@ fabric.Collection = { * @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; iString#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_ */ + /** - * Trims a string (removing whitespace from the beginning and the end) - * @function external:String#trim - * @see String#trim on MDN + * Camelizes a string + * @memberOf fabric.util.string + * @param {String} string String to camelize + * @return {String} Camelized version of a string */ - 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]+$/, ''); + 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 }; -} -/* _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 -}; }()); @@ -4907,16 +4925,16 @@ fabric.util.string = { * @return {Function} */ Function.prototype.bind = function(thisArg) { - var fn = this, args = slice.call(arguments, 1), bound; + var _this = 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))); + return apply.call(_this, this instanceof Dummy ? this : thisArg, args.concat(slice.call(arguments))); }; } else { /** @ignore */ bound = function() { - return apply.call(fn, this instanceof Dummy ? this : thisArg, arguments); + return apply.call(_this, this instanceof Dummy ? this : thisArg, arguments); }; } Dummy.prototype = this.prototype; @@ -4932,51 +4950,51 @@ fabric.util.string = { (function() { - var slice = Array.prototype.slice, emptyFunction = 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; - })(); + 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) { + /** @ignore */ + 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) { + if (property in klass.prototype && + typeof klass.prototype[property] === 'function' && + (source[property] + '').indexOf('callSuper') > -1) { - klass.prototype[property] = (function(property) { - return function() { + 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; + 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; + 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; } - }; - })(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; + } + } } - if (source.valueOf !== Object.prototype.valueOf) { - klass.prototype.valueOf = source.valueOf; - } - } - } - }; + }; function Subclass() { } @@ -5043,15 +5061,16 @@ fabric.util.string = { } return true; } - var getUniqueId = (function () { - var uid = 0; - return function (element) { - return element.__uniqueID || (element.__uniqueID = 'uniqueID__' + uid++); - }; - })(); /** @ignore */ - var getElement, setElement; + var getElement, + setElement, + getUniqueId = (function () { + var uid = 0; + return function (element) { + return element.__uniqueID || (element.__uniqueID = 'uniqueID__' + uid++); + }; + })(); (function () { var elements = { }; @@ -5207,9 +5226,9 @@ fabric.util.string = { event || (event = fabric.window.event); var element = event.target || - (typeof event.srcElement !== unknown ? event.srcElement : null); + (typeof event.srcElement !== unknown ? event.srcElement : null), - var scroll = fabric.util.getScrollLeftTop(element, upperCanvasEl); + scroll = fabric.util.getScrollLeftTop(element, upperCanvasEl); return { x: pointerX(event) + scroll.left, @@ -5222,9 +5241,9 @@ fabric.util.string = { // 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); - }; + }, - var pointerY = function(event) { + pointerY = function(event) { return (typeof event.clientY !== unknown ? event.clientY : 0); }; @@ -5339,21 +5358,21 @@ fabric.util.string = { return typeof id === 'string' ? fabric.document.getElementById(id) : id; } - /** - * Converts an array-like object (e.g. arguments or NodeList) to an array - * @memberOf fabric.util - * @param {Object} arrayLike - * @return {Array} - */ - var toArray = function(arrayLike) { - return _slice.call(arrayLike, 0); - }; + var sliceCanConvertNodelists, + /** + * Converts an array-like object (e.g. arguments or NodeList) to an array + * @memberOf fabric.util + * @param {Object} arrayLike + * @return {Array} + */ + toArray = function(arrayLike) { + return _slice.call(arrayLike, 0); + }; - var sliceCanConvertNodelists; try { sliceCanConvertNodelists = toArray(fabric.document.childNodes) instanceof Array; } - catch(err) { } + catch (err) { } if (!sliceCanConvertNodelists) { toArray = function(arrayLike) { @@ -5395,7 +5414,7 @@ fabric.util.string = { * @param {String} className Class to add to an element */ function addClass(element, className) { - if ((' ' + element.className + ' ').indexOf(' ' + className + ' ') === -1) { + if (element && (' ' + element.className + ' ').indexOf(' ' + className + ' ') === -1) { element.className += (element.className ? ' ' : '') + className; } } @@ -5418,7 +5437,14 @@ fabric.util.string = { wrapper.appendChild(element); return wrapper; } - + + /** + * Returns element scroll offsets + * @memberOf fabric.util + * @param {HTMLElement} element Element to operate on + * @param {HTMLElement} upperCanvasEl Upper canvas element + * @return {Object} Object with left/top values + */ function getScrollLeftTop(element, upperCanvasEl) { var firstFixedAncestor, @@ -5469,19 +5495,19 @@ fabric.util.string = { */ function getElementOffset(element) { var docElem, - box = {left: 0, top: 0}, doc = element && element.ownerDocument, - offset = {left: 0, top: 0}, + box = { left: 0, top: 0 }, + offset = { left: 0, top: 0 }, scrollLeftTop, offsetAttributes = { - 'borderLeftWidth': 'left', - 'borderTopWidth': 'top', - 'paddingLeft': 'left', - 'paddingTop': 'top' + borderLeftWidth: 'left', + borderTopWidth: 'top', + paddingLeft: 'left', + paddingTop: 'top' }; - if (!doc){ - return {left: 0, top: 0}; + if (!doc) { + return { left: 0, top: 0 }; } for (var attr in offsetAttributes) { @@ -5489,7 +5515,7 @@ fabric.util.string = { } docElem = doc.documentElement; - if ( typeof element.getBoundingClientRect !== "undefined" ) { + if ( typeof element.getBoundingClientRect !== 'undefined' ) { box = element.getBoundingClientRect(); } @@ -5508,33 +5534,33 @@ fabric.util.string = { * @param {String} attr Style attribute to get for element * @return {String} Style attribute value of the given element. */ - function getElementStyle(element, attr) { - if (!element.style) { - element.style = { }; - } - - if (fabric.document.defaultView && fabric.document.defaultView.getComputedStyle) { + var getElementStyle; + if (fabric.document.defaultView && fabric.document.defaultView.getComputedStyle) { + getElementStyle = function(element, attr) { return fabric.document.defaultView.getComputedStyle(element, null)[attr]; - } - else { + }; + } + else { + getElementStyle = function(element, attr) { var value = element.style[attr]; - if (!value && element.currentStyle) value = element.currentStyle[attr]; + if (!value && element.currentStyle) { + value = element.currentStyle[attr]; + } return value; - } + }; } (function () { - var style = fabric.document.documentElement.style; - - var selectProp = 'userSelect' in style - ? 'userSelect' - : 'MozUserSelect' in style - ? 'MozUserSelect' - : 'WebkitUserSelect' in style - ? 'WebkitUserSelect' - : 'KhtmlUserSelect' in style - ? 'KhtmlUserSelect' - : ''; + var style = fabric.document.documentElement.style, + selectProp = 'userSelect' in style + ? 'userSelect' + : 'MozUserSelect' in style + ? 'MozUserSelect' + : 'WebkitUserSelect' in style + ? 'WebkitUserSelect' + : 'KhtmlUserSelect' in style + ? 'KhtmlUserSelect' + : ''; /** * Makes element unselectable @@ -5587,7 +5613,7 @@ fabric.util.string = { * @param {Function} callback Callback to execute when script is finished loading */ function getScript(url, callback) { - var headEl = fabric.document.getElementsByTagName("head")[0], + var headEl = fabric.document.getElementsByTagName('head')[0], scriptEl = fabric.document.createElement('script'), loading = true; @@ -5631,9 +5657,9 @@ fabric.util.string = { var makeXHR = (function() { var factories = [ - function() { return new ActiveXObject("Microsoft.XMLHTTP"); }, - function() { return new ActiveXObject("Msxml2.XMLHTTP"); }, - function() { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); }, + function() { return new ActiveXObject('Microsoft.XMLHTTP'); }, + function() { return new ActiveXObject('Msxml2.XMLHTTP'); }, + function() { return new ActiveXObject('Msxml2.XMLHTTP.3.0'); }, function() { return new XMLHttpRequest(); } ]; for (var i = factories.length; i--; ) { @@ -5731,7 +5757,7 @@ if (typeof console !== 'undefined') { * @param {Number} [options.endValue=100] Ending value * @param {Number} [options.byValue=100] Value to modify the property by * @param {Function} [options.easing] Easing function - * @param {Number} [options.duration=500] Duration of change + * @param {Number} [options.duration=500] Duration of change (in ms) */ function animate(options) { @@ -5743,7 +5769,7 @@ if (typeof console !== 'undefined') { 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;}, + 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; @@ -5783,9 +5809,9 @@ if (typeof console !== 'undefined') { * @param {Function} callback Callback to invoke * @param {DOMElement} element optional Element to associate with animation */ - var requestAnimFrame = function() { + function requestAnimFrame() { return _requestAnimFrame.apply(fabric.window, arguments); - }; + } fabric.util.animate = animate; fabric.util.requestAnimFrame = requestAnimFrame; @@ -5796,8 +5822,13 @@ if (typeof console !== 'undefined') { (function() { 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); + 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 }; } @@ -5812,7 +5843,7 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeOutCubic(t, b, c, d) { - return c*((t=t/d-1)*t*t + 1) + b; + return c * ((t = t / d - 1) * t * t + 1) + b; } /** @@ -5821,8 +5852,10 @@ if (typeof console !== 'undefined') { */ function easeInOutCubic(t, b, c, d) { t /= d/2; - if (t < 1) return c/2*t*t*t + b; - return c/2*((t-=2)*t*t + 2) + b; + if (t < 1) { + return c / 2 * t * t * t + b; + } + return c / 2 * ((t -= 2) * t * t + 2) + b; } /** @@ -5830,7 +5863,7 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeInQuart(t, b, c, d) { - return c*(t/=d)*t*t*t + b; + return c * (t /= d) * t * t * t + b; } /** @@ -5838,7 +5871,7 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeOutQuart(t, b, c, d) { - return -c * ((t=t/d-1)*t*t*t - 1) + b; + return -c * ((t = t / d - 1) * t * t * t - 1) + b; } /** @@ -5846,9 +5879,11 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeInOutQuart(t, b, c, d) { - t /= d/2; - if (t < 1) return c/2*t*t*t*t + b; - return -c/2 * ((t-=2)*t*t*t - 2) + b; + t /= d / 2; + if (t < 1) { + return c / 2 * t * t * t * t + b; + } + return -c / 2 * ((t -= 2) * t * t * t - 2) + b; } /** @@ -5856,7 +5891,7 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeInQuint(t, b, c, d) { - return c*(t/=d)*t*t*t*t + b; + return c * (t /= d) * t * t * t * t + b; } /** @@ -5864,7 +5899,7 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeOutQuint(t, b, c, d) { - return c*((t=t/d-1)*t*t*t*t + 1) + b; + return c * ((t = t / d - 1) * t * t * t * t + 1) + b; } /** @@ -5872,9 +5907,11 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeInOutQuint(t, b, c, d) { - t /= d/2; - if (t < 1) return c/2*t*t*t*t*t + b; - return c/2*((t-=2)*t*t*t*t + 2) + b; + t /= d / 2; + if (t < 1) { + return c / 2 * t * t * t * t * t + b; + } + return c / 2 * ((t -= 2) * t * t * t * t + 2) + b; } /** @@ -5882,7 +5919,7 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeInSine(t, b, c, d) { - return -c * Math.cos(t/d * (Math.PI/2)) + c + b; + return -c * Math.cos(t / d * (Math.PI / 2)) + c + b; } /** @@ -5890,7 +5927,7 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeOutSine(t, b, c, d) { - return c * Math.sin(t/d * (Math.PI/2)) + b; + return c * Math.sin(t / d * (Math.PI / 2)) + b; } /** @@ -5898,7 +5935,7 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeInOutSine(t, b, c, d) { - return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b; + return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b; } /** @@ -5906,7 +5943,7 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeInExpo(t, b, c, d) { - return (t===0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b; + return (t === 0) ? b : c * Math.pow(2, 10 * (t / d - 1)) + b; } /** @@ -5914,7 +5951,7 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeOutExpo(t, b, c, d) { - return (t===d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b; + return (t === d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b; } /** @@ -5922,11 +5959,17 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeInOutExpo(t, b, c, d) { - if (t===0) return b; - if (t===d) return b+c; - t /= d/2; - if (t < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b; - return c/2 * (-Math.pow(2, -10 * --t) + 2) + b; + if (t === 0) { + return b; + } + if (t === d) { + return b + c; + } + t /= d / 2; + if (t < 1) { + return c / 2 * Math.pow(2, 10 * (t - 1)) + b; + } + return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b; } /** @@ -5934,7 +5977,7 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeInCirc(t, b, c, d) { - return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b; + return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b; } /** @@ -5942,7 +5985,7 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeOutCirc(t, b, c, d) { - return c * Math.sqrt(1 - (t=t/d-1)*t) + b; + return c * Math.sqrt(1 - (t = t / d - 1) * t) + b; } /** @@ -5950,9 +5993,11 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeInOutCirc(t, b, c, d) { - t /= d/2; - if (t < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b; - return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b; + t /= d / 2; + if (t < 1) { + return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b; + } + return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b; } /** @@ -5960,11 +6005,17 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeInElastic(t, b, c, d) { - var s=1.70158;var p=0;var a=c; - if (t===0) return b; + var s = 1.70158, p = 0, a = c; + if (t === 0) { + return b; + } t /= d; - if (t===1) return b+c; - if (!p) p=d*0.3; + if (t === 1) { + return b + c; + } + if (!p) { + p = d * 0.3; + } var opts = normalize(a, c, p, s); return -elastic(opts, t, d) + b; } @@ -5974,13 +6025,19 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeOutElastic(t, b, c, d) { - var s=1.70158;var p=0;var a=c; - if (t===0) return b; + var s = 1.70158, p = 0, a = c; + if (t === 0) { + return b; + } t /= d; - if (t===1) return b+c; - if (!p) p=d*0.3; + if (t === 1) { + return b + c; + } + if (!p) { + p = d * 0.3; + } 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; + return opts.a * Math.pow(2, -10 * t) * Math.sin((t * d - opts.s) * (2 * Math.PI) / opts.p ) + opts.c + b; } /** @@ -5988,14 +6045,22 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeInOutElastic(t, b, c, d) { - var s=1.70158;var p=0;var a=c; - if (t===0) return b; - t /= d/2; - if (t===2) return b+c; - if (!p) p=d*(0.3*1.5); + var s = 1.70158, p = 0, a = c; + if (t === 0) { + return b; + } + t /= d / 2; + if (t === 2) { + return b + c; + } + if (!p) { + p = d * (0.3 * 1.5); + } 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; + 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; } /** @@ -6003,8 +6068,10 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeInBack(t, b, c, d, s) { - if (s === undefined) s = 1.70158; - return c*(t/=d)*t*((s+1)*t - s) + b; + if (s === undefined) { + s = 1.70158; + } + return c * (t /= d) * t * ((s + 1) * t - s) + b; } /** @@ -6012,8 +6079,10 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeOutBack(t, b, c, d, s) { - if (s === undefined) s = 1.70158; - return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b; + if (s === undefined) { + s = 1.70158; + } + return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; } /** @@ -6021,10 +6090,14 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeInOutBack(t, b, c, d, s) { - if (s === undefined) s = 1.70158; - t /= d/2; - if (t < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b; - return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b; + if (s === undefined) { + s = 1.70158; + } + t /= d / 2; + if (t < 1) { + return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b; + } + return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b; } /** @@ -6032,7 +6105,7 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeInBounce(t, b, c, d) { - return c - easeOutBounce (d-t, 0, c, d) + b; + return c - easeOutBounce (d - t, 0, c, d) + b; } /** @@ -6040,14 +6113,17 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeOutBounce(t, b, c, d) { - if ((t/=d) < (1/2.75)) { - return c*(7.5625*t*t) + b; - } else if (t < (2/2.75)) { - return c*(7.5625*(t-=(1.5/2.75))*t + 0.75) + b; - } else if (t < (2.5/2.75)) { - return c*(7.5625*(t-=(2.25/2.75))*t + 0.9375) + b; - } else { - return c*(7.5625*(t-=(2.625/2.75))*t + 0.984375) + b; + if ((t /= d) < (1 / 2.75)) { + return c * (7.5625 * t * t) + b; + } + else if (t < (2/2.75)) { + return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b; + } + else if (t < (2.5/2.75)) { + return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b; + } + else { + return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b; } } @@ -6056,8 +6132,10 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeInOutBounce(t, b, c, d) { - if (t < d/2) return easeInBounce (t*2, 0, c, d) * 0.5 + b; - return easeOutBounce (t*2-d, 0, c, d) * 0.5 + c*0.5 + b; + if (t < d / 2) { + return easeInBounce (t * 2, 0, c, d) * 0.5 + b; + } + return easeOutBounce(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b; } /** @@ -6072,7 +6150,7 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ easeInQuad: function(t, b, c, d) { - return c*(t/=d)*t + b; + return c * (t /= d) * t + b; }, /** @@ -6080,7 +6158,7 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ easeOutQuad: function(t, b, c, d) { - return -c *(t/=d)*(t-2) + b; + return -c * (t /= d) * (t - 2) + b; }, /** @@ -6088,9 +6166,11 @@ if (typeof console !== 'undefined') { * @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; + t /= (d / 2); + if (t < 1) { + return c / 2 * t * t + b; + } + return -c / 2 * ((--t) * (t - 2) - 1) + b; }, /** @@ -6098,7 +6178,7 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ easeInCubic: function(t, b, c, d) { - return c*(t/=d)*t*t + b; + return c * (t /= d) * t * t + b; }, easeOutCubic: easeOutCubic, @@ -6134,7 +6214,7 @@ if (typeof console !== 'undefined') { (function(global) { - "use strict"; + 'use strict'; /** * @name fabric @@ -6146,34 +6226,37 @@ if (typeof console !== 'undefined') { capitalize = fabric.util.string.capitalize, clone = fabric.util.object.clone, toFixed = fabric.util.toFixed, - multiplyTransformMatrices = fabric.util.multiplyTransformMatrices; + multiplyTransformMatrices = fabric.util.multiplyTransformMatrices, - var attributesMap = { - 'fill-opacity': 'fillOpacity', - 'fill-rule': 'fillRule', - 'font-family': 'fontFamily', - 'font-size': 'fontSize', - 'font-style': 'fontStyle', - 'font-weight': 'fontWeight', - 'cx': 'left', - 'x': 'left', - 'r': 'radius', - 'stroke-dasharray': 'strokeDashArray', - 'stroke-linecap': 'strokeLineCap', - 'stroke-linejoin': 'strokeLineJoin', - 'stroke-miterlimit':'strokeMiterLimit', - 'stroke-opacity': 'strokeOpacity', - 'stroke-width': 'strokeWidth', - 'text-decoration': 'textDecoration', - 'cy': 'top', - 'y': 'top', - 'transform': 'transformMatrix' - }; + attributesMap = { + cx: 'left', + x: 'left', + r: 'radius', + cy: 'top', + y: 'top', + display: 'visible', + visibility: 'visible', + transform: 'transformMatrix', + 'fill-opacity': 'fillOpacity', + 'fill-rule': 'fillRule', + 'font-family': 'fontFamily', + 'font-size': 'fontSize', + 'font-style': 'fontStyle', + 'font-weight': 'fontWeight', + 'stroke-dasharray': 'strokeDashArray', + 'stroke-linecap': 'strokeLineCap', + 'stroke-linejoin': 'strokeLineJoin', + 'stroke-miterlimit': 'strokeMiterLimit', + 'stroke-opacity': 'strokeOpacity', + 'stroke-width': 'strokeWidth', + 'text-decoration': 'textDecoration', + 'text-anchor': 'originX' + }, - var colorAttributes = { - 'stroke': 'strokeOpacity', - 'fill': 'fillOpacity' - }; + colorAttributes = { + stroke: 'strokeOpacity', + fill: 'fillOpacity' + }; function normalizeAttr(attr) { // transform attribute names @@ -6204,6 +6287,16 @@ if (typeof console !== 'undefined') { value = fabric.parseTransformAttribute(value); } } + else if (attr === 'visible') { + value = (value === 'none' || value === 'hidden') ? false : true; + // display=none on parent element always takes precedence over child element + if (parentAttributes.visible === false) { + value = false; + } + } + else if (attr === 'originX' /* text-anchor */) { + value = value === 'start' ? 'left' : value === 'end' ? 'right' : 'center'; + } isArray = Object.prototype.toString.call(value) === '[object Array]'; @@ -6284,30 +6377,30 @@ if (typeof console !== 'undefined') { ], // == begin transform regexp - number = '(?:[-+]?\\d+(?:\\.\\d+)?(?:e[-+]?\\d+)?)', + number = '(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)', - comma_wsp = '(?:\\s+,?\\s*|,\\s*)', + commaWsp = '(?:\\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*\\))', + commaWsp + '(' + number + ')' + + commaWsp + '(' + number + '))?\\s*\\))', scale = '(?:(scale)\\s*\\(\\s*(' + number + ')(?:' + - comma_wsp + '(' + number + '))?\\s*\\))', + commaWsp + '(' + number + '))?\\s*\\))', translate = '(?:(translate)\\s*\\(\\s*(' + number + ')(?:' + - comma_wsp + '(' + number + '))?\\s*\\))', + commaWsp + '(' + number + '))?\\s*\\))', matrix = '(?:(matrix)\\s*\\(\\s*' + - '(' + number + ')' + comma_wsp + - '(' + number + ')' + comma_wsp + - '(' + number + ')' + comma_wsp + - '(' + number + ')' + comma_wsp + - '(' + number + ')' + comma_wsp + + '(' + number + ')' + commaWsp + + '(' + number + ')' + commaWsp + + '(' + number + ')' + commaWsp + + '(' + number + ')' + commaWsp + + '(' + number + ')' + commaWsp + '(' + number + ')' + '\\s*\\))', @@ -6320,12 +6413,12 @@ if (typeof console !== 'undefined') { skewY + ')', - transforms = '(?:' + transform + '(?:' + comma_wsp + transform + ')*' + ')', + transforms = '(?:' + transform + '(?:' + commaWsp + transform + ')*' + ')', - transform_list = '^\\s*(?:' + transforms + '?)\\s*$', + transformList = '^\\s*(?:' + transforms + '?)\\s*$', // http://www.w3.org/TR/SVG/coords.html#TransformAttribute - reTransformList = new RegExp(transform_list), + reTransformList = new RegExp(transformList), // == end transform regexp reTransform = new RegExp(transform, 'g'); @@ -6333,8 +6426,8 @@ if (typeof console !== 'undefined') { return function(attributeValue) { // start with identity matrix - var matrix = iMatrix.concat(); - var matrices = [ ]; + var matrix = iMatrix.concat(), + matrices = [ ]; // return if no argument was given or // an argument does not match transform attribute regexp @@ -6350,11 +6443,12 @@ if (typeof console !== 'undefined') { operation = m[1], args = m.slice(2).map(parseFloat); - switch(operation) { + switch (operation) { case 'translate': translateMatrix(matrix, args); break; case 'rotate': + args[0] = fabric.util.degreesToRadians(args[0]); rotateMatrix(matrix, args); break; case 'scale': @@ -6393,19 +6487,19 @@ if (typeof console !== 'undefined') { if (!match) return; - var fontStyle = match[1]; - // Font variant is not used - // var fontVariant = match[2]; - var fontWeight = match[3]; - var fontSize = match[4]; - var lineHeight = match[5]; - var fontFamily = match[6]; + var fontStyle = match[1], + // font variant is not used + // fontVariant = match[2], + fontWeight = match[3], + fontSize = match[4], + lineHeight = match[5], + fontFamily = match[6]; if (fontStyle) { oStyle.fontStyle = fontStyle; } if (fontWeight) { - oStyle.fontSize = isNaN(parseFloat(fontWeight)) ? fontWeight : parseFloat(fontWeight); + oStyle.fontWeight = isNaN(parseFloat(fontWeight)) ? fontWeight : parseFloat(fontWeight); } if (fontSize) { oStyle.fontSize = parseFloat(fontSize); @@ -6493,22 +6587,22 @@ if (typeof console !== 'undefined') { */ fabric.parseSVGDocument = (function() { - var reAllowedSVGTagNames = /^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/; + var reAllowedSVGTagNames = /^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/, - // http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute - // \d doesn't quite cut it (as we need to match an actual float number) + // http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute + // \d doesn't quite cut it (as we need to match an actual float number) - // matches, e.g.: +14.56e-12, etc. - var reNum = '(?:[-+]?\\d+(?:\\.\\d+)?(?:e[-+]?\\d+)?)'; + // matches, e.g.: +14.56e-12, etc. + reNum = '(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)', - var reViewBoxAttrValue = new RegExp( - '^' + - '\\s*(' + reNum + '+)\\s*,?' + - '\\s*(' + reNum + '+)\\s*,?' + - '\\s*(' + reNum + '+)\\s*,?' + - '\\s*(' + reNum + '+)\\s*' + - '$' - ); + reViewBoxAttrValue = new RegExp( + '^' + + '\\s*(' + reNum + '+)\\s*,?' + + '\\s*(' + reNum + '+)\\s*,?' + + '\\s*(' + reNum + '+)\\s*,?' + + '\\s*(' + reNum + '+)\\s*' + + '$' + ); function hasAncestorWithNodeName(element, nodeName) { while (element && (element = element.parentNode)) { @@ -6525,10 +6619,10 @@ if (typeof console !== 'undefined') { var startTime = new Date(), descendants = fabric.util.toArray(doc.getElementsByTagName('*')); - if (descendants.length === 0) { + if (descendants.length === 0 && fabric.isLikelyNode) { // we're likely in node, where "o3-xml" library fails to gEBTN("*") // https://github.com/ajaxorg/node-o3-xml/issues/21 - descendants = doc.selectNodes("//*[name(.)!='svg']"); + descendants = doc.selectNodes('//*[name(.)!="svg"]'); var arr = [ ]; for (var i = 0, len = descendants.length; i < len; i++) { arr[i] = descendants[i]; @@ -6541,30 +6635,43 @@ if (typeof console !== 'undefined') { !hasAncestorWithNodeName(el, /^(?:pattern|defs)$/); // http://www.w3.org/TR/SVG/struct.html#DefsElement }); - if (!elements || (elements && !elements.length)) return; + if (!elements || (elements && !elements.length)) { + callback && callback([], {}); + return; + } var viewBoxAttr = doc.getAttribute('viewBox'), - widthAttr = doc.getAttribute('width'), - heightAttr = doc.getAttribute('height'), + widthAttr = parseFloat(doc.getAttribute('width')), + heightAttr = parseFloat(doc.getAttribute('height')), width = null, height = null, + viewBoxWidth, + viewBoxHeight, minX, minY; if (viewBoxAttr && (viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))) { - minX = parseInt(viewBoxAttr[1], 10); - minY = parseInt(viewBoxAttr[2], 10); - width = parseInt(viewBoxAttr[3], 10); - height = parseInt(viewBoxAttr[4], 10); + minX = parseFloat(viewBoxAttr[1]); + minY = parseFloat(viewBoxAttr[2]); + viewBoxWidth = parseFloat(viewBoxAttr[3]); + viewBoxHeight = parseFloat(viewBoxAttr[4]); } - // values of width/height attributes overwrite those extracted from viewbox attribute - width = widthAttr ? parseFloat(widthAttr) : width; - height = heightAttr ? parseFloat(heightAttr) : height; + if (viewBoxWidth && widthAttr && viewBoxWidth !== widthAttr) { + width = viewBoxWidth; + height = viewBoxHeight; + } + else { + // values of width/height attributes overwrite those extracted from viewbox attribute + width = widthAttr ? widthAttr : viewBoxWidth; + height = heightAttr ? heightAttr : viewBoxHeight; + } var options = { width: width, - height: height + height: height, + widthAttr: widthAttr, + heightAttr: heightAttr }; fabric.gradientDefs = fabric.getGradientDefs(doc); @@ -6748,7 +6855,7 @@ if (typeof console !== 'undefined') { * @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); + new fabric.ElementsParser(elements, callback, options, reviver).parse(); }, /** @@ -6762,7 +6869,9 @@ if (typeof console !== 'undefined') { var oStyle = { }, style = element.getAttribute('style'); - if (!style) return oStyle; + if (!style) { + return oStyle; + } if (typeof style === 'string') { parseStyleString(style, oStyle); @@ -6798,21 +6907,27 @@ if (typeof console !== 'undefined') { len = points.length; for (; i < len; i++) { var pair = points[i].split(','); - parsedPoints.push({ x: parseFloat(pair[0]), y: parseFloat(pair[1]) }); + 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]) }); + parsedPoints.push({ + x: parseFloat(points[i]), + y: parseFloat(points[i + 1]) + }); } } // odd number of points is an error - if (parsedPoints.length % 2 !== 0) { + // if (parsedPoints.length % 2 !== 0) { // return null; - } + // } return parsedPoints; }, @@ -6892,13 +7007,13 @@ if (typeof console !== 'undefined') { function onComplete(r) { var xml = r.responseXML; - if (!xml.documentElement && fabric.window.ActiveXObject && r.responseText) { + if (xml && !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; + if (!xml || !xml.documentElement) return; fabric.parseSVGDocument(xml.documentElement, function (results, options) { svgCache.set(url, { @@ -6988,78 +7103,80 @@ if (typeof console !== 'undefined') { })(typeof exports !== 'undefined' ? exports : this); -fabric.ElementsParser = { +fabric.ElementsParser = function(elements, callback, options, reviver) { + this.elements = elements; + this.callback = callback; + this.options = options; + this.reviver = reviver; +}; - parse: function(elements, callback, options, reviver) { +fabric.ElementsParser.prototype.parse = function() { + this.instances = new Array(this.elements.length); + this.numElements = this.elements.length; - this.elements = elements; - this.callback = callback; - this.options = options; - this.reviver = reviver; + this.createObjects(); +}; - this.instances = new Array(elements.length); - this.numElements = elements.length; +fabric.ElementsParser.prototype.createObjects = function() { + for (var i = 0, len = this.elements.length; i < len; i++) { + (function(_this, i) { + setTimeout(function() { + _this.createObject(_this.elements[i], i); + }, 0); + })(this, i); + } +}; - this.createObjects(); - }, - - createObjects: function() { - for (var i = 0, len = this.elements.length; i < len; i++) { - this.createObject(this.elements[i], i); +fabric.ElementsParser.prototype.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(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(); - } - }, +fabric.ElementsParser.prototype._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(); + } +}; - _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(); - } - }, +fabric.ElementsParser.prototype.createCallback = function(index, el) { + var _this = this; + return function(obj) { + _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); - } +fabric.ElementsParser.prototype.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"; + 'use strict'; /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */ @@ -7309,7 +7426,7 @@ fabric.ElementsParser = { * @return {String} */ toString: function () { - return this.x + "," + this.y; + return this.x + ',' + this.y; }, /** @@ -7348,172 +7465,171 @@ fabric.ElementsParser = { })(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, + uaT = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x), + ubT = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x), + uB = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y); + if (uB !== 0) { + var ua = uaT / uB, + ub = ubT / uB; + 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 (uaT === 0 || ubT === 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"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }); @@ -7556,6 +7672,11 @@ fabric.ElementsParser = { color = Color.colorNameMap[color]; } + if (color === 'transparent') { + this.setSource([255,255,255,0]); + return; + } + source = Color.sourceFromHex(color); if (!source) { @@ -7674,15 +7795,15 @@ fabric.ElementsParser = { * @return {String} ex: FF5555 */ toHex: function() { - var source = this.getSource(); + var source = this.getSource(), r, g, b; - var r = source[0].toString(16); + r = source[0].toString(16); r = (r.length === 1) ? ('0' + r) : r; - var g = source[1].toString(16); + g = source[1].toString(16); g = (g.length === 1) ? ('0' + g) : g; - var b = source[2].toString(16); + b = source[2].toString(16); b = (b.length === 1) ? ('0' + b) : b; return r.toUpperCase() + g.toUpperCase() + b.toUpperCase(); @@ -7769,7 +7890,7 @@ fabric.ElementsParser = { * @field * @memberOf fabric.Color */ - fabric.Color.reRGBa = /^rgba?\(\s*(\d{1,3}\%?)\s*,\s*(\d{1,3}\%?)\s*,\s*(\d{1,3}\%?)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/; + fabric.Color.reRGBa = /^rgba?\(\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/; /** * Regex matching color in HSL or HSLA formats (ex: hsl(200, 80%, 10%), hsla(300, 50%, 80%, 0.5), hsla( 300 , 50% , 80% , 0.5 )) @@ -7795,23 +7916,23 @@ fabric.ElementsParser = { * @see: http://www.w3.org/TR/CSS2/syndata.html#color-units */ fabric.Color.colorNameMap = { - 'aqua': '#00FFFF', - 'black': '#000000', - 'blue': '#0000FF', - 'fuchsia': '#FF00FF', - 'gray': '#808080', - 'green': '#008000', - 'lime': '#00FF00', - 'maroon': '#800000', - 'navy': '#000080', - 'olive': '#808000', - 'orange': '#FFA500', - 'purple': '#800080', - 'red': '#FF0000', - 'silver': '#C0C0C0', - 'teal': '#008080', - 'white': '#FFFFFF', - 'yellow': '#FFFF00' + aqua: '#00FFFF', + black: '#000000', + blue: '#0000FF', + fuchsia: '#FF00FF', + gray: '#808080', + green: '#008000', + lime: '#00FF00', + maroon: '#800000', + navy: '#000080', + olive: '#808000', + orange: '#FFA500', + purple: '#800080', + red: '#FF0000', + silver: '#C0C0C0', + teal: '#008080', + white: '#FFFFFF', + yellow: '#FFFF00' }; /** @@ -7822,11 +7943,21 @@ fabric.ElementsParser = { * @return {Number} */ function hue2rgb(p, q, t){ - if (t < 0) t += 1; - if (t > 1) t -= 1; - if (t < 1/6) return p + (q - p) * 6 * t; - if (t < 1/2) return q; - if (t < 2/3) return p + (q - p) * (2/3 - t) * 6; + if (t < 0) { + t += 1; + } + if (t > 1) { + t -= 1; + } + if (t < 1/6) { + return p + (q - p) * 6 * t; + } + if (t < 1/2) { + return q; + } + if (t < 2/3) { + return p + (q - p) * (2/3 - t) * 6; + } return p; } @@ -7903,8 +8034,8 @@ fabric.ElementsParser = { r = g = b = l; } else { - var q = l <= 0.5 ? l * (s + 1) : l + s - l * s; - var p = l * 2 - q; + var q = l <= 0.5 ? l * (s + 1) : l + s - l * s, + p = l * 2 - q; r = hue2rgb(p, q, h + 1/3); g = hue2rgb(p, q, h); @@ -7994,7 +8125,7 @@ fabric.ElementsParser = { if (style) { var keyValuePairs = style.split(/\s*;\s*/); - if (keyValuePairs[keyValuePairs.length-1] === '') { + if (keyValuePairs[keyValuePairs.length - 1] === '') { keyValuePairs.pop(); } @@ -8097,7 +8228,11 @@ fabric.ElementsParser = { addColorStop: function(colorStop) { for (var position in colorStop) { var color = new fabric.Color(colorStop[position]); - this.colorStops.push({offset: position, color: color.toRgb(), opacity: color.getAlpha()}); + this.colorStops.push({ + offset: position, + color: color.toRgb(), + opacity: color.getAlpha() + }); } return this; }, @@ -8461,10 +8596,10 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ * @return {String} SVG representation of a pattern */ toSVG: function(object) { - var patternSource = typeof this.source === 'function' ? this.source() : this.source; - var patternWidth = patternSource.width / object.getWidth(); - var patternHeight = patternSource.height / object.getHeight(); - var patternImgSrc = ''; + var patternSource = typeof this.source === 'function' ? this.source() : this.source, + patternWidth = patternSource.width / object.getWidth(), + patternHeight = patternSource.height / object.getHeight(), + patternImgSrc = ''; if (patternSource.src) { patternImgSrc = patternSource.src; @@ -8493,11 +8628,23 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ * @return {CanvasPattern} */ toLive: function(ctx) { - var source = typeof this.source === 'function' ? this.source() : this.source; + var source = typeof this.source === 'function' + ? this.source() + : this.source; + + // if the image failed to load, return, and allow rest to continue loading + if (!source) { + return ''; + } + // if an image if (typeof source.src !== 'undefined') { - if (!source.complete) return ''; - if (source.naturalWidth === 0 || source.naturalHeight === 0) return ''; + if (!source.complete) { + return ''; + } + if (source.naturalWidth === 0 || source.naturalHeight === 0) { + return ''; + } } return ctx.createPattern(source, this.repeat); } @@ -8506,7 +8653,7 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }); @@ -8587,9 +8734,8 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ * @return {Object} Shadow object with color, offsetX, offsetY and blur */ _parseShadow: function(shadow) { - var shadowStr = shadow.trim(); - - var offsetsAndBlur = fabric.Shadow.reOffsetsAndBlur.exec(shadowStr) || [ ], + var shadowStr = shadow.trim(), + offsetsAndBlur = fabric.Shadow.reOffsetsAndBlur.exec(shadowStr) || [ ], color = shadowStr.replace(fabric.Shadow.reOffsetsAndBlur, '') || 'rgb(0,0,0)'; return { @@ -8679,7 +8825,7 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ (function () { - "use strict"; + 'use strict'; if (fabric.StaticCanvas) { fabric.warn('fabric.StaticCanvas is already defined.'); @@ -8803,7 +8949,14 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ * @type Boolean * @default */ - allowTouchScrolling: false, + allowTouchScrolling: false, + + /** + * Indicates whether this canvas will use image smoothing, this is on by default in browsers + * @type Boolean + * @default + */ + imageSmoothingEnabled: true, /** * The transformation (in the format of Canvas transform) which focuses the viewport @@ -8830,6 +8983,7 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ this._createLowerCanvas(el); this._initOptions(options); + this._setImageSmoothing(); if (options.overlayImage) { this.setOverlayImage(options.overlayImage, this.renderAll.bind(this)); @@ -8989,6 +9143,20 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ return this.__setBgOverlayColor('backgroundColor', backgroundColor, callback); }, + /** + * @private + * @see {@link http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-imagesmoothingenabled|WhatWG Canvas Standard} + */ + _setImageSmoothing: function(){ + var ctx = this.getContext(); + + ctx.imageSmoothingEnabled = this.imageSmoothingEnabled; + ctx.webkitImageSmoothingEnabled = this.imageSmoothingEnabled; + ctx.mozImageSmoothingEnabled = this.imageSmoothingEnabled; + ctx.msImageSmoothingEnabled = this.imageSmoothingEnabled; + ctx.oImageSmoothingEnabled = this.imageSmoothingEnabled; + }, + /** * @private * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundImage|backgroundImage} @@ -9076,11 +9244,14 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ this[prop] = options[prop]; } - this.width = parseInt(this.lowerCanvasEl.width, 10) || 0; - this.height = parseInt(this.lowerCanvasEl.height, 10) || 0; + this.width = this.width || parseInt(this.lowerCanvasEl.width, 10) || 0; + this.height = this.height || parseInt(this.lowerCanvasEl.height, 10) || 0; if (!this.lowerCanvasEl.style) return; + this.lowerCanvasEl.width = this.width; + this.lowerCanvasEl.height = this.height; + this.lowerCanvasEl.style.width = this.width + 'px'; this.lowerCanvasEl.style.height = this.height + 'px'; }, @@ -9402,8 +9573,8 @@ 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(); + var canvasToDrawOn = this[(allOnTop === true && this.interactive) ? 'contextTop' : 'contextContainer'], + activeGroup = this.getActiveGroup(); if (this.contextTop && this.selection && !this._groupSelector) { this.clearContext(this.contextTop); @@ -9444,12 +9615,21 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ * @param {fabric.Group} activeGroup */ _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]))) { + var i, length; + + // fast path + if (!activeGroup) { + for (i = 0, length = this._objects.length; i < length; ++i) { this._draw(ctx, this._objects[i]); } } + else { + for (i = 0, length = this._objects.length; i < length; ++i) { + if (this._objects[i] && !activeGroup.contains(this._objects[i])) { + this._draw(ctx, this._objects[i]); + } + } + } }, /** @@ -9538,9 +9718,7 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ activeGroup.render(ctx); } - if (this.overlayImage) { - ctx.drawImage(this.overlayImage, this.overlayImageLeft, this.overlayImageTop); - } + this._renderOverlay(ctx); this.fire('after:render'); @@ -9657,11 +9835,20 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ fabric.util.populateWithProperties(this, data, propertiesToInclude); if (activeGroup) { - this.setActiveGroup(new fabric.Group(activeGroup.getObjects())); + this.setActiveGroup(new fabric.Group(activeGroup.getObjects(), { + originX: 'center', + originY: 'center' + })); activeGroup.forEachObject(function(o) { o.set('active', true); }); + + if (this._currentTransform) { + this._currentTransform.target = this.getActiveGroup(); + } } + + return data; }, @@ -9798,7 +9985,7 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ 'width="', (options.viewBox ? options.viewBox.width : this.width), '" ', 'height="', (options.viewBox ? options.viewBox.height : this.height), '" ', (this.backgroundColor && !this.backgroundColor.toLive - ? 'style="background-color: ' + this.backgroundColor +'" ' + ? 'style="background-color: ' + this.backgroundColor + '" ' : null), (options.viewBox ? 'viewBox="' + @@ -9930,7 +10117,7 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ newIdx = idx; // traverse down the stack looking for the nearest intersecting object - for (var i=idx-1; i>=0; --i) { + for (var i = idx - 1; i >= 0; --i) { var isIntersecting = object.intersectsWithObject(this._objects[i]) || object.isContainedWithinObject(this._objects[i]) || @@ -9960,7 +10147,7 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ var idx = this._objects.indexOf(object); // if object is not on top of stack (last item in an array) - if (idx !== this._objects.length-1) { + if (idx !== this._objects.length - 1) { var newIdx = this._findNewUpperIndex(object, idx, intersecting); removeFromArray(this._objects, object); @@ -9993,7 +10180,7 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ } } else { - newIdx = idx+1; + newIdx = idx + 1; } return newIdx; @@ -10028,7 +10215,7 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ * @return {String} string representation of an instance */ toString: function () { - return '#'; } }); @@ -10319,27 +10506,27 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); ctx.beginPath(); - var p1 = this._points[0]; - var p2 = this._points[1]; + var p1 = this._points[0], + p2 = this._points[1]; //if we only have 2 points in the path and they are the same //it means that the user only clicked the canvas without moving the mouse //then we should be drawing a dot. A path isn't drawn between two identical dots //that's why we set them apart a bit if (this._points.length === 2 && p1.x === p2.x && p1.y === p2.y) { - p1.x -= 0.5; - p2.x += 0.5; + p1.x -= 0.5; + p2.x += 0.5; } ctx.moveTo(p1.x, p1.y); for (var i = 1, len = this._points.length; i < len; i++) { - // we pick the point between pi+1 & pi+2 as the + // we pick the point between pi + 1 & pi + 2 as the // end point and p1 as our control point. var midPoint = p1.midPointFrom(p2); ctx.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y); p1 = this._points[i]; - p2 = this._points[i+1]; + p2 = this._points[i + 1]; } // Draw last line as a straight line while // we wait for the next point to be able to calculate @@ -10365,7 +10552,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype * @param {Array} points * @return {Object} object with minx, miny, maxx, maxy */ - getPathBoundingBox: function(points) { + getPathBoundingBox: function(points) { var xBounds = [], yBounds = [], p1 = points[0], @@ -10381,19 +10568,19 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype yBounds.push(midPoint.y); p1 = points[i]; - p2 = points[i+1]; + p2 = points[i + 1]; startPoint = midPoint; - } // end for + } - xBounds.push(p1.x); - yBounds.push(p1.y); + xBounds.push(p1.x); + yBounds.push(p1.y); - return { - minx: utilMin(xBounds), - miny: utilMin(yBounds), - maxx: utilMax(xBounds), - maxy: utilMax(yBounds) - }; + return { + minx: utilMin(xBounds), + miny: utilMin(yBounds), + maxx: utilMax(xBounds), + maxy: utilMax(yBounds) + }; }, /** @@ -10402,9 +10589,9 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype * @return {String} SVG path */ convertPointsToSVGPath: function(points, minX, maxX, minY) { - var path = []; - var p1 = new fabric.Point(points[0].x - minX, points[0].y - minY); - var p2 = new fabric.Point(points[1].x - minX, points[1].y - minY); + var path = [], + p1 = new fabric.Point(points[0].x - minX, points[0].y - minY), + p2 = new fabric.Point(points[1].x - minX, points[1].y - minY); path.push('M ', points[0].x - minX, ' ', points[0].y - minY, ' '); for (var i = 1, len = points.length; i < len; i++) { @@ -10414,8 +10601,8 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype // start point is p(i-1) value. path.push('Q ', p1.x, ' ', p1.y, ' ', midPoint.x, ' ', midPoint.y, ' '); p1 = new fabric.Point(points[i].x - minX, points[i].y - minY); - if ((i+1) < points.length) { - p2 = new fabric.Point(points[i+1].x - minX, points[i+1].y - minY); + if ((i + 1) < points.length) { + p2 = new fabric.Point(points[i + 1].x - minX, points[i + 1].y - minY); } } path.push('L ', p1.x, ' ', p1.y, ' '); @@ -10454,7 +10641,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype ctx.closePath(); var pathData = this._getSVGPathData().join(''); - if (pathData === "M 0 0 Q 0 0 0 0 L 0 0") { + if (pathData === 'M 0 0 Q 0 0 0 0 L 0 0') { // do not create 0 width/height paths, as they are // rendered inconsistently across browsers // Firefox 4, for example, renders a dot, @@ -10464,8 +10651,8 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype } // set path origin coordinates based on our bounding box - var originLeft = this.box.minx + (this.box.maxx - this.box.minx) /2; - var originTop = this.box.miny + (this.box.maxy - this.box.miny) /2; + var originLeft = this.box.minx + (this.box.maxx - this.box.minx) / 2, + originTop = this.box.miny + (this.box.maxy - this.box.miny) / 2; this.canvas.contextTop.arc(originLeft, originTop, 3, 0, Math.PI * 2, false); @@ -10518,8 +10705,8 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric * @param {Object} pointer */ drawDot: function(pointer) { - var point = this.addPoint(pointer); - var ctx = this.canvas.contextTop; + var point = this.addPoint(pointer), + ctx = this.canvas.contextTop; var v = this.canvas.viewportTransform; ctx.save(); @@ -10562,15 +10749,15 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric var circles = [ ]; for (var i = 0, len = this.points.length; i < len; i++) { - var point = this.points[i]; - var circle = new fabric.Circle({ - radius: this.points[i].radius, - left: point.x, - top: point.y, - originX: 'center', - originY: 'center', - fill: this.points[i].fill - }); + var point = this.points[i], + circle = new fabric.Circle({ + radius: point.radius, + left: point.x, + top: point.y, + originX: 'center', + originY: 'center', + fill: point.fill + }); this.shadow && circle.setShadow(this.shadow); @@ -10593,12 +10780,12 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric * @return {fabric.Point} Just added pointer point */ addPoint: function(pointer) { - var pointerPoint = new fabric.Point(pointer.x, pointer.y); + var pointerPoint = new fabric.Point(pointer.x, pointer.y), - var circleRadius = fabric.util.getRandomInt( - Math.max(0, this.width - 20), this.width + 20) / 2; + circleRadius = fabric.util.getRandomInt( + Math.max(0, this.width - 20), this.width + 20) / 2, - var circleColor = new fabric.Color(this.color) + circleColor = new fabric.Color(this.color) .setAlpha(fabric.util.getRandomInt(0, 100) / 100) .toRgba(); @@ -10903,6 +11090,8 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab * @fires mouse:down * @fires mouse:move * @fires mouse:up + * @fires mouse:over + * @fires mouse:out * */ fabric.Canvas = fabric.util.createClass(fabric.StaticCanvas, /** @lends fabric.Canvas.prototype */ { @@ -11077,10 +11266,10 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab var t = this._currentTransform; t.target.set({ - 'scaleX': t.original.scaleX, - 'scaleY': t.original.scaleY, - 'left': t.original.left, - 'top': t.original.top + scaleX: t.original.scaleX, + scaleY: t.original.scaleY, + left: t.original.left, + top: t.original.top }); if (this._shouldCenterTransform(e, t.target)) { @@ -11127,7 +11316,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab // http://www.geog.ubc.ca/courses/klink/gis.notes/ncgia/u32.html // http://idav.ucdavis.edu/~okreylos/TAship/Spring2000/PointInPolygon.html - return (target.containsPoint(xy) || target._findTargetCorner(e, this._offset)); + return (target.containsPoint(xy) || target._findTargetCorner(pointer)); }, /** @@ -11137,14 +11326,12 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab var activeGroup = this.getActiveGroup(), x = pointer.x, y = pointer.y, + isObjectInGroup = ( + activeGroup && + object.type !== 'group' && + activeGroup.contains(object)), lt; - var isObjectInGroup = ( - activeGroup && - object.type !== 'group' && - activeGroup.contains(object) - ); - if (isObjectInGroup) { lt = new fabric.Point(activeGroup.left, activeGroup.top); lt = fabric.util.transformPoint(lt, this.viewportTransform, true); @@ -11279,11 +11466,8 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab _setupCurrentTransform: function (e, target) { if (!target) return; - var corner = target._findTargetCorner(e, this._offset), - pointer = fabric.util.transformPoint( - getPointer(e, this.upperCanvasEl), - fabric.util.invertTransform(this.viewportTransform) - ), + var pointer = this.getPointer(e), + corner = target._findTargetCorner(this.getPointer(e, true)), action = this._getActionFromCorner(target, corner), origin = this._getOriginFromCorner(target, corner); @@ -11345,7 +11529,6 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab */ _scaleObject: function (x, y, by) { var t = this._currentTransform, - offset = this._offset, target = t.target, lockScalingX = target.get('lockScalingX'), lockScalingY = target.get('lockScalingY'); @@ -11353,8 +11536,8 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab if (lockScalingX && lockScalingY) return; // Get the constraint point - var constraintPosition = target.translateToOriginPoint(target.getCenterPoint(), t.originX, t.originY); - var localMouse = target.toLocalPoint(new fabric.Point(x - offset.left, y - offset.top), t.originX, t.originY); + var constraintPosition = target.translateToOriginPoint(target.getCenterPoint(), t.originX, t.originY), + localMouse = target.toLocalPoint(new fabric.Point(x, y), t.originX, t.originY); this._setLocalMouse(localMouse, t); @@ -11401,9 +11584,8 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab */ _scaleObjectEqually: function(localMouse, target, transform) { - var dist = localMouse.y + localMouse.x; - - var lastDist = (target.height + (target.strokeWidth)) * transform.original.scaleY + + var dist = localMouse.y + localMouse.x, + lastDist = (target.height + (target.strokeWidth)) * transform.original.scaleY + (target.width + (target.strokeWidth)) * transform.original.scaleX; // We use transform.scaleX/Y instead of target.scaleX/Y @@ -11500,13 +11682,12 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab */ _rotateObject: function (x, y) { - var t = this._currentTransform, - o = this._offset; + var t = this._currentTransform; if (t.target.get('lockRotation')) return; - var lastAngle = atan2(t.ey - t.top - o.top, t.ex - t.left - o.left), - curAngle = atan2(y - t.top - o.top, x - t.left - o.left), + var lastAngle = atan2(t.ey - t.top, t.ex - t.left), + curAngle = atan2(y - t.top, x - t.left), angle = radiansToDegrees(curAngle - lastAngle + t.theta); // normalize angle to positive value @@ -11559,15 +11740,15 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab // selection border if (this.selectionDashArray.length > 1) { - var px = groupSelector.ex + STROKE_OFFSET - ((left > 0) ? 0: aleft); - var py = groupSelector.ey + STROKE_OFFSET - ((top > 0) ? 0: atop); + var px = groupSelector.ex + STROKE_OFFSET - ((left > 0) ? 0: aleft), + py = groupSelector.ey + STROKE_OFFSET - ((top > 0) ? 0: atop); ctx.beginPath(); - fabric.util.drawDashedLine(ctx, px, py, px+aleft, py, this.selectionDashArray); - fabric.util.drawDashedLine(ctx, px, py+atop-1, px+aleft, py+atop-1, this.selectionDashArray); - fabric.util.drawDashedLine(ctx, px, py, px, py+atop, this.selectionDashArray); - fabric.util.drawDashedLine(ctx, px+aleft-1, py, px+aleft-1, py+atop, this.selectionDashArray); + fabric.util.drawDashedLine(ctx, px, py, px + aleft, py, this.selectionDashArray); + fabric.util.drawDashedLine(ctx, px, py + atop - 1, px + aleft, py + atop - 1, this.selectionDashArray); + fabric.util.drawDashedLine(ctx, px, py, px, py + atop, this.selectionDashArray); + fabric.util.drawDashedLine(ctx, px + aleft - 1, py, px + aleft - 1, py + atop, this.selectionDashArray); ctx.closePath(); ctx.stroke(); @@ -11591,7 +11772,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab this.lastRenderedObjectWithControlsAboveOverlay && this.lastRenderedObjectWithControlsAboveOverlay.visible && this.containsPoint(e, this.lastRenderedObjectWithControlsAboveOverlay) && - this.lastRenderedObjectWithControlsAboveOverlay._findTargetCorner(e, this._offset)); + this.lastRenderedObjectWithControlsAboveOverlay._findTargetCorner(this.getPointer(e, true))); }, /** @@ -11609,10 +11790,56 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab // first check current group (if one exists) var activeGroup = this.getActiveGroup(); if (activeGroup && !skipGroup && this.containsPoint(e, activeGroup)) { + console.log('AG', activeGroup); return activeGroup; } - return this._searchPossibleTargets(e); + var target = this._searchPossibleTargets(e); + this._fireOverOutEvents(target); + + return target; + }, + + /** + * @private + */ + _fireOverOutEvents: function(target) { + if (target) { + if (this._hoveredTarget !== target) { + this.fire('mouse:over', { target: target }); + target.fire('mouseover'); + if (this._hoveredTarget) { + this.fire('mouse:out', { target: this._hoveredTarget }); + this._hoveredTarget.fire('mouseout'); + } + this._hoveredTarget = target; + } + } + else if (this._hoveredTarget) { + this.fire('mouse:out', { target: this._hoveredTarget }); + this._hoveredTarget.fire('mouseout'); + this._hoveredTarget = null; + } + }, + + /** + * @private + */ + _checkTarget: function(e, obj, pointer) { + if (obj && + obj.visible && + obj.evented && + this.containsPoint(e, obj)){ + if ((this.perPixelTargetFind || obj.perPixelTargetFind) && !obj.isEditing) { + var isTransparent = this.isTargetTransparent(obj, pointer.x, pointer.y); + if (!isTransparent) { + return true; + } + } + else { + return true; + } + } }, /** @@ -11621,33 +11848,15 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab _searchPossibleTargets: function(e) { // Cache all targets where their bounding box contains point. - var possibleTargets = [], - target, - pointer = this.getPointer(e, true); + var target, + pointer = this.getPointer(e); - for (var i = this._objects.length; i--; ) { - if (this._objects[i] && - this._objects[i].visible && - this._objects[i].evented && - this.containsPoint(e, this._objects[i])) { + var i = this._objects.length; - if (this.perPixelTargetFind || this._objects[i].perPixelTargetFind) { - possibleTargets[possibleTargets.length] = this._objects[i]; - } - else { - target = this._objects[i]; - this.relatedTarget = target; - break; - } - } - } - - for (var j = 0, len = possibleTargets.length; j < len; j++) { - pointer = this.getPointer(e, true); - var isTransparent = this.isTargetTransparent(possibleTargets[j], pointer.x, pointer.y); - if (!isTransparent) { - target = possibleTargets[j]; - this.relatedTarget = target; + while (i--) { + if (this._checkTarget(e, this._objects[i], pointer)){ + this.relatedTarget = this._objects[i]; + target = this._objects[i]; break; } } @@ -11664,7 +11873,12 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab if (!upperCanvasEl) { upperCanvasEl = this.upperCanvasEl; } - var pointer = getPointer(e, upperCanvasEl); + var pointer = getPointer(e, upperCanvasEl), + bounds = upperCanvasEl.getBoundingClientRect(), + cssScale; + + pointer.x = pointer.x - this._offset.left; + pointer.y = pointer.y - this._offset.top; if (!ignoreZoom) { pointer = fabric.util.transformPoint( pointer, @@ -11672,9 +11886,19 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab ); } + if (bounds.width === 0 || bounds.height === 0) { + // If bounds are not available (i.e. not visible), do not apply scale. + cssScale = { width: 1, height: 1 }; + } + else { + cssScale = { + width: upperCanvasEl.width / bounds.width, + height: upperCanvasEl.height / bounds.height + }; + } return { - x: pointer.x - this._offset.left, - y: pointer.y - this._offset.top + x: pointer.x * cssScale.width, + y: pointer.y * cssScale.height }; }, @@ -11985,32 +12209,36 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab (function(){ - var cursorMap = [ - 'n-resize', - 'ne-resize', - 'e-resize', - 'se-resize', - 's-resize', - 'sw-resize', - 'w-resize', - 'nw-resize' - ], - cursorOffset = { - 'mt': 0, // n - 'tr': 1, // ne - 'mr': 2, // e - 'br': 3, // se - 'mb': 4, // s - 'bl': 5, // sw - 'ml': 6, // w - 'tl': 7 // nw + var cursorOffset = { + mt: 0, // n + tr: 1, // ne + mr: 2, // e + br: 3, // se + mb: 4, // s + bl: 5, // sw + ml: 6, // w + tl: 7 // nw }, addListener = fabric.util.addListener, - removeListener = fabric.util.removeListener, - getPointer = fabric.util.getPointer; + removeListener = fabric.util.removeListener; fabric.util.object.extend(fabric.Canvas.prototype, /** @lends fabric.Canvas.prototype */ { + /** + * Map of cursor style values for each of the object controls + * @private + */ + cursorMap: [ + 'n-resize', + 'ne-resize', + 'e-resize', + 'se-resize', + 's-resize', + 'sw-resize', + 'w-resize', + 'nw-resize' + ], + /** * Adds mouse listeners to canvas * @private @@ -12126,14 +12354,20 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab _onMouseDown: function (e) { this.__onMouseDown(e); - addListener(fabric.document, 'mouseup', this._onMouseUp); addListener(fabric.document, 'touchend', this._onMouseUp); - - addListener(fabric.document, 'mousemove', this._onMouseMove); addListener(fabric.document, 'touchmove', this._onMouseMove); removeListener(this.upperCanvasEl, 'mousemove', this._onMouseMove); removeListener(this.upperCanvasEl, 'touchmove', this._onMouseMove); + + if (e.type === 'touchstart') { + // Unbind mousedown to prevent double triggers from touch devices + removeListener(this.upperCanvasEl, 'mousedown', this._onMouseDown); + } + else { + addListener(fabric.document, 'mouseup', this._onMouseUp); + addListener(fabric.document, 'mousemove', this._onMouseMove); + } }, /** @@ -12151,6 +12385,15 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab addListener(this.upperCanvasEl, 'mousemove', this._onMouseMove); addListener(this.upperCanvasEl, 'touchmove', this._onMouseMove); + + if (e.type === 'touchend') { + // Wait 400ms before rebinding mousedown to prevent double triggers + // from touch devices + var _this = this; + setTimeout(function() { + addListener(_this.upperCanvasEl, 'mousedown', _this._onMouseDown); + }, 400); + } }, /** @@ -12249,8 +12492,8 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab */ _finalizeCurrentTransform: function() { - var transform = this._currentTransform; - var target = transform.target; + var transform = this._currentTransform, + target = transform.target; if (target._scaling) { target._scaling = false; @@ -12392,7 +12635,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab this.stateful && target.saveState(); // determine if it's a drag or rotate case - if ((corner = target._findTargetCorner(e, this._offset))) { + if ((corner = target._findTargetCorner(this.getPointer(e)))) { this.onBeforeScaleRotate(target); } @@ -12514,11 +12757,11 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab * @param {Event} e Event fired on mousemove */ _transformObject: function(e) { - var pointer = fabric.util.transformPoint( - getPointer(e, this.upperCanvasEl), + fabric.util.getPointer(e, this.upperCanvasEl), fabric.util.invertTransform(this.viewportTransform) ), + pointer = this.getPointer(e), transform = this._currentTransform; transform.reset = false, @@ -12566,7 +12809,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab * @private */ _fire: function(eventName, target, e) { - this.fire('object:' + eventName, { target: target, e: e}); + this.fire('object:' + eventName, { target: target, e: e }); target.fire(eventName, { e: e }); }, @@ -12623,11 +12866,11 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab return false; } else { - var activeGroup = this.getActiveGroup(); - // only show proper corner when group selection is not active - var corner = target._findTargetCorner + var activeGroup = this.getActiveGroup(), + // only show proper corner when group selection is not active + corner = target._findTargetCorner && (!activeGroup || !activeGroup.contains(target)) - && target._findTargetCorner(e, this._offset); + && target._findTargetCorner(this.getPointer(e, true)); if (!corner) { style.cursor = target.hoverCursor || this.hoverCursor; @@ -12670,7 +12913,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab // normalize n to be from 0 to 7 n %= 8; - return cursorMap[n]; + return this.cursorMap[n]; } }); })(); @@ -12777,13 +13020,11 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab */ _createGroup: function(target) { - var objects = this.getObjects(); - - var isActiveLower = objects.indexOf(this._activeObject) < objects.indexOf(target); - - var groupObjects = isActiveLower - ? [ this._activeObject, target ] - : [ target, this._activeObject ]; + var objects = this.getObjects(), + isActiveLower = objects.indexOf(this._activeObject) < objects.indexOf(target), + groupObjects = isActiveLower + ? [ this._activeObject, target ] + : [ target, this._activeObject ]; return new fabric.Group(groupObjects, { originX: 'center', @@ -12935,8 +13176,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati this.renderAll(true); - var canvasEl = this.upperCanvasEl || this.lowerCanvasEl; - var croppedCanvasEl = this.__getCroppedCanvas(canvasEl, cropping); + var canvasEl = this.upperCanvasEl || this.lowerCanvasEl, + croppedCanvasEl = this.__getCroppedCanvas(canvasEl, cropping); // to avoid common confusion https://github.com/kangax/fabric.js/issues/806 if (format === 'jpg') { @@ -12963,9 +13204,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati __getCroppedCanvas: function(canvasEl, cropping) { var croppedCanvasEl, - croppedCtx; - - var shouldCrop = 'left' in cropping || + croppedCtx, + shouldCrop = 'left' in cropping || 'top' in cropping || 'width' in cropping || 'height' in cropping; @@ -12998,7 +13238,9 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati ctx = this.contextTop || this.contextContainer; - this.setWidth(scaledWidth).setHeight(scaledHeight); + if (multiplier > 1) { + this.setWidth(scaledWidth).setHeight(scaledHeight); + } ctx.scale(multiplier, multiplier); if (cropping.left) { @@ -13010,9 +13252,15 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati if (cropping.width) { cropping.width *= multiplier; } + else if (multiplier < 1) { + cropping.width = scaledWidth; + } if (cropping.height) { cropping.height *= multiplier; } + else if (multiplier < 1) { + cropping.height = scaledHeight; + } if (activeGroup) { // not removing group due to complications with restoring it with correct state afterwords @@ -13225,8 +13473,9 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati _enlivenObjects: function (objects, callback, reviver) { var _this = this; - if (objects.length === 0) { + if (!objects || objects.length === 0) { callback && callback(); + return; } var renderOnAddRemove = this.renderOnAddRemove; @@ -13324,7 +13573,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati */ __onTransformGesture: function(e, self) { - if (this.isDrawingMode || e.touches.length !== 2 || 'gesture' !== self.gesture) { + if (this.isDrawingMode || !e.touches || e.touches.length !== 2 || 'gesture' !== self.gesture) { return; } @@ -13335,7 +13584,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati this._scaleObjectBy(self.scale); } - this.fire('touch:gesture', {target: target, e: e, self: self}); + this.fire('touch:gesture', { target: target, e: e, self: self }); }, /** @@ -13345,7 +13594,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati * @param self Event proxy object by Event.js */ __onDrag: function(e, self) { - this.fire('touch:drag', {e: e, self: self}); + this.fire('touch:drag', { e: e, self: self }); }, /** @@ -13355,7 +13604,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati * @param self Event proxy object by Event.js */ __onOrientationChange: function(e, self) { - this.fire('touch:orientation', {e: e, self: self}); + this.fire('touch:orientation', { e: e, self: self }); }, /** @@ -13365,7 +13614,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati * @param self Event proxy object by Event.js */ __onShake: function(e, self) { - this.fire('touch:shake', {e: e, self: self}); + this.fire('touch:shake', { e: e, self: self }); }, /** @@ -13376,10 +13625,9 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati */ _scaleObjectBy: function(s, by) { var t = this._currentTransform, - target = t.target; - - var lockScalingX = target.get('lockScalingX'), - lockScalingY = target.get('lockScalingY'); + target = t.target, + lockScalingX = target.get('lockScalingX'), + lockScalingY = target.get('lockScalingY'); if (lockScalingX && lockScalingY) return; @@ -13417,7 +13665,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend, @@ -14028,6 +14276,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati /** * Function that determines clipping of an object (context is passed as a first argument) + * Note that context origin is at the object's center point (not left/top corner) * @type Function */ clipTo: null, @@ -14169,34 +14418,34 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati */ toObject: function(propertiesToInclude) { - var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS; + var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, - var object = { - type: this.type, - originX: this.originX, - originY: this.originY, - left: toFixed(this.left, NUM_FRACTION_DIGITS), - top: toFixed(this.top, NUM_FRACTION_DIGITS), - width: toFixed(this.width, NUM_FRACTION_DIGITS), - height: toFixed(this.height, NUM_FRACTION_DIGITS), - fill: (this.fill && this.fill.toObject) ? this.fill.toObject() : this.fill, - stroke: (this.stroke && this.stroke.toObject) ? this.stroke.toObject() : this.stroke, - strokeWidth: toFixed(this.strokeWidth, NUM_FRACTION_DIGITS), - strokeDashArray: this.strokeDashArray, - strokeLineCap: this.strokeLineCap, - strokeLineJoin: this.strokeLineJoin, - strokeMiterLimit: toFixed(this.strokeMiterLimit, NUM_FRACTION_DIGITS), - scaleX: toFixed(this.scaleX, NUM_FRACTION_DIGITS), - scaleY: toFixed(this.scaleY, NUM_FRACTION_DIGITS), - angle: toFixed(this.getAngle(), NUM_FRACTION_DIGITS), - flipX: this.flipX, - flipY: this.flipY, - opacity: toFixed(this.opacity, NUM_FRACTION_DIGITS), - shadow: (this.shadow && this.shadow.toObject) ? this.shadow.toObject() : this.shadow, - visible: this.visible, - clipTo: this.clipTo && String(this.clipTo), - backgroundColor: this.backgroundColor - }; + object = { + type: this.type, + originX: this.originX, + originY: this.originY, + left: toFixed(this.left, NUM_FRACTION_DIGITS), + top: toFixed(this.top, NUM_FRACTION_DIGITS), + width: toFixed(this.width, NUM_FRACTION_DIGITS), + height: toFixed(this.height, NUM_FRACTION_DIGITS), + fill: (this.fill && this.fill.toObject) ? this.fill.toObject() : this.fill, + stroke: (this.stroke && this.stroke.toObject) ? this.stroke.toObject() : this.stroke, + strokeWidth: toFixed(this.strokeWidth, NUM_FRACTION_DIGITS), + strokeDashArray: this.strokeDashArray, + strokeLineCap: this.strokeLineCap, + strokeLineJoin: this.strokeLineJoin, + strokeMiterLimit: toFixed(this.strokeMiterLimit, NUM_FRACTION_DIGITS), + scaleX: toFixed(this.scaleX, NUM_FRACTION_DIGITS), + scaleY: toFixed(this.scaleY, NUM_FRACTION_DIGITS), + angle: toFixed(this.getAngle(), NUM_FRACTION_DIGITS), + flipX: this.flipX, + flipY: this.flipY, + opacity: toFixed(this.opacity, NUM_FRACTION_DIGITS), + shadow: (this.shadow && this.shadow.toObject) ? this.shadow.toObject() : this.shadow, + visible: this.visible, + clipTo: this.clipTo && String(this.clipTo), + backgroundColor: this.backgroundColor + }; if (!this.includeDefaultValues) { object = this._removeDefaultValues(object); @@ -14222,8 +14471,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati * @param {Object} object */ _removeDefaultValues: function(object) { - var prototype = fabric.util.getKlass(object.type).prototype; - var stateProperties = prototype.stateProperties; + var prototype = fabric.util.getKlass(object.type).prototype, + stateProperties = prototype.stateProperties; stateProperties.forEach(function(prop) { if (object[prop] === prototype[prop]) { @@ -14239,7 +14488,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati * @return {String} */ toString: function() { - return "#"; + return '#'; }, /** @@ -14251,6 +14500,15 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati return this[property]; }, + /** + * @private + */ + _setObject: function(obj) { + for (var prop in obj) { + this._set(prop, obj[prop]); + } + }, + /** * Sets property to a given value. When changing position/dimension -related properties (left, top, scale, angle, etc.) `set` does not update position of object's borders/controls. If you need to update those, call `setCoords()`. * @param {String|Object} key Property name or object (if object, iterate over the object properties) @@ -14260,9 +14518,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati */ set: function(key, value) { if (typeof key === 'object') { - for (var prop in key) { - this._set(prop, key[prop]); - } + this._setObject(key); } else { if (typeof value === 'function' && key !== 'clipTo') { @@ -14332,6 +14588,18 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati return this; }, + /** + * Retrieves viewportTransform from Object's canvas if possible + * @method getViewportTransform + * @memberOf fabric.Object.prototype + * @return {Boolean} flipY value // TODO + */ + getViewportTransform: function() { + if (this.canvas && this.canvas.viewportTransform) + return this.canvas.viewportTransform; + return [1, 0, 0, 1, 0, 0]; + }, + /** * Renders an object on a specified context * @param {CanvasRenderingContext2D} ctx Context to render on @@ -14343,6 +14611,9 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati ctx.save(); + //setup fill rule for current object + this._setupFillRule(ctx); + this._transform(ctx, noTransform); this._setStrokeStyles(ctx); this._setFillStyles(ctx); @@ -14358,6 +14629,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati this._render(ctx, noTransform); this.clipTo && ctx.restore(); this._removeShadow(ctx); + this._restoreFillRule(ctx); + ctx.restore(); this._renderControls(ctx, noTransform); @@ -14365,7 +14638,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati _transform: function(ctx, noTransform) { var m = this.transformMatrix; - var v = this.canvas.viewportTransform; + var v = this.getViewportTransform(); ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); @@ -14403,7 +14676,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati * @param {Boolean} [noTransform] When true, context is not transformed */ _renderControls: function(ctx, noTransform) { - var v = this.canvas.viewportTransform; + var v = this.getViewportTransform(); ctx.save(); if (this.active && !noTransform) { @@ -14444,6 +14717,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati * @param {CanvasRenderingContext2D} ctx Context to render on */ _removeShadow: function(ctx) { + if (!this.shadow) return; + ctx.shadowColor = ''; ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0; }, @@ -14461,7 +14736,12 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati -this.width / 2 + this.fill.offsetX || 0, -this.height / 2 + this.fill.offsetY || 0); } - ctx.fill(); + if (this.fillRule === 'destination-over') { + ctx.fill('evenodd'); + } + else { + ctx.fill(); + } if (this.fill.toLive) { ctx.restore(); } @@ -14660,7 +14940,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati setGradient: function(property, options) { options || (options = { }); - var gradient = {colorStops: []}; + var gradient = { colorStops: [] }; gradient.type = options.type || (options.r1 || options.r2 ? 'radial' : 'linear'); gradient.coords = { @@ -14677,7 +14957,11 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati for (var position in options.colorStops) { var color = new fabric.Color(options.colorStops[position]); - gradient.colorStops.push({offset: position, color: color.toRgb(), opacity: color.getAlpha()}); + gradient.colorStops.push({ + offset: position, + color: color.toRgb(), + opacity: color.getAlpha() + }); } return this.set(property, fabric.Gradient.forObject(this, gradient)); @@ -14735,7 +15019,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati /** * Sets "color" of an instance (alias of `set('fill', …)`) * @param {String} color Color value - * @return {fabric.Text} thisArg + * @return {fabric.Object} thisArg * @chainable */ setColor: function(color) { @@ -14743,6 +15027,28 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati return this; }, + /** + * Sets "angle" of an instance + * @param {Number} angle Angle value + * @return {fabric.Object} thisArg + * @chainable + */ + setAngle: function(angle) { + var shouldCenterOrigin = (this.originX !== 'center' || this.originY !== 'center') && this.centeredRotation; + + if (shouldCenterOrigin) { + this._setOriginToCenter(); + } + + this.set('angle', angle); + + if (shouldCenterOrigin) { + this._resetOrigin(); + } + + return this; + }, + /** * Centers object horizontally on canvas to which it was added last. * You might need to call `setCoords` on an object after centering, to update controls area. @@ -14782,7 +15088,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati * @chainable */ remove: function() { - return this.canvas.remove(this); + this.canvas.remove(this); + return this; }, /** @@ -14798,6 +15105,28 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati x: pointer.x - objectLeftTop.x, y: pointer.y - objectLeftTop.y }; + }, + + /** + * Sets canvas globalCompositeOperation for specific object + * custom composition operation for the particular object can be specifed using fillRule property + * @param {CanvasRenderingContext2D} ctx Rendering canvas context + */ + _setupFillRule: function (ctx) { + if (this.fillRule) { + this._prevFillRule = ctx.globalCompositeOperation; + ctx.globalCompositeOperation = this.fillRule; + } + }, + + /** + * Restores previously saved canvas globalCompositeOperation after obeject rendering + * @param {CanvasRenderingContext2D} ctx Rendering canvas context + */ + _restoreFillRule: function (ctx) { + if (this.fillRule && this._prevFillRule) { + ctx.globalCompositeOperation = this._prevFillRule; + } } }); @@ -14851,17 +15180,17 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati cy = point.y, strokeWidth = this.stroke ? this.strokeWidth : 0; - if (originX === "left") { + if (originX === 'left') { cx = point.x + (this.getWidth() + strokeWidth * this.scaleX) / 2; } - else if (originX === "right") { + else if (originX === 'right') { cx = point.x - (this.getWidth() + strokeWidth * this.scaleX) / 2; } - if (originY === "top") { + if (originY === 'top') { cy = point.y + (this.getHeight() + strokeWidth * this.scaleY) / 2; } - else if (originY === "bottom") { + else if (originY === 'bottom') { cy = point.y - (this.getHeight() + strokeWidth * this.scaleY) / 2; } @@ -14882,16 +15211,16 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati strokeWidth = this.stroke ? this.strokeWidth : 0; // Get the point coordinates - if (originX === "left") { + if (originX === 'left') { x = center.x - (this.getWidth() + strokeWidth * this.scaleX) / 2; } - else if (originX === "right") { + else if (originX === 'right') { x = center.x + (this.getWidth() + strokeWidth * this.scaleX) / 2; } - if (originY === "top") { + if (originY === 'top') { y = center.y - (this.getHeight() + strokeWidth * this.scaleY) / 2; } - else if (originY === "bottom") { + else if (originY === 'bottom') { y = center.y + (this.getHeight() + strokeWidth * this.scaleY) / 2; } @@ -14941,20 +15270,20 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati x, y; if (originX && originY) { - if (originX === "left") { + if (originX === 'left') { x = center.x - (this.getWidth() + strokeWidth * this.scaleX) / 2; } - else if (originX === "right") { + else if (originX === 'right') { x = center.x + (this.getWidth() + strokeWidth * this.scaleX) / 2; } else { x = center.x; } - if (originY === "top") { + if (originY === 'top') { y = center.y - (this.getHeight() + strokeWidth * this.scaleY) / 2; } - else if (originY === "bottom") { + else if (originY === 'bottom') { y = center.y + (this.getHeight() + strokeWidth * this.scaleY) / 2; } else { @@ -14987,8 +15316,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati * @return {void} */ setPositionByOrigin: function(pos, originX, originY) { - var center = this.translateToCenterPoint(pos, originX, originY); - var position = this.translateToOriginPoint(center, this.originX, this.originY); + var center = this.translateToCenterPoint(pos, originX, originY), + position = this.translateToOriginPoint(center, this.originX, this.originY); this.set('left', position.x); this.set('top', position.y); @@ -14998,13 +15327,13 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati * @param {String} to One of 'left', 'center', 'right' */ adjustPosition: function(to) { - var angle = degreesToRadians(this.angle); - var hypotHalf = this.getWidth() / 2; - var xHalf = Math.cos(angle) * hypotHalf; - var yHalf = Math.sin(angle) * hypotHalf; - var hypotFull = this.getWidth(); - var xFull = Math.cos(angle) * hypotFull; - var yFull = Math.sin(angle) * hypotFull; + var angle = degreesToRadians(this.angle), + hypotHalf = this.getWidth() / 2, + xHalf = Math.cos(angle) * hypotHalf, + yHalf = Math.sin(angle) * hypotHalf, + hypotFull = this.getWidth(), + xFull = Math.cos(angle) * hypotFull, + yFull = Math.sin(angle) * hypotFull; if (this.originX === 'center' && to === 'left' || this.originX === 'right' && to === 'center') { @@ -15033,6 +15362,45 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati this.originX = to; }, + /** + * @private + * Sets the origin/position of the object to it's center point + * @return {void} + */ + _setOriginToCenter: function() { + this._originalOriginX = this.originX; + this._originalOriginY = this.originY; + + var center = this.getCenterPoint(); + + this.originX = 'center'; + this.originY = 'center'; + + this.left = center.x; + this.top = center.y; + }, + + /** + * @private + * Resets the origin/position of the object to it's original origin + * @return {void} + */ + _resetOrigin: function() { + var originPoint = this.translateToOriginPoint( + this.getCenterPoint(), + this._originalOriginX, + this._originalOriginY); + + this.originX = this._originalOriginX; + this.originY = this._originalOriginY; + + this.left = originPoint.x; + this.top = originPoint.y; + + this._originalOriginX = null; + this._originalOriginY = null; + }, + /** * @private */ @@ -15068,13 +15436,12 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati tl = new fabric.Point(oCoords.tl.x, oCoords.tl.y), tr = new fabric.Point(oCoords.tr.x, oCoords.tr.y), bl = new fabric.Point(oCoords.bl.x, oCoords.bl.y), - br = new fabric.Point(oCoords.br.x, oCoords.br.y); - - var intersection = fabric.Intersection.intersectPolygonRectangle( - [tl, tr, br, bl], - pointTL, - pointBR - ); + br = new fabric.Point(oCoords.br.x, oCoords.br.y), + intersection = fabric.Intersection.intersectPolygonRectangle( + [tl, tr, br, bl], + pointTL, + pointBR + ); return intersection.status === 'Intersection'; }, @@ -15094,12 +15461,11 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati }; } var thisCoords = getCoords(this.oCoords), - otherCoords = getCoords(other.oCoords); - - var intersection = fabric.Intersection.intersectPolygonPolygon( - [thisCoords.tl, thisCoords.tr, thisCoords.br, thisCoords.bl], - [otherCoords.tl, otherCoords.tr, otherCoords.br, otherCoords.bl] - ); + otherCoords = getCoords(other.oCoords), + intersection = fabric.Intersection.intersectPolygonPolygon( + [thisCoords.tl, thisCoords.tr, thisCoords.br, thisCoords.bl], + [otherCoords.tl, otherCoords.tr, otherCoords.br, otherCoords.bl] + ); return intersection.status === 'Intersection'; }, @@ -15127,10 +15493,10 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati var boundingRect = this.getBoundingRect(); return ( - boundingRect.left > pointTL.x && - boundingRect.left + boundingRect.width < pointBR.x && - boundingRect.top > pointTL.y && - boundingRect.top + boundingRect.height < pointBR.y + boundingRect.left >= pointTL.x && + boundingRect.left + boundingRect.width <= pointBR.x && + boundingRect.top >= pointTL.y && + boundingRect.top + boundingRect.height <= pointBR.y ); }, @@ -15204,7 +15570,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati else { b1 = 0; b2 = (iLine.d.y - iLine.o.y) / (iLine.d.x - iLine.o.x); - a1 = point.y- b1 * point.x; + a1 = point.y - b1 * point.x; a2 = iLine.o.y - b2 * iLine.o.x; xi = - (a1 - a2) / (b1 - b2); @@ -15247,15 +15613,15 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati getBoundingRect: function() { this.oCoords || this.setCoords(); - var xCoords = [this.oCoords.tl.x, this.oCoords.tr.x, this.oCoords.br.x, this.oCoords.bl.x]; - var minX = fabric.util.array.min(xCoords); - var maxX = fabric.util.array.max(xCoords); - var width = Math.abs(minX - maxX); + var xCoords = [this.oCoords.tl.x, this.oCoords.tr.x, this.oCoords.br.x, this.oCoords.bl.x], + minX = fabric.util.array.min(xCoords), + maxX = fabric.util.array.max(xCoords), + width = Math.abs(minX - maxX), - var yCoords = [this.oCoords.tl.y, this.oCoords.tr.y, this.oCoords.br.y, this.oCoords.bl.y]; - var minY = fabric.util.array.min(yCoords); - var maxY = fabric.util.array.max(yCoords); - var height = Math.abs(minY - maxY); + yCoords = [this.oCoords.tl.y, this.oCoords.tr.y, this.oCoords.br.y, this.oCoords.bl.y], + minY = fabric.util.array.min(yCoords), + maxY = fabric.util.array.max(yCoords), + height = Math.abs(minY - maxY); return { left: minX, @@ -15289,12 +15655,13 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati */ _constrainScale: function(value) { if (Math.abs(value) < this.minScaleLimit) { - if (value < 0) + if (value < 0) { return -this.minScaleLimit; - else + } + else { return this.minScaleLimit; + } } - return value; }, @@ -15355,7 +15722,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati theta = degreesToRadians(this.angle), vpt; // TODO: ideally we should never setCoords an object which lacks a canvas - vpt = this.canvas ? this.canvas.viewportTransform : [1, 0, 0, 1, 0, 0]; + vpt = this.getViewportTransform(); var f = function (p) { return fabric.util.transformPoint(p, vpt); @@ -15370,13 +15737,13 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati } var _hypotenuse = Math.sqrt( - Math.pow(this.currentWidth / 2, 2) + - Math.pow(this.currentHeight / 2, 2)); + Math.pow(this.currentWidth / 2, 2) + + Math.pow(this.currentHeight / 2, 2)), - var _angle = Math.atan(isFinite(this.currentHeight / this.currentWidth) ? this.currentHeight / this.currentWidth : 0); + _angle = Math.atan(isFinite(this.currentHeight / this.currentWidth) ? this.currentHeight / this.currentWidth : 0), - // offset added for rotate and scale actions - var offsetX = Math.cos(_angle + theta) * _hypotenuse, + // offset added for rotate and scale actions + offsetX = Math.cos(_angle + theta) * _hypotenuse, offsetY = Math.sin(_angle + theta) * _hypotenuse, sinTh = Math.sin(theta), cosTh = Math.cos(theta), @@ -15533,32 +15900,32 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot getSvgStyles: function() { var fill = this.fill - ? (this.fill.toLive ? 'url(#SVGID_' + this.fill.id + ')' : this.fill) - : 'none'; + ? (this.fill.toLive ? 'url(#SVGID_' + this.fill.id + ')' : this.fill) + : 'none', - var stroke = this.stroke - ? (this.stroke.toLive ? 'url(#SVGID_' + this.stroke.id + ')' : this.stroke) - : 'none'; + stroke = this.stroke + ? (this.stroke.toLive ? 'url(#SVGID_' + this.stroke.id + ')' : this.stroke) + : 'none', - var strokeWidth = this.strokeWidth ? this.strokeWidth : '0'; - var strokeDashArray = this.strokeDashArray ? this.strokeDashArray.join(' ') : ''; - var strokeLineCap = this.strokeLineCap ? this.strokeLineCap : 'butt'; - var strokeLineJoin = this.strokeLineJoin ? this.strokeLineJoin : 'miter'; - var strokeMiterLimit = this.strokeMiterLimit ? this.strokeMiterLimit : '4'; - var opacity = typeof this.opacity !== 'undefined' ? this.opacity : '1'; + strokeWidth = this.strokeWidth ? this.strokeWidth : '0', + strokeDashArray = this.strokeDashArray ? this.strokeDashArray.join(' ') : '', + strokeLineCap = this.strokeLineCap ? this.strokeLineCap : 'butt', + strokeLineJoin = this.strokeLineJoin ? this.strokeLineJoin : 'miter', + strokeMiterLimit = this.strokeMiterLimit ? this.strokeMiterLimit : '4', + opacity = typeof this.opacity !== 'undefined' ? this.opacity : '1', - var visibility = this.visible ? '' : " visibility: hidden;"; - var filter = this.shadow && this.type !== 'text' ? 'filter: url(#SVGID_' + this.shadow.id + ');' : ''; + visibility = this.visible ? '' : ' visibility: hidden;', + filter = this.shadow && this.type !== 'text' ? 'filter: url(#SVGID_' + this.shadow.id + ');' : ''; return [ - "stroke: ", stroke, "; ", - "stroke-width: ", strokeWidth, "; ", - "stroke-dasharray: ", strokeDashArray, "; ", - "stroke-linecap: ", strokeLineCap, "; ", - "stroke-linejoin: ", strokeLineJoin, "; ", - "stroke-miterlimit: ", strokeMiterLimit, "; ", - "fill: ", fill, "; ", - "opacity: ", opacity, ";", + 'stroke: ', stroke, '; ', + 'stroke-width: ', strokeWidth, '; ', + 'stroke-dasharray: ', strokeDashArray, '; ', + 'stroke-linecap: ', strokeLineCap, '; ', + 'stroke-linejoin: ', strokeLineJoin, '; ', + 'stroke-miterlimit: ', strokeMiterLimit, '; ', + 'fill: ', fill, '; ', + 'opacity: ', opacity, ';', filter, visibility ].join(''); @@ -15569,34 +15936,37 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot * @return {String} */ getSvgTransform: function() { - var toFixed = fabric.util.toFixed; - var angle = this.getAngle(); - var center = this.getCenterPoint(); + var toFixed = fabric.util.toFixed, + angle = this.getAngle(), + center = this.getCenterPoint(), - var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS; + NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, - var translatePart = "translate(" + + translatePart = 'translate(' + toFixed(center.x, NUM_FRACTION_DIGITS) + - " " + + ' ' + toFixed(center.y, NUM_FRACTION_DIGITS) + - ")"; + ')', - var anglePart = angle !== 0 - ? (" rotate(" + toFixed(angle, NUM_FRACTION_DIGITS) + ")") - : ''; + anglePart = angle !== 0 + ? (' rotate(' + toFixed(angle, NUM_FRACTION_DIGITS) + ')') + : '', - var scalePart = (this.scaleX === 1 && this.scaleY === 1) - ? '' : - (" scale(" + - toFixed(this.scaleX, NUM_FRACTION_DIGITS) + - " " + - toFixed(this.scaleY, NUM_FRACTION_DIGITS) + - ")"); + scalePart = (this.scaleX === 1 && this.scaleY === 1) + ? '' : + (' scale(' + + toFixed(this.scaleX, NUM_FRACTION_DIGITS) + + ' ' + + toFixed(this.scaleY, NUM_FRACTION_DIGITS) + + ')'), - var flipXPart = this.flipX ? "matrix(-1 0 0 1 0 0) " : ""; - var flipYPart = this.flipY ? "matrix(1 0 0 -1 0 0)" : ""; + flipXPart = this.flipX ? 'matrix(-1 0 0 1 0 0) ' : '', - return [ translatePart, anglePart, scalePart, flipXPart, flipYPart ].join(''); + flipYPart = this.flipY ? 'matrix(1 0 0 -1 0 0)' : ''; + + return [ + translatePart, anglePart, scalePart, flipXPart, flipYPart + ].join(''); }, /** @@ -15669,8 +16039,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot (function(){ - var getPointer = fabric.util.getPointer, - degreesToRadians = fabric.util.degreesToRadians, + var degreesToRadians = fabric.util.degreesToRadians, isVML = typeof G_vmlCanvasManager !== 'undefined'; fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { @@ -15684,15 +16053,13 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot /** * Determines which corner has been clicked * @private - * @param {Event} e Event object - * @param {Object} offset Canvas offset + * @param {Object} pointer The pointer indicating the mouse position * @return {String|Boolean} corner code (tl, tr, bl, br, etc.), or false if nothing is found */ - _findTargetCorner: function(e, offset) { + _findTargetCorner: function(pointer) { if (!this.hasControls || !this.active) return false; - var pointer = this.canvas.getPointer(e, true), - ex = pointer.x, + var ex = pointer.x, ey = pointer.y, xPoints, lines; @@ -15707,7 +16074,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot continue; } - if (this.get('lockUniScaling') && (i === 'mt' || i === 'mr' || i === 'mb' || i === 'ml')) { + if (this.get('lockUniScaling') && + (i === 'mt' || i === 'mr' || i === 'mb' || i === 'ml')) { continue; } @@ -15727,7 +16095,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot // canvas.contextTop.fillRect(lines.rightline.d.x, lines.rightline.d.y, 2, 2); // canvas.contextTop.fillRect(lines.rightline.o.x, lines.rightline.o.y, 2, 2); - xPoints = this._findCrossPoints({x: ex, y: ey}, lines); + xPoints = this._findCrossPoints({ x: ex, y: ey }, lines); if (xPoints !== 0 && xPoints % 2 === 1) { this.__corner = i; return i; @@ -15947,7 +16315,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot ctx.lineWidth = 1 / this.borderScaleFactor; - var vpt = this.canvas.viewportTransform, + var vpt = this.getViewportTransform(), wh = fabric.util.transformPoint(new fabric.Point(this.getWidth(), this.getHeight()), vpt, true), sxy = fabric.util.transformPoint(new fabric.Point(scaleX, scaleY), vpt, true), w = wh.x, @@ -15999,7 +16367,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot var size = this.cornerSize, size2 = size / 2, strokeWidth2 = ~~(this.strokeWidth / 2), // half strokeWidth rounded down - wh = fabric.util.transformPoint(new fabric.Point(this.getWidth(), this.getHeight()), this.canvas.viewportTransform, true), + wh = fabric.util.transformPoint(new fabric.Point(this.getWidth(), this.getHeight()), this.getViewportTransform(), true), width = wh.x, height = wh.y, left = -(width / 2), @@ -16029,7 +16397,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot top - scaleOffset - strokeWidth2 - padding); // bottom-left - this._drawControl('tr', ctx, methodName, + this._drawControl('bl', ctx, methodName, left - scaleOffset - strokeWidth2 - padding, top + height + scaleOffsetSize + strokeWidth2 + padding); @@ -16051,7 +16419,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot top + height + scaleOffsetSize + strokeWidth2 + padding); // middle-right - this._drawControl('mb', ctx, methodName, + this._drawControl('mr', ctx, methodName, left + width + scaleOffsetSize + strokeWidth2 + padding, top + height/2 - scaleOffset); @@ -16140,15 +16508,15 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot _getControlsVisibility: function() { if (!this._controlsVisibility) { this._controlsVisibility = { - tl: true, - tr: true, - br: true, - bl: true, - ml: true, - mt: true, - mr: true, - mb: true, - mtr: true + tl: true, + tr: true, + br: true, + bl: true, + ml: true, + mt: true, + mr: true, + mb: true, + mtr: true }; } return this._controlsVisibility; @@ -16301,7 +16669,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot for (prop in arguments[0]) { propsToAnimate.push(prop); } - for (var i = 0, len = propsToAnimate.length; i' - ); + '"/>'); return reviver ? reviver(markup.join('')) : markup.join(''); }, @@ -17400,7 +17888,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), toFixed = fabric.util.toFixed; @@ -17425,6 +17913,13 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot */ type: 'polyline', + /** + * Points array + * @type Array + * @default + */ + points: null, + /** * Constructor * @param {Array} points Array of points (where each point is an object with x and y) @@ -17522,7 +18017,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot ctx.beginPath(); for (var i = 0, len = this.points.length; i < len; i++) { p1 = this.points[i]; - p2 = this.points[i+1] || p1; + p2 = this.points[i + 1] || p1; fabric.util.drawDashedLine(ctx, p1.x, p1.y, p2.x, p2.y, this.strokeDashArray); } }, @@ -17585,7 +18080,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend, @@ -17613,6 +18108,13 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot */ type: 'polygon', + /** + * Points array + * @type Array + * @default + */ + points: null, + /** * Constructor * @param {Array} points Array of points @@ -17723,7 +18225,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot ctx.beginPath(); for (var i = 0, len = this.points.length; i < len; i++) { p1 = this.points[i]; - p2 = this.points[i+1] || this.points[0]; + p2 = this.points[i + 1] || this.points[0]; fabric.util.drawDashedLine(ctx, p1.x, p1.y, p2.x, p2.y, this.strokeDashArray); } ctx.closePath(); @@ -17786,26 +18288,25 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot (function(global) { - var commandLengths = { - m: 2, - l: 2, - h: 1, - v: 1, - c: 6, - s: 4, - q: 4, - t: 2, - a: 7 - }; - - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), min = fabric.util.array.min, max = fabric.util.array.max, extend = fabric.util.object.extend, _toString = Object.prototype.toString, - drawArc = fabric.util.drawArc; + drawArc = fabric.util.drawArc, + commandLengths = { + m: 2, + l: 2, + h: 1, + v: 1, + c: 6, + s: 4, + q: 4, + t: 2, + a: 7 + }; if (fabric.Path) { fabric.warn('fabric.Path is already defined'); @@ -17848,6 +18349,13 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot */ type: 'path', + /** + * Array of path points + * @type Array + * @default + */ + path: null, + /** * Constructor * @param {Array|String} path Path data (sequence of coordinates and corresponding "command" tokens) @@ -17911,7 +18419,9 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot this.left = this.width / 2; } } - this.pathOffset = this.pathOffset || this._calculatePathOffset(origLeft, origTop); //Save top-left coords as offset + this.pathOffset = this.pathOffset || + // Save top-left coords as offset + this._calculatePathOffset(origLeft, origTop); }, /** @@ -18067,8 +18577,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot tempX = current[3]; tempY = current[4]; // calculate reflection of previous control points - controlX = 2*x - controlX; - controlY = 2*y - controlY; + controlX = 2 * x - controlX; + controlY = 2 * y - controlY; ctx.bezierCurveTo( controlX + l, controlY + t, @@ -18129,7 +18639,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot tempX = x + current[1]; tempY = y + current[2]; - if (previous[0].match(/[QqTt]/) === null) { // If there is no previous command or if the previous command was not a Q, q, T or t, // assume the control point is coincident with the current point @@ -18230,7 +18739,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot ctx.save(); var m = this.transformMatrix; - var v = this.canvas.viewportTransform; + var v = this.getViewportTransform(); ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); if (m) { @@ -18271,7 +18780,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot */ toObject: function(propertiesToInclude) { var o = extend(this.callSuper('toObject', propertiesToInclude), { - path: this.path, + path: this.path.map(function(item) { return item.slice() }), pathOffset: this.pathOffset }); if (this.sourcePath) { @@ -18343,7 +18852,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot coords = [ ], currentPath, parsed, - re = /(-?\.\d+)|(-?\d+(\.\d+)?)/g, + re = /([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/ig, match, coordsStr; @@ -18399,14 +18908,14 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot maxX = max(aX), maxY = max(aY), deltaX = maxX - minX, - deltaY = maxY - minY; + deltaY = maxY - minY, - var o = { - left: this.left + (minX + deltaX / 2), - top: this.top + (minY + deltaY / 2), - width: deltaX, - height: deltaY - }; + o = { + left: this.left + (minX + deltaX / 2), + top: this.top + (minY + deltaY / 2), + width: deltaX, + height: deltaY + }; return o; }, @@ -18427,13 +18936,18 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot isLowerCase = true; } - var xy = this._getXY(item, isLowerCase, previous); + var xy = this._getXY(item, isLowerCase, previous), + val; - var val = parseInt(xy.x, 10); - if (!isNaN(val)) aX.push(val); + val = parseInt(xy.x, 10); + if (!isNaN(val)) { + aX.push(val); + } val = parseInt(xy.y, 10); - if (!isNaN(val)) aY.push(val); + if (!isNaN(val)) { + aY.push(val); + } }, _getXY: function(item, isLowerCase, previous) { @@ -18445,13 +18959,13 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot ? previous.x + getX(item) : item[0] === 'V' ? previous.x - : getX(item); + : getX(item), - var y = isLowerCase - ? previous.y + getY(item) - : item[0] === 'H' - ? previous.y - : getY(item); + y = isLowerCase + ? previous.y + getY(item) + : item[0] === 'H' + ? previous.y + : getY(item); return { x: x, y: y }; } @@ -18467,9 +18981,9 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot fabric.Path.fromObject = function(object, callback) { if (typeof object.path === 'string') { fabric.loadSVGFromURL(object.path, function (elements) { - var path = elements[0]; + var path = elements[0], + pathUrl = object.path; - var pathUrl = object.path; delete object.path; fabric.util.object.extend(path, object); @@ -18520,7 +19034,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend, @@ -18572,6 +19086,15 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot this.setOptions(options); + if (options.widthAttr) { + this.scaleX = options.widthAttr / options.width; + } + if (options.heightAttr) { + this.scaleY = options.heightAttr / options.height; + } + + this.setCoords(); + if (options.sourcePath) { this.setSourcePath(options.sourcePath); } @@ -18589,7 +19112,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot var m = this.transformMatrix; - var v = this.canvas.viewportTransform; + var v = this.getViewportTransform(); ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); if (m) { @@ -18663,13 +19186,13 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot * @return {String} svg representation of an instance */ toSVG: function(reviver) { - var objects = this.getObjects(); - var markup = [ - '' - ]; + var objects = this.getObjects(), + markup = [ + '' + ]; for (var i = 0, len = objects.length; i < len; i++) { markup.push(objects[i].toSVG(reviver)); @@ -18694,9 +19217,9 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot * @return {Boolean} true if all paths are of the same color (`fill`) */ isSameColor: function() { - var firstPathFill = this.getObjects()[0].get('fill'); + var firstPathFill = (this.getObjects()[0].get('fill') || '').toLowerCase(); return this.getObjects().every(function(path) { - return path.get('fill') === firstPathFill; + return (path.get('fill') || '').toLowerCase() === firstPathFill; }); }, @@ -18760,7 +19283,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot (function(global){ - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend, @@ -18992,7 +19515,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot * @private */ _renderObject: function(object, ctx) { - var v = this.canvas.viewportTransform, + var v = this.getViewportTransform(), sxy = fabric.util.transformPoint( new fabric.Point(this.scaleX, this.scaleY), v, @@ -19169,11 +19692,10 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot */ _setOpacityIfSame: function() { var objects = this.getObjects(), - firstValue = objects[0] ? objects[0].get('opacity') : 1; - - var isSameOpacity = objects.every(function(o) { - return o.get('opacity') === firstValue; - }); + firstValue = objects[0] ? objects[0].get('opacity') : 1, + isSameOpacity = objects.every(function(o) { + return o.get('opacity') === firstValue; + }); if (isSameOpacity) { this.opacity = firstValue; @@ -19183,7 +19705,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot /** * @private */ - _calcBounds: function() { + _calcBounds: function(onlyWidthHeight) { var aX = [], aY = [], o; @@ -19197,26 +19719,29 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot } } - this.set(this._getBounds(aX, aY)); + this.set(this._getBounds(aX, aY, onlyWidthHeight)); }, /** * @private */ - _getBounds: function(aX, aY) { + _getBounds: function(aX, aY, onlyWidthHeight) { var ivt; if (this.canvas) { - ivt = fabric.util.invertTransform(this.canvas.viewportTransform); + ivt = fabric.util.invertTransform(this.getViewportTransform()); } var minXY = fabric.util.transformPoint(new fabric.Point(min(aX), min(aY)), ivt), - maxXY = fabric.util.transformPoint(new fabric.Point(max(aX), max(aY)), ivt); + maxXY = fabric.util.transformPoint(new fabric.Point(max(aX), max(aY)), ivt), + obj = { + width: (maxXY.x - minXY.x) || 0, + height: (maxXY.y - minXY.y) || 0 + }; - return { - width: (maxXY.x - minXY.x) || 0, - height: (maxXY.y - minXY.y) || 0, - left: (minXY.x + maxXY.x) / 2 || 0, - top: (minXY.y + maxXY.y) / 2 || 0, - }; + if (!onlyWidthHeight) { + obj.left = (minXY.x + maxXY.x) / 2 || 0; + obj.top = (minXY.y + maxXY.y) / 2 || 0; + } + return obj; }, /* _TO_SVG_START_ */ @@ -19299,7 +19824,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot (function(global) { - "use strict"; + 'use strict'; var extend = fabric.util.object.extend; @@ -19421,17 +19946,15 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot if (!this.visible) return; ctx.save(); - var m = this.transformMatrix; - var v; - v = this.canvas.viewportTransform; - - var isInPathGroup = this.group && this.group.type === 'path-group'; + var m = this.transformMatrix, + v = this.getViewportTransform(), + isInPathGroup = this.group && this.group.type === 'path-group'; ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); // this._resetWidthHeight(); if (isInPathGroup) { - ctx.translate(-this.group.width/2 + this.width/2, -this.group.height/2 + this.height/2); + ctx.translate(-this.group.width/2, -this.group.height/2); } if (m) { ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); @@ -19439,6 +19962,9 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot if (!noTransform) { this.transform(ctx); } + if (isInPathGroup) { + ctx.translate(this.width/2, this.height/2); + } ctx.save(); this._setShadow(ctx); @@ -19482,10 +20008,10 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot this._setStrokeStyles(ctx); ctx.beginPath(); - fabric.util.drawDashedLine(ctx, x, y, x+w, y, this.strokeDashArray); - fabric.util.drawDashedLine(ctx, x+w, y, x+w, y+h, this.strokeDashArray); - fabric.util.drawDashedLine(ctx, x+w, y+h, x, y+h, this.strokeDashArray); - fabric.util.drawDashedLine(ctx, x, y+h, x, y, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, x, y, x + w, y, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, x + w, y, x + w, y + h, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, x + w, y + h, x, y + h, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, x, y + h, x, y, this.strokeDashArray); ctx.closePath(); ctx.restore(); }, @@ -19552,7 +20078,9 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot * @return {String} Source of an image */ getSrc: function() { - return this.getElement().src || this.getElement()._src; + if (this.getElement()) { + return this.getElement().src || this.getElement()._src; + } }, /** @@ -19581,6 +20109,10 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot */ applyFilters: function(callback) { + if (!this._originalElement) { + return; + } + if (this.filters.length === 0) { this._element = this._originalElement; callback && callback(); @@ -19630,13 +20162,13 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot * @param {CanvasRenderingContext2D} ctx Context to render on */ _render: function(ctx) { - ctx.drawImage( - this._element, - -this.width / 2, - -this.height / 2, - this.width, - this.height - ); + this._element && ctx.drawImage( + this._element, + -this.width / 2, + -this.height / 2, + this.width, + this.height + ); }, /** @@ -19668,7 +20200,9 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot options || (options = { }); this.setOptions(options); this._setWidthHeight(options); - this._element.crossOrigin = this.crossOrigin; + if (this._element && this.crossOrigin) { + this._element.crossOrigin = this.crossOrigin; + } }, /** @@ -19694,11 +20228,15 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot _setWidthHeight: function(options) { this.width = 'width' in options ? options.width - : (this.getElement().width || 0); + : (this.getElement() + ? this.getElement().width || 0 + : 0); this.height = 'height' in options ? options.height - : (this.getElement().height || 0); + : (this.getElement() + ? this.getElement().height || 0 + : 0); }, /** @@ -19716,7 +20254,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot * @type String * @default */ - fabric.Image.CSS_CANVAS = "canvas-img"; + fabric.Image.CSS_CANVAS = 'canvas-img'; /** * Alias for getSrc @@ -19805,9 +20343,9 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot _getAngleValueForStraighten: function() { var angle = this.getAngle() % 360; if (angle > 0) { - return Math.round((angle-1)/90) * 90; + return Math.round((angle - 1) / 90) * 90; } - return Math.round(angle/90) * 90; + return Math.round(angle / 90) * 90; }, /** @@ -19894,7 +20432,6 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati */ fabric.Image.filters = fabric.Image.filters || { }; - /** * Root filter class from which all filter classes inherit from * @class fabric.Image.filters.BaseFilter @@ -19930,7 +20467,7 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend; @@ -19962,11 +20499,11 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag * Constructor * @memberOf fabric.Image.filters.Brightness.prototype * @param {Object} [options] Options object - * @param {Number} [options.brightness=100] Value to brighten the image up (0..255) + * @param {Number} [options.brightness=0] Value to brighten the image up (0..255) */ initialize: function(options) { options = options || { }; - this.brightness = options.brightness || 100; + this.brightness = options.brightness || 0; }, /** @@ -20014,7 +20551,7 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend; @@ -20080,9 +20617,11 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag options = options || { }; this.opaque = options.opaque; - this.matrix = options.matrix || [ 0, 0, 0, + this.matrix = options.matrix || [ + 0, 0, 0, 0, 1, 0, - 0, 0, 0 ]; + 0, 0, 0 + ]; var canvasEl = fabric.util.createCanvasElement(); this.tmpCtx = canvasEl.getContext('2d'); @@ -20109,49 +20648,49 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag halfSide = Math.floor(side/2), src = pixels.data, sw = pixels.width, - sh = pixels.height; + sh = pixels.height, - // pad output by the convolution matrix - var w = sw; - var h = sh; - var output = this._createImageData(w, h); + // pad output by the convolution matrix + w = sw, + h = sh, + output = this._createImageData(w, h), - var dst = output.data; + dst = output.data, - // go through the destination image pixels - var alphaFac = this.opaque ? 1 : 0; + // go through the destination image pixels + alphaFac = this.opaque ? 1 : 0; - for (var y=0; y sh || scx < 0 || scx > sw) continue; - var srcOff = (scy*sw+scx)*4; - var wt = weights[cy*side+cx]; + var srcOff = (scy * sw + scx) * 4, + wt = weights[cy * side + cx]; r += src[srcOff] * wt; - g += src[srcOff+1] * wt; - b += src[srcOff+2] * wt; - a += src[srcOff+3] * wt; + g += src[srcOff + 1] * wt; + b += src[srcOff + 2] * wt; + a += src[srcOff + 3] * wt; } } dst[dstOff] = r; - dst[dstOff+1] = g; - dst[dstOff+2] = b; - dst[dstOff+3] = a + alphaFac*(255-a); + dst[dstOff + 1] = g; + dst[dstOff + 2] = b; + dst[dstOff + 3] = a + alphaFac * (255 - a); } } @@ -20177,7 +20716,7 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag * @return {fabric.Image.filters.Convolute} Instance of fabric.Image.filters.Convolute */ fabric.Image.filters.Convolute.fromObject = function(object) { - return new fabric.Image.filters.Convolute(object); + return new fabric.Image.filters.Convolute(object); }; })(typeof exports !== 'undefined' ? exports : this); @@ -20185,7 +20724,7 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend; @@ -20268,7 +20807,7 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }); @@ -20331,7 +20870,7 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }); @@ -20390,7 +20929,7 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend; @@ -20495,7 +21034,7 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend; @@ -20527,11 +21066,11 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag * Constructor * @memberOf fabric.Image.filters.Noise.prototype * @param {Object} [options] Options object - * @param {Number} [options.noise=100] Noise value + * @param {Number} [options.noise=0] Noise value */ initialize: function(options) { options = options || { }; - this.noise = options.noise || 100; + this.noise = options.noise || 0; }, /** @@ -20582,7 +21121,7 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend; @@ -20639,9 +21178,9 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag index = (i * 4) * jLen + (j * 4); r = data[index]; - g = data[index+1]; - b = data[index+2]; - a = data[index+3]; + g = data[index + 1]; + b = data[index + 2]; + a = data[index + 3]; /* blocksize: 4 @@ -20694,7 +21233,7 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend; @@ -20752,17 +21291,17 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag for (var i = 0, len = data.length; i < len; i += 4) { r = data[i]; - g = data[i+1]; - b = data[i+2]; + g = data[i + 1]; + b = data[i + 2]; if (r > limit && g > limit && b > limit && - abs(r-g) < distance && - abs(r-b) < distance && - abs(g-b) < distance + abs(r - g) < distance && + abs(r - b) < distance && + abs(g - b) < distance ) { - data[i+3] = 1; + data[i + 3] = 1; } } @@ -20796,7 +21335,7 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }); @@ -20856,7 +21395,7 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }); @@ -20919,7 +21458,7 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend; @@ -21033,7 +21572,101 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag (function(global) { - "use strict"; + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend; + + /** + * Multiply filter class + * Adapted from http://www.laurenscorijn.com/articles/colormath-basics + * @class fabric.Image.filters.Multiply + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @example Multiply filter with hex color + * var filter = new fabric.Image.filters.Multiply({ + * color: '#F0F' + * }); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + * @example Multiply filter with rgb color + * var filter = new fabric.Image.filters.Multiply({ + * color: 'rgb(53, 21, 176)' + * }); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + */ + fabric.Image.filters.Multiply = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Multiply.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Multiply', + + /** + * Constructor + * @memberOf fabric.Image.filters.Multiply.prototype + * @param {Object} [options] Options object + * @param {String} [options.color=#000000] Color to multiply the image pixels with + */ + initialize: function(options) { + options = options || { }; + + this.color = options.color || '#000000'; + }, + + /** + * Applies filter to canvas element + * @param {Object} canvasEl Canvas element to apply filter to + */ + applyTo: function(canvasEl) { + var context = canvasEl.getContext('2d'), + imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), + data = imageData.data, + iLen = data.length, i, + source; + + source = new fabric.Color(this.color).getSource(); + + for (i = 0; i < iLen; i+=4) { + data[i] *= source[0]/255; + data[i + 1] *= source[1]/255; + data[i + 2] *= source[2]/255; + + } + + context.putImageData(imageData, 0, 0); + }, + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + return extend(this.callSuper('toObject'), { + color: this.color + }); + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @return {fabric.Image.filters.Multiply} Instance of fabric.Image.filters.Multiply + */ + fabric.Image.filters.Multiply.fromObject = function(object) { + return new fabric.Image.filters.Multiply(object); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend, @@ -21460,8 +22093,8 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag for (var i = 0, len = textLines.length; i < len; i++) { - var lineWidth = this._getLineWidth(ctx, textLines[i]); - var lineLeftOffset = this._getLineLeftOffset(lineWidth); + var lineWidth = this._getLineWidth(ctx, textLines[i]), + lineLeftOffset = this._getLineLeftOffset(lineWidth); this._boundaries.push({ height: this.fontSize * this.lineHeight, @@ -21544,18 +22177,18 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag return; } - var lineWidth = ctx.measureText(line).width; - var totalWidth = this.width; + var lineWidth = ctx.measureText(line).width, + totalWidth = this.width; if (totalWidth > lineWidth) { // stretch the line - var words = line.split(/\s+/); - var wordsWidth = ctx.measureText(line.replace(/\s+/g, '')).width; - var widthDiff = totalWidth - wordsWidth; - var numSpaces = words.length - 1; - var spaceWidth = widthDiff / numSpaces; + var words = line.split(/\s+/), + wordsWidth = ctx.measureText(line.replace(/\s+/g, '')).width, + widthDiff = totalWidth - wordsWidth, + numSpaces = words.length - 1, + spaceWidth = widthDiff / numSpaces, + leftOffset = 0; - var leftOffset = 0; for (var i = 0, len = words.length; i < len; i++) { this._renderChars(method, ctx, words[i], left + leftOffset, top, lineIndex); leftOffset += ctx.measureText(words[i]).width + spaceWidth; @@ -21697,8 +22330,8 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag if (textLines[i] !== '') { - var lineWidth = this._getLineWidth(ctx, textLines[i]); - var lineLeftOffset = this._getLineLeftOffset(lineWidth); + var lineWidth = this._getLineWidth(ctx, textLines[i]), + lineLeftOffset = this._getLineLeftOffset(lineWidth); ctx.fillRect( this._getLeftOffset() + lineLeftOffset, @@ -21747,15 +22380,15 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag if (!this.textDecoration) return; // var halfOfVerticalBox = this.originY === 'top' ? 0 : this._getTextHeight(ctx, textLines) / 2; - var halfOfVerticalBox = this._getTextHeight(ctx, textLines) / 2; - var _this = this; + var halfOfVerticalBox = this._getTextHeight(ctx, textLines) / 2, + _this = this; /** @ignore */ function renderLinesAtOffset(offset) { for (var i = 0, len = textLines.length; i < len; i++) { - var lineWidth = _this._getLineWidth(ctx, textLines[i]); - var lineLeftOffset = _this._getLineLeftOffset(lineWidth); + var lineWidth = _this._getLineWidth(ctx, textLines[i]), + lineLeftOffset = _this._getLineLeftOffset(lineWidth); ctx.fillRect( _this._getLeftOffset() + lineLeftOffset, @@ -21799,8 +22432,13 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag if (!this.visible) return; ctx.save(); - var v = this.canvas.viewportTransform; + var v = this.getViewportTransform(); ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + + var m = this.transformMatrix; + if (m && (!this.group || this.group.type === 'path-group')) { + ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + } this._render(ctx); ctx.restore(); @@ -21986,7 +22624,7 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag (i === 0 || this.useNative ? 'y' : 'dy'), '="', toFixed(this.useNative ? ((lineHeight * i) - this.height / 2) - : (lineHeight * lineTopOffsetMultiplier), 2) , '" ', + : (lineHeight * lineTopOffsetMultiplier), 2), '" ', // doing this on elements since setting opacity // on containing one doesn't work in Illustrator this._getFillAttributes(this.fill), '>', @@ -22081,7 +22719,14 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag * @see: http://www.w3.org/TR/SVG/text.html#TextElement */ fabric.Text.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat( - 'x y font-family font-style font-weight font-size text-decoration'.split(' ')); + 'x y dx dy font-family font-style font-weight font-size text-decoration text-anchor'.split(' ')); + + /** + * Default SVG font size + * @static + * @memberOf fabric.Text + */ + fabric.Text.DEFAULT_SVG_FONT_SIZE = 16; /** * Returns fabric.Text instance from an SVG element (not yet implemented) @@ -22099,6 +22744,20 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag var parsedAttributes = fabric.parseAttributes(element, fabric.Text.ATTRIBUTE_NAMES); options = fabric.util.object.extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes); + if ('dx' in parsedAttributes) { + options.left += parsedAttributes.dx; + } + if ('dy' in parsedAttributes) { + options.top += parsedAttributes.dy; + } + if (!('fontSize' in options)) { + options.fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE; + } + + if (!options.originX) { + options.originX = 'center'; + } + var text = new fabric.Text(element.textContent, options); /* @@ -22221,9 +22880,9 @@ fabric.util.object.extend(fabric.Text.prototype, { * @extends fabric.Text * @mixes fabric.Observable * - * @fires text:changed - * @fires editing:entered - * @fires editing:exited + * @fires changed ("text:changed" when observing canvas) + * @fires editing:entered ("text:editing:entered" when observing canvas) + * @fires editing:exited ("text:editing:exited" when observing canvas) * * @return {fabric.IText} thisArg * @see {@link fabric.IText#initialize} for constructor definition @@ -22246,6 +22905,7 @@ fabric.util.object.extend(fabric.Text.prototype, { * Copy text: ctrl/cmd + c * Paste text: ctrl/cmd + v * Cut text: ctrl/cmd + x + * Select entire text: ctrl/cmd + a * * *

Supported mouse/touch combination

@@ -22430,6 +23090,9 @@ fabric.util.object.extend(fabric.Text.prototype, { * @param {Number} index Index to set selection start to */ setSelectionStart: function(index) { + if (this.selectionStart !== index) { + this.canvas && this.canvas.fire('text:selection:changed', { target: this }); + } this.selectionStart = index; this.hiddenTextarea && (this.hiddenTextarea.selectionStart = index); }, @@ -22439,6 +23102,9 @@ fabric.util.object.extend(fabric.Text.prototype, { * @param {Number} index Index to set selection end to */ setSelectionEnd: function(index) { + if (this.selectionEnd !== index) { + this.canvas && this.canvas.fire('text:selection:changed', { target: this }); + } this.selectionEnd = index; this.hiddenTextarea && (this.hiddenTextarea.selectionEnd = index); }, @@ -22502,9 +23168,9 @@ fabric.util.object.extend(fabric.Text.prototype, { }, /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ _render: function(ctx) { this.callSuper('_render', ctx); this.ctx = ctx; @@ -22538,8 +23204,8 @@ fabric.util.object.extend(fabric.Text.prototype, { if (typeof selectionStart === 'undefined') { selectionStart = this.selectionStart; } - var textBeforeCursor = this.text.slice(0, selectionStart); - var linesBeforeCursor = textBeforeCursor.split(this._reNewline); + var textBeforeCursor = this.text.slice(0, selectionStart), + linesBeforeCursor = textBeforeCursor.split(this._reNewline); return { lineIndex: linesBeforeCursor.length - 1, @@ -22547,6 +23213,26 @@ fabric.util.object.extend(fabric.Text.prototype, { }; }, + /** + * Returns complete style of char at the current cursor + * @param {Number} lineIndex Line index + * @param {Number} charIndex Char index + * @return {Object} Character style + */ + getCurrentCharStyle: function(lineIndex, charIndex) { + var style = this.styles[lineIndex] && this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)]; + + return { + fontSize: style && style.fontSize || this.fontSize, + fill: style && style.fill || this.fill, + textBackgroundColor: style && style.textBackgroundColor || this.textBackgroundColor, + textDecoration: style && style.textDecoration || this.textDecoration, + fontFamily: style && style.fontFamily || this.fontFamily, + stroke: style && style.stroke || this.stroke, + strokeWidth: style && style.strokeWidth || this.strokeWidth + }; + }, + /** * Returns fontSize of char at the current cursor * @param {Number} lineIndex Line index @@ -22693,13 +23379,16 @@ fabric.util.object.extend(fabric.Text.prototype, { var cursorLocation = this.get2DCursorLocation(), lineIndex = cursorLocation.lineIndex, charIndex = cursorLocation.charIndex, - charHeight = this.getCurrentCharFontSize(lineIndex, charIndex); + charHeight = this.getCurrentCharFontSize(lineIndex, charIndex), + leftOffset = (lineIndex === 0 && charIndex === 0) + ? this._getCachedLineOffset(lineIndex, this.text.split(this._reNewline)) + : boundaries.leftOffset; ctx.fillStyle = this.getCurrentCharColor(lineIndex, charIndex); - ctx.globalAlpha = this._currentCursorOpacity; + ctx.globalAlpha = this.__isMousedown ? 1 : this._currentCursorOpacity; ctx.fillRect( - boundaries.left + boundaries.leftOffset, + boundaries.left + leftOffset, boundaries.top + boundaries.topOffset, this.cursorWidth / this.scaleX, charHeight); @@ -22719,39 +23408,43 @@ fabric.util.object.extend(fabric.Text.prototype, { ctx.fillStyle = this.selectionColor; - var cursorLocation = this.get2DCursorLocation(), - lineIndex = cursorLocation.lineIndex, - charIndex = cursorLocation.charIndex, - textLines = this.text.split(this._reNewline), - origLineIndex = lineIndex; + var start = this.get2DCursorLocation(this.selectionStart), + end = this.get2DCursorLocation(this.selectionEnd), + startLine = start.lineIndex, + endLine = end.lineIndex, + textLines = this.text.split(this._reNewline); - for (var i = this.selectionStart; i < this.selectionEnd; i++) { + for (var i = startLine; i <= endLine; i++) { + var lineOffset = this._getCachedLineOffset(i, textLines) || 0, + lineHeight = this._getCachedLineHeight(i), + boxWidth = 0; - if (chars[i] === '\n') { - boundaries.leftOffset = 0; - boundaries.topOffset += this._getHeightOfLine(ctx, lineIndex); - lineIndex++; - charIndex = 0; - } - else if (i !== this.text.length) { - - var charWidth = this._getWidthOfChar(ctx, chars[i], lineIndex, charIndex), - lineOffset = this._getLineLeftOffset(this._getWidthOfLine(ctx, lineIndex, textLines)) || 0; - - if (lineIndex === origLineIndex) { - // only offset the line if we're rendering selection of 2nd, 3rd, etc. line - lineOffset = 0; + if (i === startLine) { + for (var j = 0, len = textLines[i].length; j < len; j++) { + if (j >= start.charIndex && (i !== endLine || j < end.charIndex)) { + boxWidth += this._getWidthOfChar(ctx, textLines[i][j], i, j); + } + if (j < start.charIndex) { + lineOffset += this._getWidthOfChar(ctx, textLines[i][j], i, j); + } } - - ctx.fillRect( - boundaries.left + boundaries.leftOffset + lineOffset, - boundaries.top + boundaries.topOffset, - charWidth, - this._getHeightOfLine(ctx, lineIndex)); - - boundaries.leftOffset += charWidth; - charIndex++; } + else if (i > startLine && i < endLine) { + boxWidth += this._getCachedLineWidth(i, textLines) || 5; + } + else if (i === endLine) { + for (var j2 = 0, j2len = end.charIndex; j2 < j2len; j2++) { + boxWidth += this._getWidthOfChar(ctx, textLines[i][j2], i, j2); + } + } + + ctx.fillRect( + boundaries.left + lineOffset, + boundaries.top + boundaries.topOffset, + boxWidth, + lineHeight); + + boundaries.topOffset += lineHeight; } ctx.restore(); }, @@ -22781,14 +23474,26 @@ fabric.util.object.extend(fabric.Text.prototype, { lineWidth = this._getWidthOfLine(ctx, lineIndex, textLines), lineHeight = this._getHeightOfLine(ctx, lineIndex, textLines), lineLeftOffset = this._getLineLeftOffset(lineWidth), - chars = line.split(''); + chars = line.split(''), + prevStyle, + charsToRender = ''; left += lineLeftOffset || 0; ctx.save(); - for (var i = 0, len = chars.length; i < len; i++) { - this._renderChar(method, ctx, lineIndex, i, chars[i], left, top, lineHeight); + + for (var i = 0, len = chars.length; i <= len; i++) { + prevStyle = prevStyle || this.getCurrentCharStyle(lineIndex, i); + var thisStyle = this.getCurrentCharStyle(lineIndex, i + 1); + + if (this._hasStyleChanged(prevStyle, thisStyle) || i === len) { + this._renderChar(method, ctx, lineIndex, i - 1, charsToRender, left, top, lineHeight); + charsToRender = ''; + prevStyle = thisStyle; + } + charsToRender += chars[i]; } + ctx.restore(); }, @@ -22851,6 +23556,22 @@ fabric.util.object.extend(fabric.Text.prototype, { } }, + /** + * @private + * @param {Object} prevStyle + * @param {Object} thisStyle + */ + _hasStyleChanged: function(prevStyle, thisStyle) { + return (prevStyle.fill !== thisStyle.fill || + prevStyle.fontSize !== thisStyle.fontSize || + prevStyle.textBackgroundColor !== thisStyle.textBackgroundColor || + prevStyle.textDecoration !== thisStyle.textDecoration || + prevStyle.fontFamily !== thisStyle.fontFamily || + prevStyle.stroke !== thisStyle.stroke || + prevStyle.strokeWidth !== thisStyle.strokeWidth + ); + }, + /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on @@ -22858,10 +23579,10 @@ fabric.util.object.extend(fabric.Text.prototype, { _renderCharDecoration: function(ctx, styleDeclaration, left, top, charWidth, lineHeight, charHeight) { var textDecoration = styleDeclaration - ? (styleDeclaration.textDecoration || this.textDecoration) - : this.textDecoration; + ? (styleDeclaration.textDecoration || this.textDecoration) + : this.textDecoration, - var fontSize = (styleDeclaration ? styleDeclaration.fontSize : null) || this.fontSize; + fontSize = (styleDeclaration ? styleDeclaration.fontSize : null) || this.fontSize; if (!textDecoration) return; @@ -23081,15 +23802,35 @@ fabric.util.object.extend(fabric.Text.prototype, { } }, + /** + * @private + * @param {Number} lineIndex + * @param {Number} charIndex + */ + _getStyleDeclaration: function(lineIndex, charIndex) { + return (this.styles[lineIndex] && this.styles[lineIndex][charIndex]) + ? clone(this.styles[lineIndex][charIndex]) + : { }; + }, + /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _getWidthOfChar: function(ctx, _char, lineIndex, charIndex) { - ctx.save(); - var width = this._applyCharStylesGetWidth(ctx, _char, lineIndex, charIndex); - ctx.restore(); - return width; + var styleDeclaration = this._getStyleDeclaration(lineIndex, charIndex); + this._applyFontStyles(styleDeclaration); + var cacheProp = this._getCacheProp(_char, styleDeclaration); + + if (this._charWidthsCache[cacheProp] && this.caching) { + return this._charWidthsCache[cacheProp]; + } + else if (ctx) { + ctx.save(); + var width = this._applyCharStylesGetWidth(ctx, _char, lineIndex, charIndex); + ctx.restore(); + return width; + } }, /** @@ -23175,10 +23916,9 @@ fabric.util.object.extend(fabric.Text.prototype, { textLines = textLines || this.text.split(this._reNewline); - var maxHeight = this._getHeightOfChar(ctx, textLines[lineIndex][0], lineIndex, 0); - - var line = textLines[lineIndex]; - var chars = line.split(''); + var maxHeight = this._getHeightOfChar(ctx, textLines[lineIndex][0], lineIndex, 0), + line = textLines[lineIndex], + chars = line.split(''); for (var i = 1, len = chars.length; i < len; i++) { var currentCharHeight = this._getHeightOfChar(ctx, chars[i], lineIndex, i); @@ -23211,6 +23951,26 @@ fabric.util.object.extend(fabric.Text.prototype, { return topOffset - (this.fontSize / this._fontSizeFraction); }, + /** + * @private + * This method is overwritten to account for different top offset + */ + _renderTextBoxBackground: function(ctx) { + if (!this.backgroundColor) return; + + ctx.save(); + ctx.fillStyle = this.backgroundColor; + + ctx.fillRect( + this._getLeftOffset(), + this._getTopOffset() + (this.fontSize / this._fontSizeFraction), + this.width, + this.height + ); + + ctx.restore(); + }, + /** * Returns object representation of an instance * @methd toObject @@ -23235,6 +23995,12 @@ fabric.util.object.extend(fabric.Text.prototype, { return new fabric.IText(object.text, clone(object)); }; + /** + * Contains all fabric.IText objects that have been created + * @static + * @memberof fabric.IText + * @type Array + */ fabric.IText.instances = [ ]; })(); @@ -23250,10 +24016,9 @@ fabric.util.object.extend(fabric.Text.prototype, { * Initializes all the interactive behavior of IText */ initBehavior: function() { - this.initKeyHandlers(); + this.initAddedHandler(); this.initCursorSelectionHandlers(); this.initDoubleClickSimulation(); - this.initHiddenTextarea(); }, /** @@ -23266,10 +24031,17 @@ fabric.util.object.extend(fabric.Text.prototype, { setTimeout(function() { _this.selected = true; }, 100); + }); + }, - if (!this._hasCanvasHandlers) { + /** + * Initializes "added" event handler + */ + initAddedHandler: function() { + this.on('added', function() { + if (this.canvas && !this.canvas._hasITextHandlers) { + this.canvas._hasITextHandlers = true; this._initCanvasHandlers(); - this._hasCanvasHandlers = true; } }); }, @@ -23278,25 +24050,18 @@ fabric.util.object.extend(fabric.Text.prototype, { * @private */ _initCanvasHandlers: function() { - var _this = this; - - this.canvas.on('selection:cleared', function(options) { - - // do not exit editing if event fired - // when clicking on an object again (in editing mode) - if (options.e && _this.canvas.containsPoint(options.e, _this)) return; - - _this.exitEditing(); + this.canvas.on('selection:cleared', function() { + fabric.IText.prototype.exitEditingOnOthers.call(); }); this.canvas.on('mouse:up', function() { - this.getObjects('i-text').forEach(function(obj) { + fabric.IText.instances.forEach(function(obj) { obj.__isMousedown = false; }); }); - this.canvas.on('object:selected', function() { - fabric.IText.prototype.exitEditingOnOthers.call(this); + this.canvas.on('object:selected', function(options) { + fabric.IText.prototype.exitEditingOnOthers.call(options.target); }); }, @@ -23356,15 +24121,23 @@ fabric.util.object.extend(fabric.Text.prototype, { /** * Initializes delayed cursor */ - initDelayedCursor: function() { - var _this = this; + initDelayedCursor: function(restart) { + var _this = this, + delay = restart ? 0 : this.cursorDelay; + + if (restart) { + this._abortCursorAnimation = true; + clearTimeout(this._cursorTimeout1); + this._currentCursorOpacity = 1; + this.canvas && this.canvas.renderAll(); + } if (this._cursorTimeout2) { clearTimeout(this._cursorTimeout2); } this._cursorTimeout2 = setTimeout(function() { _this._abortCursorAnimation = false; _this._tick(); - }, this.cursorDelay); + }, delay); }, /** @@ -23391,6 +24164,7 @@ fabric.util.object.extend(fabric.Text.prototype, { selectAll: function() { this.selectionStart = 0; this.selectionEnd = this.text.length; + this.canvas && this.canvas.fire('text:selection:changed', { target: this }); }, /** @@ -23482,8 +24256,9 @@ fabric.util.object.extend(fabric.Text.prototype, { * @return {Number} Number of newlines in selected text */ getNumNewLinesInSelectedText: function() { - var selectedText = this.getSelectedText(); - var numNewLines = 0; + var selectedText = this.getSelectedText(), + numNewLines = 0; + for (var i = 0, chars = selectedText.split(''), len = chars.length; i < len; i++) { if (chars[i] === '\n') { numNewLines++; @@ -23498,9 +24273,9 @@ fabric.util.object.extend(fabric.Text.prototype, { * @param {Number} direction: 1 or -1 */ searchWordBoundary: function(selectionStart, direction) { - var index = selectionStart; - var _char = this.text.charAt(index); - var reNonWord = /[ \n\.,;!\?\-]/; + var index = this._reSpace.test(this.text.charAt(selectionStart)) ? selectionStart - 1 : selectionStart, + _char = this.text.charAt(index), + reNonWord = /[ \n\.,;!\?\-]/; while (!reNonWord.test(_char) && index > 0 && index < this.text.length) { index += direction; @@ -23517,11 +24292,12 @@ fabric.util.object.extend(fabric.Text.prototype, { * @param {Number} selectionStart Index of a character */ selectWord: function(selectionStart) { - var newSelectionStart = this.searchWordBoundary(selectionStart, -1); /* search backwards */ - var newSelectionEnd = this.searchWordBoundary(selectionStart, 1); /* search forward */ + var newSelectionStart = this.searchWordBoundary(selectionStart, -1), /* search backwards */ + newSelectionEnd = this.searchWordBoundary(selectionStart, 1); /* search forward */ this.setSelectionStart(newSelectionStart); this.setSelectionEnd(newSelectionEnd); + this.initDelayedCursor(true); }, /** @@ -23529,11 +24305,12 @@ fabric.util.object.extend(fabric.Text.prototype, { * @param {Number} selectionStart Index of a character */ selectLine: function(selectionStart) { - var newSelectionStart = this.findLineBoundaryLeft(selectionStart); - var newSelectionEnd = this.findLineBoundaryRight(selectionStart); + var newSelectionStart = this.findLineBoundaryLeft(selectionStart), + newSelectionEnd = this.findLineBoundaryRight(selectionStart); this.setSelectionStart(newSelectionStart); this.setSelectionEnd(newSelectionEnd); + this.initDelayedCursor(true); }, /** @@ -23548,6 +24325,7 @@ fabric.util.object.extend(fabric.Text.prototype, { this.isEditing = true; + this.initHiddenTextarea(); this._updateTextarea(); this._saveEditingProps(); this._setEditingProps(); @@ -23556,14 +24334,17 @@ fabric.util.object.extend(fabric.Text.prototype, { this.canvas && this.canvas.renderAll(); this.fire('editing:entered'); + this.canvas && this.canvas.fire('text:editing:entered', { target: this }); return this; }, exitEditingOnOthers: function() { fabric.IText.instances.forEach(function(obj) { - if (obj === this) return; - obj.exitEditing(); + obj.selected = false; + if (obj.isEditing) { + obj.exitEditing(); + } }, this); }, @@ -23591,7 +24372,6 @@ fabric.util.object.extend(fabric.Text.prototype, { this.hiddenTextarea.value = this.text; this.hiddenTextarea.selectionStart = this.selectionStart; - this.hiddenTextarea.focus(); }, /** @@ -23639,13 +24419,15 @@ fabric.util.object.extend(fabric.Text.prototype, { this.selectable = true; this.selectionEnd = this.selectionStart; - this.hiddenTextarea && this.hiddenTextarea.blur(); + this.hiddenTextarea && this.canvas && this.hiddenTextarea.parentNode.removeChild(this.hiddenTextarea); + this.hiddenTextarea = null; this.abortCursorAnimation(); this._restoreEditingProps(); this._currentCursorOpacity = 0; this.fire('editing:exited'); + this.canvas && this.canvas.fire('text:editing:exited', { target: this }); return this; }, @@ -23672,8 +24454,9 @@ fabric.util.object.extend(fabric.Text.prototype, { var prevIndex = this.get2DCursorLocation(i).charIndex; i--; - var index = this.get2DCursorLocation(i).charIndex; - var isNewline = index > prevIndex; + + var index = this.get2DCursorLocation(i).charIndex, + isNewline = index > prevIndex; if (isNewline) { this.removeStyleObject(isNewline, i + 1); @@ -23702,10 +24485,10 @@ fabric.util.object.extend(fabric.Text.prototype, { if (this.selectionStart === this.selectionEnd) { this.insertStyleObjects(_chars, isEndOfLine, this.copiedStyles); } - else if (this.selectionEnd - this.selectionStart > 1) { + // else if (this.selectionEnd - this.selectionStart > 1) { // TODO: replace styles properly - console.log('replacing MORE than 1 char'); - } + // console.log('replacing MORE than 1 char'); + // } this.selectionStart += _chars.length; this.selectionEnd = this.selectionStart; @@ -23717,7 +24500,8 @@ fabric.util.object.extend(fabric.Text.prototype, { } this.setCoords(); - this.fire('text:changed'); + this.fire('changed'); + this.canvas && this.canvas.fire('text:changed', { target: this }); }, /** @@ -23864,7 +24648,9 @@ fabric.util.object.extend(fabric.Text.prototype, { var textLines = this.text.split(this._reNewline), textOnPreviousLine = textLines[lineIndex - 1], - newCharIndexOnPrevLine = textOnPreviousLine.length; + newCharIndexOnPrevLine = textOnPreviousLine + ? textOnPreviousLine.length + : 0; if (!this.styles[lineIndex - 1]) { this.styles[lineIndex - 1] = { }; @@ -23921,7 +24707,7 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot // for triple click this.__lastLastClickTime = +new Date(); - this.lastPointer = { }; + this.__lastPointer = { }; this.on('mousedown', this.onMouseDown.bind(this)); }, @@ -23943,12 +24729,13 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot this.__lastLastClickTime = this.__lastClickTime; this.__lastClickTime = this.__newClickTime; this.__lastPointer = newPointer; + this.__lastIsEditing = this.isEditing; }, isDoubleClick: function(newPointer) { return this.__newClickTime - this.__lastClickTime < 500 && this.__lastPointer.x === newPointer.x && - this.__lastPointer.y === newPointer.y; + this.__lastPointer.y === newPointer.y && this.__lastIsEditing; }, isTripleClick: function(newPointer) { @@ -24005,9 +24792,13 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot this.canvas.wrapperEl.appendChild(this.hiddenTextarea); } - if (this.isEditing) { + if (this.selected) { this.setCursorByClick(options.e); + } + + if (this.isEditing) { this.__selectionStartOnMouseDown = this.selectionStart; + this.initDelayedCursor(true); } }); }, @@ -24048,12 +24839,13 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot initMouseupHandler: function() { this.on('mouseup', function(options) { this.__isMousedown = false; - if (this._isObjectMoved(options.e)) return; if (this.selected) { this.enterEditing(); + this.initDelayedCursor(true); } + this.selected = true; }); }, @@ -24115,10 +24907,10 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot height += this._getHeightOfLine(this.ctx, i) * this.scaleY; - var widthOfLine = this._getWidthOfLine(this.ctx, i, textLines); - var lineLeftOffset = this._getLineLeftOffset(widthOfLine); + var widthOfLine = this._getWidthOfLine(this.ctx, i, textLines), + lineLeftOffset = this._getLineLeftOffset(widthOfLine); - width = lineLeftOffset; + width = lineLeftOffset * this.scaleX; if (this.flipX) { // when oject is horizontally flipped we reverse chars @@ -24141,6 +24933,11 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot return this._getNewSelectionStartFromOffset( mouseOffset, prevWidth, width, charIndex + i, jlen); } + + if (mouseOffset.y < height) { + return this._getNewSelectionStartFromOffset( + mouseOffset, prevWidth, width, charIndex + i, jlen, j); + } } // clicked somewhere after all chars, so set at the end @@ -24152,7 +24949,7 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot /** * @private */ - _getNewSelectionStartFromOffset: function(mouseOffset, prevWidth, width, index, jlen) { + _getNewSelectionStartFromOffset: function(mouseOffset, prevWidth, width, index, jlen, j) { var distanceBtwLastCharAndCursor = mouseOffset.x - prevWidth, distanceBtwNextCharAndCursor = width - mouseOffset.x, @@ -24168,6 +24965,10 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot newSelectionStart = this.text.length; } + if (j === jlen) { + newSelectionStart--; + } + return newSelectionStart; } }); @@ -24175,14 +24976,6 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ { - /** - * Initializes key handlers - */ - initKeyHandlers: function() { - fabric.util.addListener(fabric.document, 'keydown', this.onKeyDown.bind(this)); - fabric.util.addListener(fabric.document, 'keypress', this.onKeyPress.bind(this)); - }, - /** * Initializes hidden textarea (needed to bring up keyboard in iOS) */ @@ -24193,6 +24986,14 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot this.hiddenTextarea.style.cssText = 'position: absolute; top: 0; left: -9999px'; fabric.document.body.appendChild(this.hiddenTextarea); + + fabric.util.addListener(this.hiddenTextarea, 'keydown', this.onKeyDown.bind(this)); + fabric.util.addListener(this.hiddenTextarea, 'keypress', this.onKeyPress.bind(this)); + + if (!this._clickHandlerInitialized && this.canvas) { + fabric.util.addListener(this.canvas.upperCanvasEl, 'click', this.onClick.bind(this)); + this._clickHandlerInitialized = true; + } }, /** @@ -24218,6 +25019,11 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot 88: 'cut' }, + onClick: function() { + // No need to trigger click event here, focus is enough to have the keyboard appear on Android + this.hiddenTextarea && this.hiddenTextarea.focus(); + }, + /** * Handles keyup event * @param {Event} e Event object @@ -24324,8 +25130,8 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot var widthOfSameLineBeforeCursor = this._getWidthOfLine(this.ctx, cursorLocation.lineIndex, textLines); lineLeftOffset = this._getLineLeftOffset(widthOfSameLineBeforeCursor); - var widthOfCharsOnSameLineBeforeCursor = lineLeftOffset; - var lineIndex = cursorLocation.lineIndex; + var widthOfCharsOnSameLineBeforeCursor = lineLeftOffset, + lineIndex = cursorLocation.lineIndex; for (var i = 0, len = textOnSameLineBeforeCursor.length; i < len; i++) { _char = textOnSameLineBeforeCursor[i]; @@ -24343,17 +25149,17 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot */ _getIndexOnNextLine: function(cursorLocation, textOnNextLine, widthOfCharsOnSameLineBeforeCursor, textLines) { - var lineIndex = cursorLocation.lineIndex + 1; - var widthOfNextLine = this._getWidthOfLine(this.ctx, lineIndex, textLines); - var lineLeftOffset = this._getLineLeftOffset(widthOfNextLine); - var widthOfCharsOnNextLine = lineLeftOffset; - var indexOnNextLine = 0; - var foundMatch; + var lineIndex = cursorLocation.lineIndex + 1, + widthOfNextLine = this._getWidthOfLine(this.ctx, lineIndex, textLines), + lineLeftOffset = this._getLineLeftOffset(widthOfNextLine), + widthOfCharsOnNextLine = lineLeftOffset, + indexOnNextLine = 0, + foundMatch; for (var j = 0, jlen = textOnNextLine.length; j < jlen; j++) { - var _char = textOnNextLine[j]; - var widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j); + var _char = textOnNextLine[j], + widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j); widthOfCharsOnNextLine += widthOfChar; @@ -24361,10 +25167,10 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot foundMatch = true; - var leftEdge = widthOfCharsOnNextLine - widthOfChar; - var rightEdge = widthOfCharsOnNextLine; - var offsetFromLeftEdge = Math.abs(leftEdge - widthOfCharsOnSameLineBeforeCursor); - var offsetFromRightEdge = Math.abs(rightEdge - widthOfCharsOnSameLineBeforeCursor); + var leftEdge = widthOfCharsOnNextLine - widthOfChar, + rightEdge = widthOfCharsOnNextLine, + offsetFromLeftEdge = Math.abs(leftEdge - widthOfCharsOnSameLineBeforeCursor), + offsetFromRightEdge = Math.abs(rightEdge - widthOfCharsOnSameLineBeforeCursor); indexOnNextLine = offsetFromRightEdge < offsetFromLeftEdge ? j + 1 : j; @@ -24452,13 +25258,10 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot textOnPreviousLine = (textBeforeCursor.match(/\n?(.*)\n.*$/) || {})[1] || '', textLines = this.text.split(this._reNewline), _char, - lineLeftOffset; - - var widthOfSameLineBeforeCursor = this._getWidthOfLine(this.ctx, cursorLocation.lineIndex, textLines); - lineLeftOffset = this._getLineLeftOffset(widthOfSameLineBeforeCursor); - - var widthOfCharsOnSameLineBeforeCursor = lineLeftOffset; - var lineIndex = cursorLocation.lineIndex; + widthOfSameLineBeforeCursor = this._getWidthOfLine(this.ctx, cursorLocation.lineIndex, textLines), + lineLeftOffset = this._getLineLeftOffset(widthOfSameLineBeforeCursor), + widthOfCharsOnSameLineBeforeCursor = lineLeftOffset, + lineIndex = cursorLocation.lineIndex; for (var i = 0, len = textOnSameLineBeforeCursor.length; i < len; i++) { _char = textOnSameLineBeforeCursor[i]; @@ -24476,17 +25279,17 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot */ _getIndexOnPrevLine: function(cursorLocation, textOnPreviousLine, widthOfCharsOnSameLineBeforeCursor, textLines) { - var lineIndex = cursorLocation.lineIndex - 1; - var widthOfPreviousLine = this._getWidthOfLine(this.ctx, lineIndex, textLines); - var lineLeftOffset = this._getLineLeftOffset(widthOfPreviousLine); - var widthOfCharsOnPreviousLine = lineLeftOffset; - var indexOnPrevLine = 0; - var foundMatch; + var lineIndex = cursorLocation.lineIndex - 1, + widthOfPreviousLine = this._getWidthOfLine(this.ctx, lineIndex, textLines), + lineLeftOffset = this._getLineLeftOffset(widthOfPreviousLine), + widthOfCharsOnPreviousLine = lineLeftOffset, + indexOnPrevLine = 0, + foundMatch; for (var j = 0, jlen = textOnPreviousLine.length; j < jlen; j++) { - var _char = textOnPreviousLine[j]; - var widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j); + var _char = textOnPreviousLine[j], + widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j); widthOfCharsOnPreviousLine += widthOfChar; @@ -24494,10 +25297,10 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot foundMatch = true; - var leftEdge = widthOfCharsOnPreviousLine - widthOfChar; - var rightEdge = widthOfCharsOnPreviousLine; - var offsetFromLeftEdge = Math.abs(leftEdge - widthOfCharsOnSameLineBeforeCursor); - var offsetFromRightEdge = Math.abs(rightEdge - widthOfCharsOnSameLineBeforeCursor); + var leftEdge = widthOfCharsOnPreviousLine - widthOfChar, + rightEdge = widthOfCharsOnPreviousLine, + offsetFromLeftEdge = Math.abs(leftEdge - widthOfCharsOnSameLineBeforeCursor), + offsetFromRightEdge = Math.abs(rightEdge - widthOfCharsOnSameLineBeforeCursor); indexOnPrevLine = offsetFromRightEdge < offsetFromLeftEdge ? j : (j - 1); @@ -24747,7 +25550,8 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot } this.setCoords(); - this.fire('text:changed'); + this.fire('changed'); + this.canvas && this.canvas.fire('text:changed', { target: this }); }, /** @@ -24771,7 +25575,7 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot this.selectionStart = leftWordBoundary; } else { - var isBeginningOfLine = this.text.slice(this.selectionStart-1, this.selectionStart) === '\n'; + var isBeginningOfLine = this.text.slice(this.selectionStart - 1, this.selectionStart) === '\n'; this.removeStyleObject(isBeginningOfLine); this.selectionStart--; @@ -24905,7 +25709,7 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot return; } - var DOMParser = new require('xmldom').DOMParser, + var DOMParser = require('xmldom').DOMParser, URL = require('url'), HTTP = require('http'), HTTPS = require('https'), @@ -24923,27 +25727,26 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot } // assign request handler based on protocol - var reqHandler = ( oURL.port === 443 ) ? HTTPS : HTTP; - - var req = reqHandler.request({ - hostname: oURL.hostname, - port: oURL.port, - path: oURL.path, - method: 'GET' - }, function(response){ - var body = ""; - if (encoding) { - response.setEncoding(encoding); - } - response.on('end', function () { - callback(body); - }); - response.on('data', function (chunk) { - if (response.statusCode === 200) { - body += chunk; - } - }); - }); + var reqHandler = ( oURL.port === 443 ) ? HTTPS : HTTP, + req = reqHandler.request({ + hostname: oURL.hostname, + port: oURL.port, + path: oURL.path, + method: 'GET' + }, function(response) { + var body = ''; + if (encoding) { + response.setEncoding(encoding); + } + response.on('end', function () { + callback(body); + }); + response.on('data', function (chunk) { + if (response.statusCode === 200) { + body += chunk; + } + }); + }); req.on('error', function(err) { if (err.errno === process.ECONNREFUSED) { @@ -24958,32 +25761,33 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot } /** @private */ - function request_fs(path, callback){ + function requestFs(path, callback){ var fs = require('fs'); fs.readFile(path, function (err, data) { if (err) { fabric.log(err); throw err; - } else { + } + else { callback(data); } }); } fabric.util.loadImage = function(url, callback, context) { - var createImageAndCallBack = function(data){ + function createImageAndCallBack(data) { img.src = new Buffer(data, 'binary'); // preserving original url, which seems to be lost in node-canvas img._src = url; callback && callback.call(context, img); - }; + } var img = new Image(); if (url && (url instanceof Buffer || url.indexOf('data') === 0)) { img.src = img._src = url; callback && callback.call(context, img); } else if (url && url.indexOf('http') !== 0) { - request_fs(url, createImageAndCallBack); + requestFs(url, createImageAndCallBack); } else if (url) { request(url, 'binary', createImageAndCallBack); @@ -24996,8 +25800,8 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot fabric.loadSVGFromURL = function(url, callback, reviver) { url = url.replace(/^\n\s*/, '').replace(/\?.*$/, '').trim(); if (url.indexOf('http') !== 0) { - request_fs(url, function(body) { - fabric.loadSVGFromString(body, callback, reviver); + requestFs(url, function(body) { + fabric.loadSVGFromString(body.toString(), callback, reviver); }); } else { @@ -25037,12 +25841,15 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot * Only available when running fabric on node.js * @param width Canvas width * @param height Canvas height + * @param {Object} options to pass to FabricCanvas. + * @param {Object} options to pass to NodeCanvas. * @return {Object} wrapped canvas instance */ - fabric.createCanvasForNode = function(width, height) { + fabric.createCanvasForNode = function(width, height, options, nodeCanvasOptions) { + nodeCanvasOptions = nodeCanvasOptions || options; var canvasEl = fabric.document.createElement('canvas'), - nodeCanvas = new Canvas(width || 600, height || 600); + nodeCanvas = new Canvas(width || 600, height || 600, nodeCanvasOptions); // jsdom doesn't create style on canvas element, so here be temp. workaround canvasEl.style = { }; @@ -25050,8 +25857,9 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot canvasEl.width = nodeCanvas.width; canvasEl.height = nodeCanvas.height; - var FabricCanvas = fabric.Canvas || fabric.StaticCanvas; - var fabricCanvas = new FabricCanvas(canvasEl); + var FabricCanvas = fabric.Canvas || fabric.StaticCanvas, + fabricCanvas = new FabricCanvas(canvasEl, options); + fabricCanvas.contextContainer = nodeCanvas.getContext('2d'); fabricCanvas.nodeCanvas = nodeCanvas; fabricCanvas.Font = Canvas.Font; @@ -25090,16 +25898,3 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot })(); - -/* Footer for requirejs AMD support */ - -window.fabric = fabric; - -// make sure exports.fabric is always defined when used as 'global' later scopes -var exports = exports || {}; -exports.fabric = fabric; - -if (typeof define === "function" && define.amd) { - define([], function() { return fabric }); -} - diff --git a/dist/fabric.min.js b/dist/fabric.min.js new file mode 100644 index 00000000..2d03ca19 --- /dev/null +++ b/dist/fabric.min.js @@ -0,0 +1,7 @@ +/* build: `node build.js modules=ALL exclude=gestures,cufon,json minifier=uglifyjs` *//*! Fabric.js Copyright 2008-2014, Printio (Juriy Zaytsev, Maxim Chernyak) */var fabric=fabric||{version:"1.4.6"};typeof exports!="undefined"&&(exports.fabric=fabric),typeof document!="undefined"&&typeof window!="undefined"?(fabric.document=document,fabric.window=window):(fabric.document=require("jsdom").jsdom(""),fabric.window=fabric.document.createWindow()),fabric.isTouchSupported="ontouchstart"in fabric.document.documentElement,fabric.isLikelyNode=typeof Buffer!="undefined"&&typeof window=="undefined",fabric.SHARED_ATTRIBUTES=["display","transform","fill","fill-opacity","fill-rule","opacity","stroke","stroke-dasharray","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke-width"],function(){function e(e,t){if(!this.__eventListeners[e])return;t?fabric.util.removeFromArray(this.__eventListeners[e],t):this.__eventListeners[e].length=0}function t(e,t){this.__eventListeners||(this.__eventListeners={});if(arguments.length===1)for(var n in e)this.on(n,e[n]);else this.__eventListeners[e]||(this.__eventListeners[e]=[]),this.__eventListeners[e].push(t);return this}function n(t,n){if(!this.__eventListeners)return;if(arguments.length===0)this.__eventListeners={};else if(arguments.length===1&&typeof arguments[0]=="object")for(var r in t)e.call(this,r,t[r]);else e.call(this,t,n);return this}function r(e,t){if(!this.__eventListeners)return;var n=this.__eventListeners[e];if(!n)return;for(var r=0,i=n.length;r-1},complexity:function(){return this.getObjects().reduce(function(e,t){return e+=t.complexity?t.complexity():0,e},0)}},function(e){var t=Math.sqrt,n=Math.atan2,r=Math.PI/180;fabric.util={removeFromArray:function(e,t){var n=e.indexOf(t);return n!==-1&&e.splice(n,1),e},getRandomInt:function(e,t){return Math.floor(Math.random()*(t-e+1))+e},degreesToRadians:function(e){return e*r},radiansToDegrees:function(e){return e/r},rotatePoint:function(e,t,n){var r=Math.sin(n),i=Math.cos(n);e.subtractEquals(t);var s=e.x*i-e.y*r,o=e.x*r+e.y*i;return(new fabric.Point(s,o)).addEquals(t)},toFixed:function(e,t){return parseFloat(Number(e).toFixed(t))},falseFunction:function(){return!1},getKlass:function(e,t){return e=fabric.util.string.camelize(e.charAt(0).toUpperCase()+e.slice(1)),fabric.util.resolveNamespace(t)[e]},resolveNamespace:function(t){if(!t)return fabric;var n=t.split("."),r=n.length,i=e||fabric.window;for(var s=0;s1?r=new fabric.PathGroup(e,t):r=e[0],typeof n!="undefined"&&r.setSourcePath(n),r},populateWithProperties:function(e,t,n){if(n&&Object.prototype.toString.call(n)==="[object Array]")for(var r=0,i=n.length;rr)r+=u[p++%h],r>l&&(r=l),e[d?"lineTo":"moveTo"](r,0),d=!d;e.restore()},createCanvasElement:function(e){return e||(e=fabric.document.createElement("canvas")),!e.getContext&&typeof G_vmlCanvasManager!="undefined"&&G_vmlCanvasManager.initElement(e),e},createImage:function(){return fabric.isLikelyNode?new(require("canvas").Image):fabric.document.createElement("img")},createAccessors:function(e){var t=e.prototype;for(var n=t.stateProperties.length;n--;){var r=t.stateProperties[n],i=r.charAt(0).toUpperCase()+r.slice(1),s="set"+i,o="get"+i;t[o]||(t[o]=function(e){return new Function('return this.get("'+e+'")')}(r)),t[s]||(t[s]=function(e){return new Function("value",'return this.set("'+e+'", value)')}(r))}},clipContext:function(e,t){t.save(),t.beginPath(),e.clipTo(t),t.clip()},multiplyTransformMatrices:function(e,t){var n=[[e[0],e[2],e[4]],[e[1],e[3],e[5]],[0,0,1]],r=[[t[0],t[2],t[4]],[t[1],t[3],t[5]],[0,0,1]],i=[];for(var s=0;s<3;s++){i[s]=[];for(var o=0;o<3;o++){var u=0;for(var a=0;a<3;a++)u+=n[s][a]*r[a][o];i[s][o]=u}}return[i[0][0],i[1][0],i[0][1],i[1][1],i[0][2],i[1][2]]},getFunctionBody:function(e){return(String(e).match(/function[^{]*\{([\s\S]*)\}/)||{})[1]},normalizePoints:function(e,t){var n=fabric.util.array.min(e,"x"),r=fabric.util.array.min(e,"y");n=n<0?n:0,r=n<0?r:0;for(var i=0,s=e.length;i0&&(t>r?t-=r:t=0,n>r?n-=r:n=0);var i=!0,s=e.getImageData(t,n,r*2||1,r*2||1);for(var o=3,u=s.data.length;o0&&f===0&&(E-=2*Math.PI);var S=Math.ceil(Math.abs(E/(Math.PI*.5+.001))),x=[];for(var T=0;T1&&(h=Math.sqrt(h),t*=h,n*=h);var p=f/t,d=a/t,v=-a/n,m=f/n;return{x0:p*r+d*i,y0:v*r+m*i,x1:p*s+d*o,y1:v*s+m*o,sinTh:a,cosTh:f}}function o(e,i,s,o,u,a,f,l){r=n.call(arguments);if(t[r])return t[r];var c=l*u,h=-f*a,p=f*u,d=l*a,v=.5*(o-s),m=8/3*Math.sin(v*.5)*Math.sin(v*.5)/Math.sin(v),g=e+Math.cos(s)-m*Math.sin(s),y=i+Math.sin(s)+m*Math.cos(s),b=e+Math.cos(o),w=i+Math.sin(o),E=b+m*Math.sin(o),S=w-m*Math.cos(o);return t[r]=[c*g+h*y,p*g+d*y,c*E+h*S,p*E+d*S,c*b+h*w,p*b+d*w],t[r]}var e={},t={},n=Array.prototype.join,r;fabric.util.drawArc=function(e,t,n,r){var s=r[0],u=r[1],a=r[2],f=r[3],l=r[4],c=r[5],h=r[6],p=i(c,h,s,u,f,l,a,t,n);for(var d=0;d=t})}function r(e,t){return i(e,t,function(e,t){return e>>0;if(n===0)return-1;var r=0;arguments.length>0&&(r=Number(arguments[1]),r!==r?r=0:r!==0&&r!==Number.POSITIVE_INFINITY&&r!==Number.NEGATIVE_INFINITY&&(r=(r>0||-1)*Math.floor(Math.abs(r))));if(r>=n)return-1;var i=r>=0?r:Math.max(n-Math.abs(r),0);for(;i>>0;n>>0;r>>0;n>>0;n>>0;i>>0,n=0,r;if(arguments.length>1)r=arguments[1];else do{if(n in this){r=this[n++];break}if(++n>=t)throw new TypeError}while(!0);for(;n/g,">")}String.prototype.trim||(String.prototype.trim=function(){return this.replace(/^[\s\xA0]+/,"").replace(/[\s\xA0]+$/,"")}),fabric.util.string={camelize:e,capitalize:t,escapeXml:n}}(),function(){var e=Array.prototype.slice,t=Function.prototype.apply,n=function(){};Function.prototype.bind||(Function.prototype.bind=function(r){var i=this,s=e.call(arguments,1),o;return s.length?o=function(){return t.call(i,this instanceof n?this:r,s.concat(e.call(arguments)))}:o=function(){return t.call(i,this instanceof n?this:r,arguments)},n.prototype=this.prototype,o.prototype=new n,o})}(),function(){function i(){}function s(t){var n=this.constructor.superclass.prototype[t];return arguments.length>1?n.apply(this,e.call(arguments,1)):n.call(this)}function o(){function u(){this.initialize.apply(this,arguments)}var n=null,o=e.call(arguments,0);typeof o[0]=="function"&&(n=o.shift()),u.superclass=n,u.subclasses=[],n&&(i.prototype=n.prototype,u.prototype=new i,n.subclasses.push(u));for(var a=0,f=o.length;a-1?e.prototype[i]=function(e){return function(){var n=this.constructor.superclass;this.constructor.superclass=r;var i=t[e].apply(this,arguments);this.constructor.superclass=n;if(e!=="initialize")return i}}(i):e.prototype[i]=t[i],n&&(t.toString!==Object.prototype.toString&&(e.prototype.toString=t.toString),t.valueOf!==Object.prototype.valueOf&&(e.prototype.valueOf=t.valueOf))};fabric.util.createClass=o}(),function(){function t(e){var t=Array.prototype.slice.call(arguments,1),n,r,i=t.length;for(r=0;r-1?s(e,t.match(/opacity:\s*(\d?\.?\d*)/)[1]):e;for(var r in t)if(r==="opacity")s(e,t[r]);else{var i=r==="float"||r==="cssFloat"?typeof n.styleFloat=="undefined"?"cssFloat":"styleFloat":r;n[i]=t[r]}return e}var t=fabric.document.createElement("div"),n=typeof t.style.opacity=="string",r=typeof t.style.filter=="string",i=/alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/,s=function(e){return e};n?s=function(e,t){return e.style.opacity=t,e}:r&&(s=function(e,t){var n=e.style;return e.currentStyle&&!e.currentStyle.hasLayout&&(n.zoom=1),i.test(n.filter)?(t=t>=.9999?"":"alpha(opacity="+t*100+")",n.filter=n.filter.replace(i,t)):n.filter+=" alpha(opacity="+t*100+")",e}),fabric.util.setStyle=e}(),function(){function t(e){return typeof e=="string"?fabric.document.getElementById(e):e}function s(e,t){var n=fabric.document.createElement(e);for(var r in t)r==="class"?n.className=t[r]:r==="for"?n.htmlFor=t[r]:n.setAttribute(r,t[r]);return n}function o(e,t){e&&(" "+e.className+" ").indexOf(" "+t+" ")===-1&&(e.className+=(e.className?" ":"")+t)}function u(e,t,n){return typeof t=="string"&&(t=s(t,n)),e.parentNode&&e.parentNode.replaceChild(t,e),t.appendChild(e),t}function a(e,t){var n,r,i=0,s=0,o=fabric.document.documentElement,u=fabric.document.body||{scrollLeft:0,scrollTop:0};r=e;while(e&&e.parentNode&&!n)e=e.parentNode,e!==fabric.document&&fabric.util.getElementStyle(e,"position")==="fixed"&&(n=e),e!==fabric.document&&r!==t&&fabric.util.getElementStyle(e,"position")==="absolute"?(i=0,s=0):e===fabric.document?(i=u.scrollLeft||o.scrollLeft||0,s=u.scrollTop||o.scrollTop||0):(i+=e.scrollLeft||0,s+=e.scrollTop||0);return{left:i,top:s}}function f(e){var t,n=e&&e.ownerDocument,r={left:0,top:0},i={left:0,top:0},s,o={borderLeftWidth:"left",borderTopWidth:"top",paddingLeft:"left",paddingTop:"top"};if(!n)return{left:0,top:0};for(var u in o)i[o[u]]+=parseInt(l(e,u),10)||0;return t=n.documentElement,typeof e.getBoundingClientRect!="undefined"&&(r=e.getBoundingClientRect()),s=fabric.util.getScrollLeftTop(e,null),{left:r.left+s.left-(t.clientLeft||0)+i.left,top:r.top+s.top-(t.clientTop||0)+i.top}}var e=Array.prototype.slice,n,r=function(t){return e.call(t,0)};try{n=r(fabric.document.childNodes)instanceof Array}catch(i){}n||(r=function(e){var t=new Array(e.length),n=e.length;while(n--)t[n]=e[n];return t});var l;fabric.document.defaultView&&fabric.document.defaultView.getComputedStyle?l=function(e,t){return fabric.document.defaultView.getComputedStyle(e,null)[t]}:l=function(e,t){var n=e.style[t];return!n&&e.currentStyle&&(n=e.currentStyle[t]),n},function(){function n(e){return typeof e.onselectstart!="undefined"&&(e.onselectstart=fabric.util.falseFunction),t?e.style[t]="none":typeof e.unselectable=="string"&&(e.unselectable="on"),e}function r(e){return typeof e.onselectstart!="undefined"&&(e.onselectstart=null),t?e.style[t]="":typeof e.unselectable=="string"&&(e.unselectable=""),e}var e=fabric.document.documentElement.style,t="userSelect"in e?"userSelect":"MozUserSelect"in e?"MozUserSelect":"WebkitUserSelect"in e?"WebkitUserSelect":"KhtmlUserSelect"in e?"KhtmlUserSelect":"";fabric.util.makeElementUnselectable=n,fabric.util.makeElementSelectable=r}(),function(){function e(e,t){var n=fabric.document.getElementsByTagName("head")[0],r=fabric.document.createElement("script"),i=!0;r.onload=r.onreadystatechange=function(e){if(i){if(typeof this.readyState=="string"&&this.readyState!=="loaded"&&this.readyState!=="complete")return;i=!1,t(e||fabric.window.event),r=r.onload=r.onreadystatechange=null}},r.src=e,n.appendChild(r)}fabric.util.getScript=e}(),fabric.util.getById=t,fabric.util.toArray=r,fabric.util.makeElement=s,fabric.util.addClass=o,fabric.util.wrapElement=u,fabric.util.getScrollLeftTop=a,fabric.util.getElementOffset=f,fabric.util.getElementStyle=l}(),function(){function e(e,t){return e+(/\?/.test(e)?"&":"?")+t}function n(){}function r(r,i){i||(i={});var s=i.method?i.method.toUpperCase():"GET",o=i.onComplete||function(){},u=t(),a;return u.onreadystatechange=function(){u.readyState===4&&(o(u),u.onreadystatechange=n)},s==="GET"&&(a=null,typeof i.parameters=="string"&&(r=e(r,i.parameters))),u.open(s,r,!0),(s==="POST"||s==="PUT")&&u.setRequestHeader("Content-Type","application/x-www-form-urlencoded"),u.send(a),u}var t=function(){var e=[function(){return new ActiveXObject("Microsoft.XMLHTTP")},function(){return new ActiveXObject("Msxml2.XMLHTTP")},function(){return new ActiveXObject("Msxml2.XMLHTTP.3.0")},function(){return new XMLHttpRequest}];for(var t=e.length;t--;)try{var n=e[t]();if(n)return e[t]}catch(r){}}();fabric.util.request=r}(),fabric.log=function(){},fabric.warn=function(){},typeof console!="undefined"&&["log","warn"].forEach(function(e){typeof console[e]!="undefined"&&console[e].apply&&(fabric[e]=function(){return console[e].apply(console,arguments)})}),function(){function e(e){n(function(t){e||(e={});var r=t||+(new Date),i=e.duration||500,s=r+i,o,u=e.onChange||function(){},a=e.abort||function(){return!1},f=e.easing||function(e,t,n,r){return-n*Math.cos(e/r*(Math.PI/2))+n+t},l="startValue"in e?e.startValue:0,c="endValue"in e?e.endValue:100,h=e.byValue||c-l;e.onStart&&e.onStart(),function p(t){o=t||+(new Date);var c=o>s?i:o-r;if(a()){e.onComplete&&e.onComplete();return}u(f(c,l,h,i));if(o>s){e.onComplete&&e.onComplete();return}n(p)}(r)})}function n(){return t.apply(fabric.window,arguments)}var t=fabric.window.requestAnimationFrame||fabric.window.webkitRequestAnimationFrame||fabric.window.mozRequestAnimationFrame||fabric.window.oRequestAnimationFrame||fabric.window.msRequestAnimationFrame||function(e){fabric.window.setTimeout(e,1e3/60)};fabric.util.animate=e,fabric.util.requestAnimFrame=n}(),function(){function e(e,t,n,r){return e','')}var t=e.fabric||(e.fabric={}),n=t.util.object.extend,r=t.util.string.capitalize,i=t.util.object.clone,s=t.util.toFixed,o=t.util.multiplyTransformMatrices,u={cx:"left",x:"left",r:"radius",cy:"top",y:"top",display:"visible",visibility:"visible",transform:"transformMatrix","fill-opacity":"fillOpacity","fill-rule":"fillRule","font-family":"fontFamily","font-size":"fontSize","font-style":"fontStyle","font-weight":"fontWeight","stroke-dasharray":"strokeDashArray","stroke-linecap":"strokeLineCap","stroke-linejoin":"strokeLineJoin","stroke-miterlimit":"strokeMiterLimit","stroke-opacity":"strokeOpacity","stroke-width":"strokeWidth","text-decoration":"textDecoration","text-anchor":"originX"},a={stroke:"strokeOpacity",fill:"fillOpacity"};t.parseTransformAttribute=function(){function e(e,t){var n=t[0];e[0]=Math.cos(n),e[1]=Math.sin(n),e[2]=-Math.sin(n),e[3]=Math.cos(n)}function n(e,t){var n=t[0],r=t.length===2?t[1]:t[0];e[0]=n,e[3]=r}function r(e,t){e[2]=t[0]}function i(e,t){e[1]=t[0]}function s(e,t){e[4]=t[0],t.length===2&&(e[5]=t[1])}var o=[1,0,0,1,0,0],u="(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)",a="(?:\\s+,?\\s*|,\\s*)",f="(?:(skewX)\\s*\\(\\s*("+u+")\\s*\\))",l="(?:(skewY)\\s*\\(\\s*("+u+")\\s*\\))",c="(?:(rotate)\\s*\\(\\s*("+u+")(?:"+a+"("+u+")"+a+"("+u+"))?\\s*\\))",h="(?:(scale)\\s*\\(\\s*("+u+")(?:"+a+"("+u+"))?\\s*\\))",p="(?:(translate)\\s*\\(\\s*("+u+")(?:"+a+"("+u+"))?\\s*\\))",d="(?:(matrix)\\s*\\(\\s*("+u+")"+a+"("+u+")"+a+"("+u+")"+a+"("+u+")"+a+"("+u+")"+a+"("+u+")"+"\\s*\\))",v="(?:"+d+"|"+p+"|"+h+"|"+c+"|"+f+"|"+l+")",m="(?:"+v+"(?:"+a+v+")*"+")",g="^\\s*(?:"+m+"?)\\s*$",y=new RegExp(g),b=new RegExp(v,"g");return function(u){var a=o.concat(),f=[];if(!u||u&&!y.test(u))return a;u.replace(b,function(u){var l=(new RegExp(v)).exec(u).filter(function(e){return e!==""&&e!=null}),c=l[1],h=l.slice(2).map(parseFloat);switch(c){case"translate":s(a,h);break;case"rotate":h[0]=t.util.degreesToRadians(h[0]),e(a,h);break;case"scale":n(a,h);break;case"skewX":r(a,h);break;case"skewY":i(a,h);break;case"matrix":a=h}f.push(a.concat()),a=o.concat()});var l=f[0];while(f.length>1)f.shift(),l=t.util.multiplyTransformMatrices(l,f[0]);return l}}(),t.parseSVGDocument=function(){function s(e,t){while(e&&(e=e.parentNode))if(t.test(e.nodeName))return!0;return!1}var e=/^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/,n="(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)",r=new RegExp("^\\s*("+n+"+)\\s*,?"+"\\s*("+n+"+)\\s*,?"+"\\s*("+n+"+)\\s*,?"+"\\s*("+n+"+)\\s*"+"$");return function(n,o,u){if(!n)return;var a=new Date,f=t.util.toArray(n.getElementsByTagName("*"));if(f.length===0&&t.isLikelyNode){f=n.selectNodes('//*[name(.)!="svg"]');var l=[];for(var c=0,h=f.length;c-1;e=e.split(/\s+/);var n=[],r,i;if(t){r=0,i=e.length;for(;r/i,"")));if(!s||!s.documentElement)return;t.parseSVGDocument(s.documentElement,function(r,i){m.set(e,{objects:t.util.array.invoke(r,"toObject"),options:i}),n(r,i)},r)}e=e.replace(/^\n\s*/,"").trim(),m.has(e,function(r){r?m.get(e,function(e){var t=g(e);n(t.objects,t.options)}):new t.util.request(e,{method:"get",onComplete:i})})},loadSVGFromString:function(e,n,r){e=e.trim();var i;if(typeof DOMParser!="undefined"){var s=new DOMParser;s&&s.parseFromString&&(i=s.parseFromString(e,"text/xml"))}else t.window.ActiveXObject&&(i=new ActiveXObject("Microsoft.XMLDOM"),i.async="false",i.loadXML(e.replace(//i,"")));t.parseSVGDocument(i.documentElement,function(e,t){n(e,t)},r)},createSVGFontFacesMarkup:function(e){var t="";for(var n=0,r=e.length;n',"",""].join("")),t},createSVGRefElementsMarkup:function(e){var t=[];return y(t,e,"backgroundColor"),y(t,e,"overlayColor"),t.join("")}})}(typeof exports!="undefined"?exports:this),fabric.ElementsParser=function(e,t,n,r){this.elements=e,this.callback=t,this.options=n,this.reviver=r},fabric.ElementsParser.prototype.parse=function(){this.instances=new Array(this.elements.length),this.numElements=this.elements.length,this.createObjects()},fabric.ElementsParser.prototype.createObjects=function(){for(var e=0,t=this.elements.length;ee.x&&this.y>e.y},gte:function(e){return this.x>=e.x&&this.y>=e.y},lerp:function(e,t){return new n(this.x+(e.x-this.x)*t,this.y+(e.y-this.y)*t)},distanceFrom:function(e){var t=this.x-e.x,n=this.y-e.y;return Math.sqrt(t*t+n*n)},midPointFrom:function(e){return new n(this.x+(e.x-this.x)/2,this.y+(e.y-this.y)/2)},min:function(e){return new n(Math.min(this.x,e.x),Math.min(this.y,e.y))},max:function(e){return new n(Math.max(this.x,e.x),Math.max(this.y,e.y))},toString:function(){return this.x+","+this.y},setXY:function(e,t){this.x=e,this.y=t},setFromPoint:function(e){this.x=e.x,this.y=e.y},swap:function(e){var t=this.x,n=this.y;this.x=e.x,this.y=e.y,e.x=t,e.y=n}}}(typeof exports!="undefined"?exports:this),function(e){"use strict";function n(e){this.status=e,this.points=[]}var t=e.fabric||(e.fabric={});if(t.Intersection){t.warn("fabric.Intersection is already defined");return}t.Intersection=n,t.Intersection.prototype={appendPoint:function(e){this.points.push(e)},appendPoints:function(e){this.points=this.points.concat(e)}},t.Intersection.intersectLineLine=function(e,r,i,s){var o,u=(s.x-i.x)*(e.y-i.y)-(s.y-i.y)*(e.x-i.x),a=(r.x-e.x)*(e.y-i.y)-(r.y-e.y)*(e.x-i.x),f=(s.y-i.y)*(r.x-e.x)-(s.x-i.x)*(r.y-e.y);if(f!==0){var l=u/f,c=a/f;0<=l&&l<=1&&0<=c&&c<=1?(o=new n("Intersection"),o.points.push(new t.Point(e.x+l*(r.x-e.x),e.y+l*(r.y-e.y)))):o=new n}else u===0||a===0?o=new n("Coincident"):o=new n("Parallel");return o},t.Intersection.intersectLinePolygon=function(e,t,r){var i=new n,s=r.length;for(var o=0;o0&&(i.status="Intersection"),i},t.Intersection.intersectPolygonPolygon=function(e,t){var r=new n,i=e.length;for(var s=0;s0&&(r.status="Intersection"),r},t.Intersection.intersectPolygonRectangle=function(e,r,i){var s=r.min(i),o=r.max(i),u=new t.Point(o.x,s.y),a=new t.Point(s.x,o.y),f=n.intersectLinePolygon(s,u,e),l=n.intersectLinePolygon(u,o,e),c=n.intersectLinePolygon(o,a,e),h=n.intersectLinePolygon(a,s,e),p=new n;return p.appendPoints(f.points),p.appendPoints(l.points),p.appendPoints(c.points),p.appendPoints(h.points),p.points.length>0&&(p.status="Intersection"),p}}(typeof exports!="undefined"?exports:this),function(e){"use strict";function n(e){e?this._tryParsingColor(e):this.setSource([0,0,0,1])}function r(e,t,n){return n<0&&(n+=1),n>1&&(n-=1),n<1/6?e+(t-e)*6*n:n<.5?t:n<2/3?e+(t-e)*(2/3-n)*6:e}var t=e.fabric||(e.fabric={});if(t.Color){t.warn("fabric.Color is already defined.");return}t.Color=n,t.Color.prototype={_tryParsingColor:function(e){var t;e in n.colorNameMap&&(e=n.colorNameMap[e]);if(e==="transparent"){this.setSource([255,255,255,0]);return}t=n.sourceFromHex(e),t||(t=n.sourceFromRgb(e)),t||(t=n.sourceFromHsl(e)),t&&this.setSource(t)},_rgbToHsl:function(e,n,r){e/=255,n/=255,r/=255;var i,s,o,u=t.util.array.max([e,n,r]),a=t.util.array.min([e,n,r]);o=(u+a)/2;if(u===a)i=s=0;else{var f=u-a;s=o>.5?f/(2-u-a):f/(u+a);switch(u){case e:i=(n-r)/f+(n']:this.type==="radial"&&(r=["']);for(var i=0;i');return r.push(this.type==="linear"?"":""),r.join("")},toLive:function(e){var t;if(!this.type)return;this.type==="linear"?t=e.createLinearGradient(this.coords.x1,this.coords.y1,this.coords.x2,this.coords.y2):this.type==="radial"&&(t=e.createRadialGradient(this.coords.x1,this.coords.y1,this.coords.r1,this.coords.x2,this.coords.y2,this.coords.r2));for(var n=0,r=this.colorStops.length;n'+''+""},toLive:function(e){var t=typeof this.source=="function"?this.source():this.source;if(!t)return"";if(typeof t.src!="undefined"){if(!t.complete)return"";if(t.naturalWidth===0||t.naturalHeight===0)return""}return e.createPattern(t,this.repeat)}}),function(e){"use strict";var t=e.fabric||(e.fabric={});if(t.Shadow){t.warn("fabric.Shadow is already defined.");return}t.Shadow=t.util.createClass({color:"rgb(0,0,0)",blur:0,offsetX:0,offsetY:0,affectStroke:!1,includeDefaultValues:!0,initialize:function(e){typeof e=="string"&&(e=this._parseShadow(e));for(var n in e)this[n]=e[n];this.id=t.Object.__uid++},_parseShadow:function(e){var n=e.trim(),r=t.Shadow.reOffsetsAndBlur.exec(n)||[],i=n.replace(t.Shadow.reOffsetsAndBlur,"")||"rgb(0,0,0)";return{color:i.trim(),offsetX:parseInt(r[1],10)||0,offsetY:parseInt(r[2],10)||0,blur:parseInt(r[3],10)||0}},toString:function(){return[this.offsetX,this.offsetY,this.blur,this.color].join("px ")},toSVG:function(e){var t="SourceAlpha";return e&&(e.fill===this.color||e.stroke===this.color)&&(t="SourceGraphic"),''+''+''+""+""+''+""+""},toObject:function(){if(this.includeDefaultValues)return{color:this.color,blur:this.blur,offsetX:this.offsetX,offsetY:this.offsetY};var e={},n=t.Shadow.prototype;return this.color!==n.color&&(e.color=this.color),this.blur!==n.blur&&(e.blur=this.blur),this.offsetX!==n.offsetX&&(e.offsetX=this.offsetX),this.offsetY!==n.offsetY&&(e.offsetY=this.offsetY),e}}),t.Shadow.reOffsetsAndBlur=/(?:\s|^)(-?\d+(?:px)?(?:\s?|$))?(-?\d+(?:px)?(?:\s?|$))?(\d+(?:px)?)?(?:\s?|$)(?:$|\s)/}(typeof exports!="undefined"?exports:this),function(){"use strict";if(fabric.StaticCanvas){fabric.warn("fabric.StaticCanvas is already defined.");return}var e=fabric.util.object.extend,t=fabric.util.getElementOffset,n=fabric.util.removeFromArray,r=new Error("Could not initialize `canvas` element");fabric.StaticCanvas=fabric.util.createClass({initialize:function(e,t){t||(t={}),this._initStatic(e,t),fabric.StaticCanvas.activeInstance=this},backgroundColor:"",backgroundImage:null,overlayColor:"",overlayImage:null,includeDefaultValues:!0,stateful:!0,renderOnAddRemove:!0,clipTo:null,controlsAboveOverlay:!1,allowTouchScrolling:!1,imageSmoothingEnabled:!0,onBeforeScaleRotate:function(){},_initStatic:function(e,t){this._objects=[],this._createLowerCanvas(e),this._initOptions(t),this._setImageSmoothing(),t.overlayImage&&this.setOverlayImage(t.overlayImage,this.renderAll.bind(this)),t.backgroundImage&&this.setBackgroundImage(t.backgroundImage,this.renderAll.bind(this)),t.backgroundColor&&this.setBackgroundColor(t.backgroundColor,this.renderAll.bind(this)),t.overlayColor&&this.setOverlayColor(t.overlayColor,this.renderAll.bind(this)),this.calcOffset()},calcOffset:function(){return this._offset=t(this.lowerCanvasEl),this},setOverlayImage:function(e,t,n){return this.__setBgOverlayImage("overlayImage",e,t,n)},setBackgroundImage:function(e,t,n){return this.__setBgOverlayImage("backgroundImage",e,t,n)},setOverlayColor:function(e,t){return this.__setBgOverlayColor("overlayColor",e,t)},setBackgroundColor:function(e,t){return this.__setBgOverlayColor("backgroundColor",e,t)},_setImageSmoothing:function(){var e=this.getContext();e.imageSmoothingEnabled=this.imageSmoothingEnabled,e.webkitImageSmoothingEnabled=this.imageSmoothingEnabled,e.mozImageSmoothingEnabled=this.imageSmoothingEnabled,e.msImageSmoothingEnabled=this.imageSmoothingEnabled,e.oImageSmoothingEnabled=this.imageSmoothingEnabled},__setBgOverlayImage:function(e,t,n,r){return typeof t=="string"?fabric.util.loadImage(t,function(t){this[e]=new fabric.Image(t,r),n&&n()},this):(this[e]=t,n&&n()),this},__setBgOverlayColor:function(e,t,n){if(t.source){var r=this;fabric.util.loadImage(t.source,function(i){r[e]=new fabric.Pattern({source:i,repeat:t.repeat,offsetX:t.offsetX,offsetY:t.offsetY}),n&&n()})}else this[e]=t,n&&n();return this},_createCanvasElement:function(){var e=fabric.document.createElement("canvas");e.style||(e.style={});if(!e)throw r;return this._initCanvasElement(e),e},_initCanvasElement:function(e){fabric.util.createCanvasElement(e);if(typeof e.getContext=="undefined")throw r},_initOptions:function(e){for(var t in e)this[t]=e[t];this.width=this.width||parseInt(this.lowerCanvasEl.width,10)||0,this.height=this.height||parseInt(this.lowerCanvasEl.height,10)||0;if(!this.lowerCanvasEl.style)return;this.lowerCanvasEl.width=this.width,this.lowerCanvasEl.height=this.height,this.lowerCanvasEl.style.width=this.width+"px",this.lowerCanvasEl.style.height=this.height+"px"},_createLowerCanvas:function(e){this.lowerCanvasEl=fabric.util.getById(e)||this._createCanvasElement(),this._initCanvasElement(this.lowerCanvasEl),fabric.util.addClass(this.lowerCanvasEl,"lower-canvas"),this.interactive&&this._applyCanvasStyle(this.lowerCanvasEl),this.contextContainer=this.lowerCanvasEl.getContext("2d")},getWidth:function(){return this.width},getHeight:function(){return this.height},setWidth:function(e){return this._setDimension("width",e)},setHeight:function(e){return this._setDimension("height",e)},setDimensions:function(e){for(var t in e)this._setDimension(t,e[t]);return this},_setDimension:function(e,t){return this.lowerCanvasEl[e]=t,this.lowerCanvasEl.style[e]=t+"px",this.upperCanvasEl&&(this.upperCanvasEl[e]=t,this.upperCanvasEl.style[e]=t+"px"),this.cacheCanvasEl&&(this.cacheCanvasEl[e]=t),this.wrapperEl&&(this.wrapperEl.style[e]=t+"px"),this[e]=t,this.calcOffset(),this.renderAll(),this},getElement:function(){return this.lowerCanvasEl},getActiveObject:function(){return null},getActiveGroup:function(){return null},_draw:function(e,t){if(!t)return;if(this.controlsAboveOverlay){var n=t.hasBorders,r=t.hasControls;t.hasBorders=t.hasControls=!1,t.render(e),t.hasBorders=n,t.hasControls=r}else t.render(e)},_onObjectAdded:function(e){this.stateful&&e.setupState(),e.setCoords(),e.canvas=this,this.fire("object:added",{target:e}),e.fire("added")},_onObjectRemoved:function(e){this.getActiveObject()===e&&(this.fire("before:selection:cleared",{target:e}),this._discardActiveObject(),this.fire("selection:cleared")),this.fire("object:removed",{target:e}),e.fire("removed")},clearContext:function(e){return e.clearRect(0,0,this.width,this.height),this},getContext:function(){return this.contextContainer},clear:function(){return this._objects.length=0,this.discardActiveGroup&&this.discardActiveGroup(),this.discardActiveObject&&this.discardActiveObject(),this.clearContext(this.contextContainer),this.contextTop&&this.clearContext(this.contextTop),this.fire("canvas:cleared"),this.renderAll(),this},renderAll:function(e){var t=this[e===!0&&this.interactive?"contextTop":"contextContainer"],n=this.getActiveGroup();return this.contextTop&&this.selection&&!this._groupSelector&&this.clearContext(this.contextTop),e||this.clearContext(t),this.fire("before:render"),this.clipTo&&fabric.util.clipContext(this,t),this._renderBackground(t),this._renderObjects(t,n),this._renderActiveGroup(t,n),this.clipTo&&t.restore(),this._renderOverlay(t),this.controlsAboveOverlay&&this.interactive&&this.drawControls(t),this.fire("after:render"),this},_renderObjects:function(e,t){var n,r;if(!t)for(n=0,r=this._objects.length;n"),n.join("")},_setSVGPreamble:function(e,t){t.suppressPreamble||e.push('','\n')},_setSVGHeader:function(e,t){e.push("',"Created with Fabric.js ",fabric.version,"","",fabric.createSVGFontFacesMarkup(this.getObjects()),fabric.createSVGRefElementsMarkup(this),"")},_setSVGObjects:function(e,t){var n=this.getActiveGroup();n&&this.discardActiveGroup();for(var r=0,i=this.getObjects(),s=i.length;r"):this[t]&&t==="overlayColor"&&e.push('")},sendToBack:function(e){return n(this._objects,e),this._objects.unshift(e),this.renderAll&&this.renderAll()},bringToFront:function(e){return n(this._objects,e),this._objects.push(e),this.renderAll&&this.renderAll()},sendBackwards:function(e,t){var r=this._objects.indexOf(e);if(r!==0){var i=this._findNewLowerIndex(e,r,t);n(this._objects,e),this._objects.splice(i,0,e),this.renderAll&&this.renderAll()}return this},_findNewLowerIndex:function(e,t,n){var r;if(n){r=t;for(var i=t-1;i>=0;--i){var s=e.intersectsWithObject(this._objects[i])||e.isContainedWithinObject(this._objects[i])||this._objects[i].isContainedWithinObject(e);if(s){r=i;break}}}else r=t-1;return r},bringForward:function(e,t){var r=this._objects.indexOf(e);if(r!==this._objects.length-1){var i=this._findNewUpperIndex(e,r,t);n(this._objects,e),this._objects.splice(i,0,e),this.renderAll&&this.renderAll()}return this},_findNewUpperIndex:function(e,t,n){var r;if(n){r=t;for(var i=t+1;i"}}),e(fabric.StaticCanvas.prototype,fabric.Observable),e(fabric.StaticCanvas.prototype,fabric.Collection),e(fabric.StaticCanvas.prototype,fabric.DataURLExporter),e(fabric.StaticCanvas,{EMPTY_JSON:'{"objects": [], "background": "white"}',supports:function(e){var t=fabric.util.createCanvasElement();if(!t||!t.getContext)return null;var n=t.getContext("2d");if(!n)return null;switch(e){case"getImageData":return typeof n.getImageData!="undefined";case"setLineDash":return typeof n.setLineDash!="undefined";case"toDataURL":return typeof t.toDataURL!="undefined";case"toDataURLWithQuality":try{return t.toDataURL("image/jpeg",0),!0}catch(r){}return!1;default:return null}}}),fabric.StaticCanvas.prototype.toJSON=fabric.StaticCanvas.prototype.toObject}(),fabric.BaseBrush=fabric.util.createClass({color:"rgb(0, 0, 0)",width:1,shadow:null,strokeLineCap:"round",strokeLineJoin:"round",setShadow:function(e){return this.shadow=new fabric.Shadow(e),this},_setBrushStyles:function(){var e=this.canvas.contextTop;e.strokeStyle=this.color,e.lineWidth=this.width,e.lineCap=this.strokeLineCap,e.lineJoin=this.strokeLineJoin},_setShadow:function(){if(!this.shadow)return;var e=this.canvas.contextTop;e.shadowColor=this.shadow.color,e.shadowBlur=this.shadow.blur,e.shadowOffsetX=this.shadow.offsetX,e.shadowOffsetY=this.shadow.offsetY},_resetShadow:function(){var e=this.canvas.contextTop;e.shadowColor="",e.shadowBlur=e.shadowOffsetX=e.shadowOffsetY=0}}),function(){var e=fabric.util.array.min,t=fabric.util.array.max;fabric.PencilBrush=fabric.util.createClass(fabric.BaseBrush,{initialize:function(e){this.canvas=e,this._points=[]},onMouseDown:function(e){this._prepareForDrawing(e),this._captureDrawingPath(e),this._render()},onMouseMove:function(e){this._captureDrawingPath(e),this.canvas.clearContext(this.canvas.contextTop),this._render()},onMouseUp:function(){this._finalizeAndAddPath()},_prepareForDrawing:function(e){var t=new fabric.Point(e.x,e.y);this._reset(),this._addPoint(t),this.canvas.contextTop.moveTo(t.x,t.y)},_addPoint:function(e){this._points.push(e)},_reset:function(){this._points.length=0,this._setBrushStyles(),this._setShadow()},_captureDrawingPath:function(e){var t=new fabric.Point(e.x,e.y);this._addPoint(t)},_render:function(){var e=this.canvas.contextTop;e.beginPath();var t=this._points[0],n=this._points[1];this._points.length===2&&t.x===n.x&&t.y===n.y&&(t.x-=.5,n.x+=.5),e.moveTo(t.x,t.y);for(var r=1,i=this._points.length;rn.padding?e.x<0?e.x+=n.padding:e.x-=n.padding:e.x=0,i(e.y)>n.padding?e.y<0?e.y+=n.padding:e.y-=n.padding:e.y=0},_rotateObject:function(e,t){var i=this._currentTransform;if(i.target.get("lockRotation"))return;var s=r(i.ey-i.top,i.ex-i.left),o=r(t-i.top,e-i.left),u=n(o-s+i.theta);u<0&&(u=360+u),i.target.angle=u},_setCursor:function(e){this.upperCanvasEl.style.cursor=e},_resetObjectTransform:function(e){e.scaleX=1,e.scaleY=1,e.setAngle(0)},_drawSelection:function(){var e=this.contextTop,t=this._groupSelector,n=t.left,r=t.top,o=i(n),u=i(r);e.fillStyle=this.selectionColor,e.fillRect(t.ex-(n>0?0:-n),t.ey-(r>0?0:-r),o,u),e.lineWidth=this.selectionLineWidth,e.strokeStyle=this.selectionBorderColor;if(this.selectionDashArray.length>1){var a=t.ex+s-(n>0?0:o),f=t.ey+s-(r>0?0:u);e.beginPath(),fabric.util.drawDashedLine(e,a,f,a+o,f,this.selectionDashArray),fabric.util.drawDashedLine(e,a,f+u-1,a+o,f+u-1,this.selectionDashArray),fabric.util.drawDashedLine(e,a,f,a,f+u,this.selectionDashArray),fabric.util.drawDashedLine(e,a+o-1,f,a+o-1,f+u,this.selectionDashArray),e.closePath(),e.stroke()}else e.strokeRect(t.ex+s-(n>0?0:o),t.ey+s-(r>0?0:u),o,u)},_isLastRenderedObject:function(e){return this.controlsAboveOverlay&&this.lastRenderedObjectWithControlsAboveOverlay&&this.lastRenderedObjectWithControlsAboveOverlay.visible&&this.containsPoint(e,this.lastRenderedObjectWithControlsAboveOverlay)&&this.lastRenderedObjectWithControlsAboveOverlay._findTargetCorner(this.getPointer(e))},findTarget:function(e,t){if(this.skipTargetFind)return;if(this._isLastRenderedObject(e))return this.lastRenderedObjectWithControlsAboveOverlay;var n=this.getActiveGroup();if(n&&!t&&this.containsPoint(e,n))return n;var r=this._searchPossibleTargets(e);return this._fireOverOutEvents(r),r},_fireOverOutEvents:function(e){e?this._hoveredTarget!==e&&(this.fire("mouse:over",{target:e}),e.fire("mouseover"),this._hoveredTarget&&(this.fire("mouse:out",{target:this._hoveredTarget}),this._hoveredTarget.fire("mouseout")),this._hoveredTarget=e):this._hoveredTarget&&(this.fire("mouse:out",{target:this._hoveredTarget}),this._hoveredTarget.fire("mouseout"),this._hoveredTarget=null)},_checkTarget:function(e,t,n){if(t&&t.visible&&t.evented&&this.containsPoint(e,t)){if(!this.perPixelTargetFind&&!t.perPixelTargetFind||!!t.isEditing)return!0;var r=this.isTargetTransparent(t,n.x,n.y);if(!r)return!0}},_searchPossibleTargets:function(e){var t,n=this.getPointer(e),r=this._objects.length;while(r--)if(this._checkTarget(e,this._objects[r],n)){this.relatedTarget=this._objects[r],t=this._objects[r];break}return t},getPointer:function(t){var n=e(t,this.upperCanvasEl),r=this.upperCanvasEl.getBoundingClientRect(),i;return r.width===0||r.height===0?i={width:1,height:1}:i={width:this.upperCanvasEl.width/r.width,height:this.upperCanvasEl.height/r.height},{x:(n.x-this._offset.left)*i.width,y:(n.y-this._offset.top)*i.height}},_createUpperCanvas:function(){var e=this.lowerCanvasEl.className.replace(/\s*lower-canvas\s*/,"");this.upperCanvasEl=this._createCanvasElement(),fabric.util.addClass(this.upperCanvasEl,"upper-canvas "+e),this.wrapperEl.appendChild(this.upperCanvasEl),this._copyCanvasStyle(this.lowerCanvasEl,this.upperCanvasEl),this._applyCanvasStyle(this.upperCanvasEl),this.contextTop=this.upperCanvasEl.getContext("2d")},_createCacheCanvas:function(){this.cacheCanvasEl=this._createCanvasElement(),this.cacheCanvasEl.setAttribute("width",this.width),this.cacheCanvasEl.setAttribute("height",this.height),this.contextCache=this.cacheCanvasEl.getContext("2d")},_initWrapperElement:function(){this.wrapperEl=fabric.util.wrapElement(this.lowerCanvasEl,"div",{"class":this.containerClass}),fabric.util.setStyle(this.wrapperEl,{width:this.getWidth()+"px",height:this.getHeight()+"px",position:"relative"}),fabric.util.makeElementUnselectable(this.wrapperEl)},_applyCanvasStyle:function(e){var t=this.getWidth()||e.width,n=this.getHeight()||e.height;fabric.util.setStyle(e,{position:"absolute",width:t+"px",height:n+"px",left:0,top:0}),e.width=t,e.height=n,fabric.util.makeElementUnselectable(e)},_copyCanvasStyle:function(e,t){t.style.cssText=e.style.cssText},getSelectionContext:function(){return this.contextTop},getSelectionElement:function(){return this.upperCanvasEl},_setActiveObject:function(e){this._activeObject&&this._activeObject.set("active",!1),this._activeObject=e,e.set("active",!0)},setActiveObject:function(e,t){return this._setActiveObject(e),this.renderAll(),this.fire("object:selected",{target:e,e:t}),e.fire("selected",{e:t}),this},getActiveObject:function(){return this._activeObject},_discardActiveObject:function(){this._activeObject&&this._activeObject.set("active",!1),this._activeObject=null},discardActiveObject:function(e){return this._discardActiveObject(),this.renderAll(),this.fire("selection:cleared",{e:e}),this},_setActiveGroup:function(e){this._activeGroup=e,e&&(e.canvas=this,e.set("active",!0))},setActiveGroup:function(e,t){return this._setActiveGroup(e),e&&(this.fire("object:selected",{target:e,e:t}),e.fire("selected",{e:t})),this},getActiveGroup:function(){return this._activeGroup},_discardActiveGroup:function(){var e=this.getActiveGroup();e&&e.destroy(),this.setActiveGroup(null)},discardActiveGroup:function(e){return this._discardActiveGroup(),this.fire("selection:cleared",{e:e}),this},deactivateAll:function(){var e=this.getObjects(),t=0,n=e.length;for(;t1&&(t=new fabric.Group(t.reverse(),{originX:"center",originY:"center"}),this.setActiveGroup(t,e),t.saveCoords(),this.fire("selection:created",{target:t}),this.renderAll())},_collectObjects:function(){var n=[],r,i=this._groupSelector.ex,s=this._groupSelector.ey,o=i+this._groupSelector.left,u=s+this._groupSelector.top,a=new fabric.Point(e(i,o),e(s,u)),f=new fabric.Point(t(i,o),t(s,u)),l=i===o&&s===u;for(var c=this._objects.length;c--;){r=this._objects[c];if(!r||!r.selectable||!r.visible)continue;if(r.intersectsWithRect(a,f)||r.isContainedWithinRect(a,f)||r.containsPoint(a)||r.containsPoint(f)){r.set("active",!0),n.push(r);if(l)break}}return n},_maybeGroupObjects:function(e){this.selection&&this._groupSelector&&this._groupSelectedObjects(e);var t=this.getActiveGroup();t&&(t.setObjectsCoords().setCoords(),t.isMoving=!1,this._setCursor(this.defaultCursor)),this._groupSelector=null,this._currentTransform=null}})}(),fabric.util.object.extend(fabric.StaticCanvas.prototype,{toDataURL:function(e){e||(e={});var t=e.format||"png",n=e.quality||1,r=e.multiplier||1,i={left:e.left,top:e.top,width:e.width,height:e.height};return r!==1?this.__toDataURLWithMultiplier(t,n,i,r):this.__toDataURL(t,n,i)},__toDataURL:function(e,t,n){this.renderAll(!0);var r=this.upperCanvasEl||this.lowerCanvasEl,i=this.__getCroppedCanvas(r,n);e==="jpg"&&(e="jpeg");var s=fabric.StaticCanvas.supports("toDataURLWithQuality")?(i||r).toDataURL("image/"+e,t):(i||r).toDataURL("image/"+e);return this.contextTop&&this.clearContext(this.contextTop),this.renderAll(),i&&(i=null),s},__getCroppedCanvas:function(e,t){var n,r,i="left"in t||"top"in t||"width"in t||"height"in t;return i&&(n=fabric.util.createCanvasElement(),r=n.getContext("2d"),n.width=t.width||this.width,n.height=t.height||this.height,r.drawImage(e,-t.left||0,-t.top||0)),n},__toDataURLWithMultiplier:function(e,t,n,r){var i=this.getWidth(),s=this.getHeight(),o=i*r,u=s*r,a=this.getActiveObject(),f=this.getActiveGroup(),l=this.contextTop||this.contextContainer;r>1&&this.setWidth(o).setHeight(u),l.scale(r,r),n.left&&(n.left*=r),n.top&&(n.top*=r),n.width?n.width*=r:r<1&&(n.width=o),n.height?n.height*=r:r<1&&(n.height=u),f?this._tempRemoveBordersControlsFromGroup(f):a&&this.deactivateAll&&this.deactivateAll(),this.renderAll(!0);var c=this.__toDataURL(e,t,n);return this.width=i,this.height=s,l.scale(1/r,1/r),this.setWidth(i).setHeight(s),f?this._restoreBordersControlsOnGroup(f):a&&this.setActiveObject&&this.setActiveObject(a),this.contextTop&&this.clearContext(this.contextTop),this.renderAll(),c},toDataURLWithMultiplier:function(e,t,n){return this.toDataURL({format:e,multiplier:t,quality:n})},_tempRemoveBordersControlsFromGroup:function(e){e.origHasControls=e.hasControls,e.origBorderColor=e.borderColor,e.hasControls=!0,e.borderColor="rgba(0,0,0,0)",e.forEachObject(function(e){e.origBorderColor=e.borderColor,e.borderColor="rgba(0,0,0,0)"})},_restoreBordersControlsOnGroup:function(e){e.hideControls=e.origHideControls,e.borderColor=e.origBorderColor,e.forEachObject(function(e){e.borderColor=e.origBorderColor,delete e.origBorderColor})}}),fabric.util.object.extend(fabric.StaticCanvas.prototype,{loadFromDatalessJSON:function(e,t,n){return this.loadFromJSON(e,t,n)},loadFromJSON:function(e,t,n){if(!e)return;var r=typeof e=="string"?JSON.parse(e):e;this.clear();var i=this;return this._enlivenObjects(r.objects,function(){i._setBgOverlay(r,t)},n),this},_setBgOverlay:function(e,t){var n=this,r={backgroundColor:!1,overlayColor:!1,backgroundImage:!1,overlayImage:!1};if(!e.backgroundImage&&!e.overlayImage&&!e.background&&!e.overlay){t&&t();return}var i=function(){r.backgroundImage&&r.overlayImage&&r.backgroundColor&&r.overlayColor&&(n.renderAll(),t&&t())};this.__setBgOverlay("backgroundImage",e.backgroundImage,r,i),this.__setBgOverlay("overlayImage",e.overlayImage,r,i),this.__setBgOverlay("backgroundColor",e.background,r,i),this.__setBgOverlay("overlayColor",e.overlay,r,i),i()},__setBgOverlay:function(e,t,n,r){var i=this;if(!t){n[e]=!0;return}e==="backgroundImage"||e==="overlayImage"?fabric.Image.fromObject(t,function(t){i[e]=t,n[e]=!0,r&&r()}):this["set"+fabric.util.string.capitalize(e,!0)](t,function(){n[e]=!0,r&&r()})},_enlivenObjects:function(e,t,n){var r=this;if(!e||e.length===0){t&&t();return}var i=this.renderOnAddRemove;this.renderOnAddRemove=!1,fabric.util.enlivenObjects(e,function(e){e.forEach(function(e,t){r.insertAt(e,t,!0)}),r.renderOnAddRemove=i,t&&t()},null,n)},_toDataURL:function(e,t){this.clone(function(n){t(n.toDataURL(e))})},_toDataURLWithMultiplier:function(e,t,n){this.clone(function(r){n(r.toDataURLWithMultiplier(e,t))})},clone:function(e,t){var n=JSON.stringify(this.toJSON(t));this.cloneWithoutData(function(t){t.loadFromJSON(n,function(){e&&e(t)})})},cloneWithoutData:function(e){var t=fabric.document.createElement("canvas");t.width=this.getWidth(),t.height=this.getHeight();var n=new fabric.Canvas(t);n.clipTo=this.clipTo,this.backgroundImage?(n.setBackgroundImage(this.backgroundImage.src,function(){n.renderAll(),e&&e(n)}),n.backgroundImageOpacity=this.backgroundImageOpacity,n.backgroundImageStretch=this.backgroundImageStretch):e&&e(n)}}),function(e){"use strict";var t=e.fabric||(e.fabric={}),n=t.util.object.extend,r=t.util.toFixed,i=t.util.string.capitalize,s=t.util.degreesToRadians,o=t.StaticCanvas.supports("setLineDash");if(t.Object)return;t.Object=t.util.createClass({type:"object",originX:"left",originY:"top",top:0,left:0,width:0,height:0,scaleX:1,scaleY:1,flipX:!1,flipY:!1,opacity:1,angle:0,cornerSize:12,transparentCorners:!0,hoverCursor:null,padding:0,borderColor:"rgba(102,153,255,0.75)",cornerColor:"rgba(102,153,255,0.5)",centeredScaling:!1,centeredRotation:!0,fill:"rgb(0,0,0)",fillRule:"source-over",backgroundColor:"",stroke:null,strokeWidth:1,strokeDashArray:null,strokeLineCap:"butt",strokeLineJoin:"miter",strokeMiterLimit:10,shadow:null,borderOpacityWhenMoving:.4,borderScaleFactor:1,transformMatrix:null,minScaleLimit:.01,selectable:!0,evented:!0,visible:!0,hasControls:!0,hasBorders:!0,hasRotatingPoint:!0,rotatingPointOffset:40,perPixelTargetFind:!1,includeDefaultValues:!0,clipTo:null,lockMovementX:!1,lockMovementY:!1,lockRotation:!1,lockScalingX:!1,lockScalingY:!1,lockUniScaling:!1,stateProperties:"top left width height scaleX scaleY flipX flipY originX originY transformMatrix stroke strokeWidth strokeDashArray strokeLineCap strokeLineJoin strokeMiterLimit angle opacity fill fillRule shadow clipTo visible backgroundColor".split(" "),initialize:function(e){e&&this.setOptions(e)},_initGradient:function(e){e.fill&&e.fill.colorStops&&!(e.fill instanceof t.Gradient)&&this.set("fill",new t.Gradient(e.fill))},_initPattern:function(e){e.fill&&e.fill.source&&!(e.fill instanceof t.Pattern)&&this.set("fill",new t.Pattern(e.fill)),e.stroke&&e.stroke.source&&!(e.stroke instanceof t.Pattern)&&this.set("stroke",new t.Pattern(e.stroke))},_initClipping:function(e){if(!e.clipTo||typeof e.clipTo!="string")return;var n=t.util.getFunctionBody(e.clipTo);typeof n!="undefined"&&(this.clipTo=new Function("ctx",n))},setOptions:function(e){for(var t in e)this.set(t,e[t]);this._initGradient(e),this +._initPattern(e),this._initClipping(e)},transform:function(e,t){e.globalAlpha=this.opacity;var n=t?this._getLeftTopCoords():this.getCenterPoint();e.translate(n.x,n.y),e.rotate(s(this.angle)),e.scale(this.scaleX*(this.flipX?-1:1),this.scaleY*(this.flipY?-1:1))},toObject:function(e){var n=t.Object.NUM_FRACTION_DIGITS,i={type:this.type,originX:this.originX,originY:this.originY,left:r(this.left,n),top:r(this.top,n),width:r(this.width,n),height:r(this.height,n),fill:this.fill&&this.fill.toObject?this.fill.toObject():this.fill,stroke:this.stroke&&this.stroke.toObject?this.stroke.toObject():this.stroke,strokeWidth:r(this.strokeWidth,n),strokeDashArray:this.strokeDashArray,strokeLineCap:this.strokeLineCap,strokeLineJoin:this.strokeLineJoin,strokeMiterLimit:r(this.strokeMiterLimit,n),scaleX:r(this.scaleX,n),scaleY:r(this.scaleY,n),angle:r(this.getAngle(),n),flipX:this.flipX,flipY:this.flipY,opacity:r(this.opacity,n),shadow:this.shadow&&this.shadow.toObject?this.shadow.toObject():this.shadow,visible:this.visible,clipTo:this.clipTo&&String(this.clipTo),backgroundColor:this.backgroundColor};return this.includeDefaultValues||(i=this._removeDefaultValues(i)),t.util.populateWithProperties(this,i,e),i},toDatalessObject:function(e){return this.toObject(e)},_removeDefaultValues:function(e){var n=t.util.getKlass(e.type).prototype,r=n.stateProperties;return r.forEach(function(t){e[t]===n[t]&&delete e[t]}),e},toString:function(){return"#"},get:function(e){return this[e]},_setObject:function(e){for(var t in e)this._set(t,e[t])},set:function(e,t){return typeof e=="object"?this._setObject(e):typeof t=="function"&&e!=="clipTo"?this._set(e,t(this.get(e))):this._set(e,t),this},_set:function(e,n){var i=e==="scaleX"||e==="scaleY";return i&&(n=this._constrainScale(n)),e==="scaleX"&&n<0?(this.flipX=!this.flipX,n*=-1):e==="scaleY"&&n<0?(this.flipY=!this.flipY,n*=-1):e==="width"||e==="height"?this.minScaleLimit=r(Math.min(.1,1/Math.max(this.width,this.height)),2):e==="shadow"&&n&&!(n instanceof t.Shadow)&&(n=new t.Shadow(n)),this[e]=n,this},toggle:function(e){var t=this.get(e);return typeof t=="boolean"&&this.set(e,!t),this},setSourcePath:function(e){return this.sourcePath=e,this},render:function(e,n){if(this.width===0||this.height===0||!this.visible)return;e.save(),this._setupFillRule(e),this._transform(e,n),this._setStrokeStyles(e),this._setFillStyles(e);var r=this.transformMatrix;r&&this.group&&(e.translate(-this.group.width/2,-this.group.height/2),e.transform(r[0],r[1],r[2],r[3],r[4],r[5])),this._setShadow(e),this.clipTo&&t.util.clipContext(this,e),this._render(e,n),this.clipTo&&e.restore(),this._removeShadow(e),this._restoreFillRule(e),this.active&&!n&&(this.drawBorders(e),this.drawControls(e)),e.restore()},_transform:function(e,t){var n=this.transformMatrix;n&&!this.group&&e.setTransform(n[0],n[1],n[2],n[3],n[4],n[5]),t||this.transform(e)},_setStrokeStyles:function(e){this.stroke&&(e.lineWidth=this.strokeWidth,e.lineCap=this.strokeLineCap,e.lineJoin=this.strokeLineJoin,e.miterLimit=this.strokeMiterLimit,e.strokeStyle=this.stroke.toLive?this.stroke.toLive(e):this.stroke)},_setFillStyles:function(e){this.fill&&(e.fillStyle=this.fill.toLive?this.fill.toLive(e):this.fill)},_setShadow:function(e){if(!this.shadow)return;e.shadowColor=this.shadow.color,e.shadowBlur=this.shadow.blur,e.shadowOffsetX=this.shadow.offsetX,e.shadowOffsetY=this.shadow.offsetY},_removeShadow:function(e){if(!this.shadow)return;e.shadowColor="",e.shadowBlur=e.shadowOffsetX=e.shadowOffsetY=0},_renderFill:function(e){if(!this.fill)return;this.fill.toLive&&(e.save(),e.translate(-this.width/2+this.fill.offsetX||0,-this.height/2+this.fill.offsetY||0)),this.fillRule==="destination-over"?e.fill("evenodd"):e.fill(),this.fill.toLive&&e.restore(),this.shadow&&!this.shadow.affectStroke&&this._removeShadow(e)},_renderStroke:function(e){if(!this.stroke)return;e.save(),this.strokeDashArray?(1&this.strokeDashArray.length&&this.strokeDashArray.push.apply(this.strokeDashArray,this.strokeDashArray),o?(e.setLineDash(this.strokeDashArray),this._stroke&&this._stroke(e)):this._renderDashedStroke&&this._renderDashedStroke(e),e.stroke()):this._stroke?this._stroke(e):e.stroke(),this._removeShadow(e),e.restore()},clone:function(e,n){return this.constructor.fromObject?this.constructor.fromObject(this.toObject(n),e):new t.Object(this.toObject(n))},cloneAsImage:function(e){var n=this.toDataURL();return t.util.loadImage(n,function(n){e&&e(new t.Image(n))}),this},toDataURL:function(e){e||(e={});var n=t.util.createCanvasElement(),r=this.getBoundingRect();n.width=r.width,n.height=r.height,t.util.wrapElement(n,"div");var i=new t.Canvas(n);e.format==="jpg"&&(e.format="jpeg"),e.format==="jpeg"&&(i.backgroundColor="#fff");var s={active:this.get("active"),left:this.getLeft(),top:this.getTop()};this.set("active",!1),this.setPositionByOrigin(new t.Point(n.width/2,n.height/2),"center","center");var o=this.canvas;i.add(this);var u=i.toDataURL(e);return this.set(s).setCoords(),this.canvas=o,i.dispose(),i=null,u},isType:function(e){return this.type===e},complexity:function(){return 0},toJSON:function(e){return this.toObject(e)},setGradient:function(e,n){n||(n={});var r={colorStops:[]};r.type=n.type||(n.r1||n.r2?"radial":"linear"),r.coords={x1:n.x1,y1:n.y1,x2:n.x2,y2:n.y2};if(n.r1||n.r2)r.coords.r1=n.r1,r.coords.r2=n.r2;for(var i in n.colorStops){var s=new t.Color(n.colorStops[i]);r.colorStops.push({offset:i,color:s.toRgb(),opacity:s.getAlpha()})}return this.set(e,t.Gradient.forObject(this,r))},setPatternFill:function(e){return this.set("fill",new t.Pattern(e))},setShadow:function(e){return this.set("shadow",new t.Shadow(e))},setColor:function(e){return this.set("fill",e),this},setAngle:function(e){var t=(this.originX!=="center"||this.originY!=="center")&&this.centeredRotation;return t&&this._setOriginToCenter(),this.set("angle",e),t&&this._resetOrigin(),this},centerH:function(){return this.canvas.centerObjectH(this),this},centerV:function(){return this.canvas.centerObjectV(this),this},center:function(){return this.canvas.centerObject(this),this},remove:function(){return this.canvas.remove(this),this},getLocalPointer:function(e,t){t=t||this.canvas.getPointer(e);var n=this.translateToOriginPoint(this.getCenterPoint(),"left","top");return{x:t.x-n.x,y:t.y-n.y}},_setupFillRule:function(e){this.fillRule&&(this._prevFillRule=e.globalCompositeOperation,e.globalCompositeOperation=this.fillRule)},_restoreFillRule:function(e){this.fillRule&&this._prevFillRule&&(e.globalCompositeOperation=this._prevFillRule)}}),t.util.createAccessors(t.Object),t.Object.prototype.rotate=t.Object.prototype.setAngle,n(t.Object.prototype,t.Observable),t.Object.NUM_FRACTION_DIGITS=2,t.Object.__uid=0}(typeof exports!="undefined"?exports:this),function(){var e=fabric.util.degreesToRadians;fabric.util.object.extend(fabric.Object.prototype,{translateToCenterPoint:function(t,n,r){var i=t.x,s=t.y,o=this.stroke?this.strokeWidth:0;return n==="left"?i=t.x+(this.getWidth()+o*this.scaleX)/2:n==="right"&&(i=t.x-(this.getWidth()+o*this.scaleX)/2),r==="top"?s=t.y+(this.getHeight()+o*this.scaleY)/2:r==="bottom"&&(s=t.y-(this.getHeight()+o*this.scaleY)/2),fabric.util.rotatePoint(new fabric.Point(i,s),t,e(this.angle))},translateToOriginPoint:function(t,n,r){var i=t.x,s=t.y,o=this.stroke?this.strokeWidth:0;return n==="left"?i=t.x-(this.getWidth()+o*this.scaleX)/2:n==="right"&&(i=t.x+(this.getWidth()+o*this.scaleX)/2),r==="top"?s=t.y-(this.getHeight()+o*this.scaleY)/2:r==="bottom"&&(s=t.y+(this.getHeight()+o*this.scaleY)/2),fabric.util.rotatePoint(new fabric.Point(i,s),t,e(this.angle))},getCenterPoint:function(){var e=new fabric.Point(this.left,this.top);return this.translateToCenterPoint(e,this.originX,this.originY)},getPointByOrigin:function(e,t){var n=this.getCenterPoint();return this.translateToOriginPoint(n,e,t)},toLocalPoint:function(t,n,r){var i=this.getCenterPoint(),s=this.stroke?this.strokeWidth:0,o,u;return n&&r?(n==="left"?o=i.x-(this.getWidth()+s*this.scaleX)/2:n==="right"?o=i.x+(this.getWidth()+s*this.scaleX)/2:o=i.x,r==="top"?u=i.y-(this.getHeight()+s*this.scaleY)/2:r==="bottom"?u=i.y+(this.getHeight()+s*this.scaleY)/2:u=i.y):(o=this.left,u=this.top),fabric.util.rotatePoint(new fabric.Point(t.x,t.y),i,-e(this.angle)).subtractEquals(new fabric.Point(o,u))},setPositionByOrigin:function(e,t,n){var r=this.translateToCenterPoint(e,t,n),i=this.translateToOriginPoint(r,this.originX,this.originY);this.set("left",i.x),this.set("top",i.y)},adjustPosition:function(t){var n=e(this.angle),r=this.getWidth()/2,i=Math.cos(n)*r,s=Math.sin(n)*r,o=this.getWidth(),u=Math.cos(n)*o,a=Math.sin(n)*o;this.originX==="center"&&t==="left"||this.originX==="right"&&t==="center"?(this.left-=i,this.top-=s):this.originX==="left"&&t==="center"||this.originX==="center"&&t==="right"?(this.left+=i,this.top+=s):this.originX==="left"&&t==="right"?(this.left+=u,this.top+=a):this.originX==="right"&&t==="left"&&(this.left-=u,this.top-=a),this.setCoords(),this.originX=t},_setOriginToCenter:function(){this._originalOriginX=this.originX,this._originalOriginY=this.originY;var e=this.getCenterPoint();this.originX="center",this.originY="center",this.left=e.x,this.top=e.y},_resetOrigin:function(){var e=this.translateToOriginPoint(this.getCenterPoint(),this._originalOriginX,this._originalOriginY);this.originX=this._originalOriginX,this.originY=this._originalOriginY,this.left=e.x,this.top=e.y,this._originalOriginX=null,this._originalOriginY=null},_getLeftTopCoords:function(){return this.translateToOriginPoint(this.getCenterPoint(),"left","center")}})}(),function(){var e=fabric.util.degreesToRadians;fabric.util.object.extend(fabric.Object.prototype,{oCoords:null,intersectsWithRect:function(e,t){var n=this.oCoords,r=new fabric.Point(n.tl.x,n.tl.y),i=new fabric.Point(n.tr.x,n.tr.y),s=new fabric.Point(n.bl.x,n.bl.y),o=new fabric.Point(n.br.x,n.br.y),u=fabric.Intersection.intersectPolygonRectangle([r,i,o,s],e,t);return u.status==="Intersection"},intersectsWithObject:function(e){function t(e){return{tl:new fabric.Point(e.tl.x,e.tl.y),tr:new fabric.Point(e.tr.x,e.tr.y),bl:new fabric.Point(e.bl.x,e.bl.y),br:new fabric.Point(e.br.x,e.br.y)}}var n=t(this.oCoords),r=t(e.oCoords),i=fabric.Intersection.intersectPolygonPolygon([n.tl,n.tr,n.br,n.bl],[r.tl,r.tr,r.br,r.bl]);return i.status==="Intersection"},isContainedWithinObject:function(e){var t=e.getBoundingRect(),n=new fabric.Point(t.left,t.top),r=new fabric.Point(t.left+t.width,t.top+t.height);return this.isContainedWithinRect(n,r)},isContainedWithinRect:function(e,t){var n=this.getBoundingRect();return n.left>=e.x&&n.left+n.width<=t.x&&n.top>=e.y&&n.top+n.height<=t.y},containsPoint:function(e){var t=this._getImageLines(this.oCoords),n=this._findCrossPoints(e,t);return n!==0&&n%2===1},_getImageLines:function(e){return{topline:{o:e.tl,d:e.tr},rightline:{o:e.tr,d:e.br},bottomline:{o:e.br,d:e.bl},leftline:{o:e.bl,d:e.tl}}},_findCrossPoints:function(e,t){var n,r,i,s,o,u,a=0,f;for(var l in t){f=t[l];if(f.o.y=e.y&&f.d.y>=e.y)continue;f.o.x===f.d.x&&f.o.x>=e.x?(o=f.o.x,u=e.y):(n=0,r=(f.d.y-f.o.y)/(f.d.x-f.o.x),i=e.y-n*e.x,s=f.o.y-r*f.o.x,o=-(i-s)/(n-r),u=i+n*o),o>=e.x&&(a+=1);if(a===2)break}return a},getBoundingRectWidth:function(){return this.getBoundingRect().width},getBoundingRectHeight:function(){return this.getBoundingRect().height},getBoundingRect:function(){this.oCoords||this.setCoords();var e=[this.oCoords.tl.x,this.oCoords.tr.x,this.oCoords.br.x,this.oCoords.bl.x],t=fabric.util.array.min(e),n=fabric.util.array.max(e),r=Math.abs(t-n),i=[this.oCoords.tl.y,this.oCoords.tr.y,this.oCoords.br.y,this.oCoords.bl.y],s=fabric.util.array.min(i),o=fabric.util.array.max(i),u=Math.abs(s-o);return{left:t,top:s,width:r,height:u}},getWidth:function(){return this.width*this.scaleX},getHeight:function(){return this.height*this.scaleY},_constrainScale:function(e){return Math.abs(e)1?this.strokeWidth:0,n=this.padding,r=e(this.angle);this.currentWidth=(this.width+t)*this.scaleX+n*2,this.currentHeight=(this.height+t)*this.scaleY+n*2,this.currentWidth<0&&(this.currentWidth=Math.abs(this.currentWidth));var i=Math.sqrt(Math.pow(this.currentWidth/2,2)+Math.pow(this.currentHeight/2,2)),s=Math.atan(isFinite(this.currentHeight/this.currentWidth)?this.currentHeight/this.currentWidth:0),o=Math.cos(s+r)*i,u=Math.sin(s+r)*i,a=Math.sin(r),f=Math.cos(r),l=this.getCenterPoint(),c={x:l.x-o,y:l.y-u},h={x:c.x+this.currentWidth*f,y:c.y+this.currentWidth*a},p={x:h.x-this.currentHeight*a,y:h.y+this.currentHeight*f},d={x:c.x-this.currentHeight*a,y:c.y+this.currentHeight*f},v={x:c.x-this.currentHeight/2*a,y:c.y+this.currentHeight/2*f},m={x:c.x+this.currentWidth/2*f,y:c.y+this.currentWidth/2*a},g={x:h.x-this.currentHeight/2*a,y:h.y+this.currentHeight/2*f},y={x:d.x+this.currentWidth/2*f,y:d.y+this.currentWidth/2*a},b={x:m.x,y:m.y};return this.oCoords={tl:c,tr:h,br:p,bl:d,ml:v,mt:m,mr:g,mb:y,mtr:b},this._setCornerCoords&&this._setCornerCoords(),this}})}(),fabric.util.object.extend(fabric.Object.prototype,{sendToBack:function(){return this.group?fabric.StaticCanvas.prototype.sendToBack.call(this.group,this):this.canvas.sendToBack(this),this},bringToFront:function(){return this.group?fabric.StaticCanvas.prototype.bringToFront.call(this.group,this):this.canvas.bringToFront(this),this},sendBackwards:function(e){return this.group?fabric.StaticCanvas.prototype.sendBackwards.call(this.group,this,e):this.canvas.sendBackwards(this,e),this},bringForward:function(e){return this.group?fabric.StaticCanvas.prototype.bringForward.call(this.group,this,e):this.canvas.bringForward(this,e),this},moveTo:function(e){return this.group?fabric.StaticCanvas.prototype.moveTo.call(this.group,this,e):this.canvas.moveTo(this,e),this}}),fabric.util.object.extend(fabric.Object.prototype,{getSvgStyles:function(){var e=this.fill?this.fill.toLive?"url(#SVGID_"+this.fill.id+")":this.fill:"none",t=this.stroke?this.stroke.toLive?"url(#SVGID_"+this.stroke.id+")":this.stroke:"none",n=this.strokeWidth?this.strokeWidth:"0",r=this.strokeDashArray?this.strokeDashArray.join(" "):"",i=this.strokeLineCap?this.strokeLineCap:"butt",s=this.strokeLineJoin?this.strokeLineJoin:"miter",o=this.strokeMiterLimit?this.strokeMiterLimit:"4",u=typeof this.opacity!="undefined"?this.opacity:"1",a=this.visible?"":" visibility: hidden;",f=this.shadow&&this.type!=="text"?"filter: url(#SVGID_"+this.shadow.id+");":"";return["stroke: ",t,"; ","stroke-width: ",n,"; ","stroke-dasharray: ",r,"; ","stroke-linecap: ",i,"; ","stroke-linejoin: ",s,"; ","stroke-miterlimit: ",o,"; ","fill: ",e,"; ","opacity: ",u,";",f,a].join("")},getSvgTransform:function(){var e=fabric.util.toFixed,t=this.getAngle(),n=this.getCenterPoint(),r=fabric.Object.NUM_FRACTION_DIGITS,i="translate("+e(n.x,r)+" "+e(n.y,r)+")",s=t!==0?" rotate("+e(t,r)+")":"",o=this.scaleX===1&&this.scaleY===1?"":" scale("+e(this.scaleX,r)+" "+e(this.scaleY,r)+")",u=this.flipX?"matrix(-1 0 0 1 0 0) ":"",a=this.flipY?"matrix(1 0 0 -1 0 0)":"";return[i,s,o,u,a].join("")},_createBaseSVGMarkup:function(){var e=[];return this.fill&&this.fill.toLive&&e.push(this.fill.toSVG(this,!1)),this.stroke&&this.stroke.toLive&&e.push(this.stroke.toSVG(this,!1)),this.shadow&&e.push(this.shadow.toSVG(this)),e}}),fabric.util.object.extend(fabric.Object.prototype,{hasStateChanged:function(){return this.stateProperties.some(function(e){return this.get(e)!==this.originalState[e]},this)},saveState:function(e){return this.stateProperties.forEach(function(e){this.originalState[e]=this.get(e)},this),e&&e.stateProperties&&e.stateProperties.forEach(function(e){this.originalState[e]=this.get(e)},this),this},setupState:function(){return this.originalState={},this.saveState(),this}}),function(){var e=fabric.util.degreesToRadians,t=typeof G_vmlCanvasManager!="undefined";fabric.util.object.extend(fabric.Object.prototype,{_controlsVisibility:null,_findTargetCorner:function(e){if(!this.hasControls||!this.active)return!1;var t=e.x,n=e.y,r,i;for(var s in this.oCoords){if(!this.isControlVisible(s))continue;if(s==="mtr"&&!this.hasRotatingPoint)continue;if(!(!this.get("lockUniScaling")||s!=="mt"&&s!=="mr"&&s!=="mb"&&s!=="ml"))continue;i=this._getImageLines(this.oCoords[s].corner),r=this._findCrossPoints({x:t,y:n},i);if(r!==0&&r%2===1)return this.__corner=s,s}return!1},_setCornerCoords:function(){var t=this.oCoords,n=e(this.angle),r=e(45-this.angle),i=Math.sqrt(2*Math.pow(this.cornerSize,2))/2,s=i*Math.cos(r),o=i*Math.sin(r),u=Math.sin(n),a=Math.cos(n);t.tl.corner={tl:{x:t.tl.x-o,y:t.tl.y-s},tr:{x:t.tl.x+s,y:t.tl.y-o},bl:{x:t.tl.x-s,y:t.tl.y+o},br:{x:t.tl.x+o,y:t.tl.y+s}},t.tr.corner={tl:{x:t.tr.x-o,y:t.tr.y-s},tr:{x:t.tr.x+s,y:t.tr.y-o},br:{x:t.tr.x+o,y:t.tr.y+s},bl:{x:t.tr.x-s,y:t.tr.y+o}},t.bl.corner={tl:{x:t.bl.x-o,y:t.bl.y-s},bl:{x:t.bl.x-s,y:t.bl.y+o},br:{x:t.bl.x+o,y:t.bl.y+s},tr:{x:t.bl.x+s,y:t.bl.y-o}},t.br.corner={tr:{x:t.br.x+s,y:t.br.y-o},bl:{x:t.br.x-s,y:t.br.y+o},br:{x:t.br.x+o,y:t.br.y+s},tl:{x:t.br.x-o,y:t.br.y-s}},t.ml.corner={tl:{x:t.ml.x-o,y:t.ml.y-s},tr:{x:t.ml.x+s,y:t.ml.y-o},bl:{x:t.ml.x-s,y:t.ml.y+o},br:{x:t.ml.x+o,y:t.ml.y+s}},t.mt.corner={tl:{x:t.mt.x-o,y:t.mt.y-s},tr:{x:t.mt.x+s,y:t.mt.y-o},bl:{x:t.mt.x-s,y:t.mt.y+o},br:{x:t.mt.x+o,y:t.mt.y+s}},t.mr.corner={tl:{x:t.mr.x-o,y:t.mr.y-s},tr:{x:t.mr.x+s,y:t.mr.y-o},bl:{x:t.mr.x-s,y:t.mr.y+o},br:{x:t.mr.x+o,y:t.mr.y+s}},t.mb.corner={tl:{x:t.mb.x-o,y:t.mb.y-s},tr:{x:t.mb.x+s,y:t.mb.y-o},bl:{x:t.mb.x-s,y:t.mb.y+o},br:{x:t.mb.x+o,y:t.mb.y+s}},t.mtr.corner={tl:{x:t.mtr.x-o+u*this.rotatingPointOffset,y:t.mtr.y-s-a*this.rotatingPointOffset},tr:{x:t.mtr.x+s+u*this.rotatingPointOffset,y:t.mtr.y-o-a*this.rotatingPointOffset},bl:{x:t.mtr.x-s+u*this.rotatingPointOffset,y:t.mtr.y+o-a*this.rotatingPointOffset},br:{x:t.mtr.x+o+u*this.rotatingPointOffset,y:t.mtr.y+s-a*this.rotatingPointOffset}}},drawBorders:function(e){if(!this.hasBorders)return this;var t=this.padding,n=t*2,r=~~(this.strokeWidth/2)*2;e.save(),e.globalAlpha=this.isMoving?this.borderOpacityWhenMoving:1,e.strokeStyle=this.borderColor;var i=1/this._constrainScale(this.scaleX),s=1/this._constrainScale(this.scaleY);e.lineWidth=1/this.borderScaleFactor,e.scale(i,s);var o=this.getWidth(),u=this.getHeight();e.strokeRect(~~(-(o/2)-t-r/2*this.scaleX)-.5,~~(-(u/2)-t-r/2*this.scaleY)-.5,~~(o+n+r*this.scaleX)+1,~~(u+n+r*this.scaleY)+1);if(this.hasRotatingPoint&&this.isControlVisible("mtr")&&!this.get("lockRotation")&&this.hasControls){var a=(this.flipY?u+r*this.scaleY+t*2:-u-r*this.scaleY-t*2)/2;e.beginPath(),e.moveTo(0,a),e.lineTo(0,a+(this.flipY?this.rotatingPointOffset:-this.rotatingPointOffset)),e.closePath(),e.stroke()}return e.restore(),this},drawControls:function(e){if(!this.hasControls)return this;var t=this.cornerSize,n=t/2,r=~~(this.strokeWidth/2),i=-(this.width/2),s=-(this.height/2),o=this.padding/this.scaleX,u=this.padding/this.scaleY,a=n/this.scaleY,f=n/this.scaleX,l=(n-t)/this.scaleX,c=(n-t)/this.scaleY,h=this.height,p=this.width,d=this.transparentCorners?"strokeRect":"fillRect";return e.save(),e.lineWidth=1/Math.max(this.scaleX,this.scaleY),e.globalAlpha=this.isMoving?this.borderOpacityWhenMoving:1,e.strokeStyle=e.fillStyle=this.cornerColor,this._drawControl("tl",e,d,i-f-r-o,s-a-r-u),this._drawControl("tr",e,d,i+p-f+r+o,s-a-r-u),this._drawControl("bl",e,d,i-f-r-o,s+h+c+r+u),this._drawControl("br",e,d,i+p+l+r+o,s+h+c+r+u),this.get("lockUniScaling")||(this._drawControl("mt",e,d,i+p/2-f,s-a-r-u),this._drawControl("mb",e,d,i+p/2-f,s+h+c+r+u),this._drawControl("mr",e,d,i+p+l+r+o,s+h/2-a),this._drawControl("ml",e,d,i-f-r-o,s+h/2-a)),this.hasRotatingPoint&&this._drawControl("mtr",e,d,i+p/2-f,this.flipY?s+h+this.rotatingPointOffset/this.scaleY-this.cornerSize/this.scaleX/2+r+u:s-this.rotatingPointOffset/this.scaleY-this.cornerSize/this.scaleY/2-r-u),e.restore(),this},_drawControl:function(e,n,r,i,s){var o=this.cornerSize/this.scaleX,u=this.cornerSize/this.scaleY;this.isControlVisible(e)&&(t||this.transparentCorners||n.clearRect(i,s,o,u),n[r](i,s,o,u))},isControlVisible:function(e){return this._getControlsVisibility()[e]},setControlVisible:function(e,t){return this._getControlsVisibility()[e]=t,this},setControlsVisibility:function(e){e||(e={});for(var t in e)this.setControlVisible(t,e[t]);return this},_getControlsVisibility:function(){return this._controlsVisibility||(this._controlsVisibility={tl:!0,tr:!0,br:!0,bl:!0,ml:!0,mt:!0,mr:!0,mb:!0,mtr:!0}),this._controlsVisibility}})}(),fabric.util.object.extend(fabric.StaticCanvas.prototype,{FX_DURATION:500,fxCenterObjectH:function(e,t){t=t||{};var n=function(){},r=t.onComplete||n,i=t.onChange||n,s=this;return fabric.util.animate({startValue:e.get("left"),endValue:this.getCenter().left,duration:this.FX_DURATION,onChange:function(t){e.set("left",t),s.renderAll(),i()},onComplete:function(){e.setCoords(),r()}}),this},fxCenterObjectV:function(e,t){t=t||{};var n=function(){},r=t.onComplete||n,i=t.onChange||n,s=this;return fabric.util.animate({startValue:e.get("top"),endValue:this.getCenter().top,duration:this.FX_DURATION,onChange:function(t){e.set("top",t),s.renderAll(),i()},onComplete:function(){e.setCoords(),r()}}),this},fxRemove:function(e,t){t=t||{};var n=function(){},r=t.onComplete||n,i=t.onChange||n,s=this;return fabric.util.animate({startValue:e.get("opacity"),endValue:0,duration:this.FX_DURATION,onStart:function(){e.set("active",!1)},onChange:function(t){e.set("opacity",t),s.renderAll(),i()},onComplete:function(){s.remove(e),r()}}),this}}),fabric.util.object.extend(fabric.Object.prototype,{animate:function(){if(arguments[0]&&typeof arguments[0]=="object"){var e=[],t,n;for(t in arguments[0])e.push(t);for(var r=0,i=e.length;r'),e?e(t.join("")):t.join("")},complexity:function(){return 1}}),t.Line.ATTRIBUTE_NAMES=t.SHARED_ATTRIBUTES.concat("x1 y1 x2 y2".split(" ")),t.Line.fromElement=function(e,r){var i=t.parseAttributes(e,t.Line.ATTRIBUTE_NAMES),s=[i.x1||0,i.y1||0,i.x2||0,i.y2||0];return new t.Line(s,n(i,r))},t.Line.fromObject=function(e){var n=[e.x1,e.y1,e.x2,e.y2];return new t.Line(n,e)}}(typeof exports!="undefined"?exports:this),function(e){"use strict";function i(e){return"radius"in e&&e.radius>0}var t=e.fabric||(e.fabric={}),n=Math.PI*2,r=t.util.object.extend;if(t.Circle){t.warn("fabric.Circle is already defined.");return}t.Circle=t.util.createClass(t.Object,{type:"circle",radius:0,initialize:function(e){e=e||{},this.set("radius",e.radius||0),this.callSuper("initialize",e)},_set:function(e,t){return this.callSuper("_set",e,t),e==="radius"&&this.setRadius(t),this},toObject:function(e){return r(this.callSuper("toObject",e),{radius:this.get("radius")})},toSVG:function(e){var t=this._createBaseSVGMarkup();return t.push("'),e?e(t.join("")):t.join("")},_render:function(e,t){e.beginPath(),e.globalAlpha=this.group?e.globalAlpha*this.opacity:this.opacity,e.arc(t?this.left:0,t?this.top:0,this.radius,0,n,!1),e.closePath(),this._renderFill(e),this.stroke&&this._renderStroke(e)},getRadiusX:function(){return this.get("radius")*this.get("scaleX")},getRadiusY:function(){return this.get("radius")*this.get("scaleY")},setRadius:function(e){this.radius=e,this.set("width",e*2).set("height",e*2)},complexity:function(){return 1}}),t.Circle.ATTRIBUTE_NAMES=t.SHARED_ATTRIBUTES.concat("cx cy r".split(" ")),t.Circle.fromElement=function(e,n){n||(n={});var s=t.parseAttributes(e,t.Circle.ATTRIBUTE_NAMES);if(!i(s))throw new Error("value of `r` attribute is required and can not be negative");"left"in s&&(s.left-=n.width/2||0),"top"in s&&(s.top-=n.height/2||0);var o=new t.Circle(r(s,n));return o.cx=parseFloat(e.getAttribute("cx"))||0,o.cy=parseFloat(e.getAttribute("cy"))||0,o},t.Circle.fromObject=function(e){return new t.Circle(e)}}(typeof exports!="undefined"?exports:this),function(e){"use strict";var t=e.fabric||(e.fabric={});if(t.Triangle){t.warn("fabric.Triangle is already defined");return}t.Triangle=t.util.createClass(t.Object,{type:"triangle",initialize:function(e){e=e||{},this.callSuper("initialize",e),this.set("width",e.width||100).set("height",e.height||100)},_render:function(e){var t=this.width/2,n=this.height/2;e.beginPath(),e.moveTo(-t,n),e.lineTo(0,-n),e.lineTo(t,n),e.closePath(),this._renderFill(e),this._renderStroke(e)},_renderDashedStroke:function(e){var n=this.width/2,r=this.height/2;e.beginPath(),t.util.drawDashedLine(e,-n,r,0,-r,this.strokeDashArray),t.util.drawDashedLine(e,0,-r,n,r,this.strokeDashArray),t.util.drawDashedLine(e,n,r,-n,r,this.strokeDashArray),e.closePath()},toSVG:function(e){var t=this._createBaseSVGMarkup(),n=this.width/2,r=this.height/2,i=[-n+" "+r,"0 "+ -r,n+" "+r].join(",");return t.push("'),e?e(t.join("")):t.join("")},complexity:function(){return 1}}),t.Triangle.fromObject=function(e){return new t.Triangle(e)}}(typeof exports!="undefined"?exports:this),function(e){"use strict";var t=e.fabric||(e.fabric={}),n=Math.PI*2,r=t.util.object.extend;if(t.Ellipse){t.warn("fabric.Ellipse is already defined.");return}t.Ellipse=t.util.createClass(t.Object,{type:"ellipse",rx:0,ry:0,initialize:function(e){e=e||{},this.callSuper("initialize",e),this.set("rx",e.rx||0),this.set("ry",e.ry||0),this.set("width",this.get("rx")*2),this.set("height",this.get("ry")*2)},toObject:function(e){return r(this.callSuper("toObject",e),{rx:this.get("rx"),ry:this.get("ry")})},toSVG:function(e){var t=this._createBaseSVGMarkup();return t.push("'),e?e(t.join("")):t.join("")},render:function(e,t){if(this.rx===0||this.ry===0)return;return this.callSuper("render",e,t)},_render:function(e,t){e.beginPath(),e.save(),e.globalAlpha=this.group?e.globalAlpha*this.opacity:this.opacity,this.transformMatrix&&this.group&&e.translate(this.cx,this.cy),e.transform(1,0,0,this.ry/this.rx,0,0),e.arc(t?this.left:0,t?this.top:0,this.rx,0,n,!1),e.restore(),this._renderFill(e),this._renderStroke(e)},complexity:function(){return 1}}),t.Ellipse.ATTRIBUTE_NAMES=t.SHARED_ATTRIBUTES.concat("cx cy rx ry".split(" ")),t.Ellipse.fromElement=function(e,n){n||(n={});var i=t.parseAttributes(e,t.Ellipse.ATTRIBUTE_NAMES),s=i.left,o=i.top;"left"in i&&(i.left-=n.width/2||0),"top"in i&&(i.top-=n.height/2||0);var u=new t.Ellipse(r(i,n));return u.cx=s||0,u.cy=o||0,u},t.Ellipse.fromObject=function(e){return new t.Ellipse(e)}}(typeof exports!="undefined"?exports:this),function(e){"use strict";function i(e){return e.left=e.left||0,e.top=e.top||0,e}var t=e.fabric||(e.fabric={}),n=t.util.object.extend;if(t.Rect){console.warn("fabric.Rect is already defined");return}var r=t.Object.prototype.stateProperties.concat();r.push("rx","ry","x","y"),t.Rect=t.util.createClass(t.Object,{stateProperties:r,type:"rect",rx:0,ry:0,x:0,y:0,strokeDashArray:null,initialize:function(e){e=e||{},this.callSuper("initialize",e),this._initRxRy(),this.x=e.x||0,this.y=e.y||0},_initRxRy:function(){this.rx&&!this.ry?this.ry=this.rx:this.ry&&!this.rx&&(this.rx=this.ry)},_render:function(e){if(this.width===1&&this.height===1){e.fillRect(0,0,1,1);return}var t=this.rx?Math.min(this.rx,this.width/2):0,n=this.ry?Math.min(this.ry,this.height/2):0,r=this.width,i=this.height,s=-r/2,o=-i/2,u=this.group&&this.group.type==="path-group",a=t!==0||n!==0,f=.4477152502;e.beginPath(),e.globalAlpha=u?e.globalAlpha*this.opacity:this.opacity,this.transformMatrix&&u&&e.translate(this.width/2+this.x,this.height/2+this.y),!this.transformMatrix&&u&&e.translate(-this.group.width/2+this.width/2+this.x,-this.group.height/2+this.height/2+this.y),e.moveTo(s+t,o),e.lineTo(s+r-t,o),a&&e.bezierCurveTo(s+r-f*t,o,s+r,o+f*n,s+r,o+n),e.lineTo(s+r,o+i-n),a&&e.bezierCurveTo(s+r,o+i-f*n,s+r-f*t,o+i,s+r-t,o+i),e.lineTo(s+t,o+i),a&&e.bezierCurveTo(s+f*t,o+i,s,o+i-f*n,s,o+i-n),e.lineTo(s,o+n),a&&e.bezierCurveTo(s,o+f*n,s+f*t,o,s+t,o),e.closePath(),this._renderFill(e),this._renderStroke(e)},_renderDashedStroke:function(e){var n=-this.width/2,r=-this.height/2,i=this.width,s=this.height;e.beginPath(),t.util.drawDashedLine(e,n,r,n+i,r,this.strokeDashArray),t.util.drawDashedLine(e,n+i,r,n+i,r+s,this.strokeDashArray),t.util.drawDashedLine(e,n+i,r+s,n,r+s,this.strokeDashArray),t.util.drawDashedLine(e,n,r+s,n,r,this.strokeDashArray),e.closePath()},_normalizeLeftTopProperties:function(e){return"left"in e&&this.set("left",e.left+this.getWidth()/2),this.set("x",e.left||0),"top"in e&&this.set("top",e.top+this.getHeight()/2),this.set("y",e.top||0),this},toObject:function(e){var t=n(this.callSuper("toObject",e),{rx:this.get("rx")||0,ry:this.get("ry")||0,x:this.get("x"),y:this.get("y")});return this.includeDefaultValues||this._removeDefaultValues(t),t},toSVG:function(e){var t=this._createBaseSVGMarkup();return t.push("'),e?e(t.join +("")):t.join("")},complexity:function(){return 1}}),t.Rect.ATTRIBUTE_NAMES=t.SHARED_ATTRIBUTES.concat("x y rx ry width height".split(" ")),t.Rect.fromElement=function(e,r){if(!e)return null;var s=t.parseAttributes(e,t.Rect.ATTRIBUTE_NAMES);s=i(s);var o=new t.Rect(n(r?t.util.object.clone(r):{},s));return o._normalizeLeftTopProperties(s),o},t.Rect.fromObject=function(e){return new t.Rect(e)}}(typeof exports!="undefined"?exports:this),function(e){"use strict";var t=e.fabric||(e.fabric={}),n=t.util.toFixed;if(t.Polyline){t.warn("fabric.Polyline is already defined");return}t.Polyline=t.util.createClass(t.Object,{type:"polyline",points:null,initialize:function(e,t,n){t=t||{},this.set("points",e),this.callSuper("initialize",t),this._calcDimensions(n)},_calcDimensions:function(e){return t.Polygon.prototype._calcDimensions.call(this,e)},toObject:function(e){return t.Polygon.prototype.toObject.call(this,e)},toSVG:function(e){var t=[],r=this._createBaseSVGMarkup();for(var i=0,s=this.points.length;i'),e?e(r.join("")):r.join("")},_render:function(e){var t;e.beginPath(),e.moveTo(this.points[0].x,this.points[0].y);for(var n=0,r=this.points.length;n'),e?e(n.join("")):n.join("")},_render:function(e){var t;e.beginPath(),e.moveTo(this.points[0].x,this.points[0].y);for(var n=0,r=this.points.length;n"},toObject:function(e){var t=i(this.callSuper("toObject",e),{path:this.path.map(function(e){return e.slice()}),pathOffset:this.pathOffset});return this.sourcePath&&(t.sourcePath=this.sourcePath),this.transformMatrix&&(t.transformMatrix=this.transformMatrix),t},toDatalessObject:function(e){var t=this.toObject(e);return this.sourcePath&&(t.path=this.sourcePath),delete t.sourcePath,t},toSVG:function(e){var t=[],n=this._createBaseSVGMarkup();for(var r=0,i=this.path.length;r',"",""),e?e(n.join("")):n.join("")},complexity:function(){return this.path.length},_parsePath:function(){var e=[],t=[],n,r,i=/([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/ig,s,o;for(var a=0,f,l=this.path.length;ad)for(var v=1,m=f.length;v"];for(var r=0,i=t.length;r"),e?e(n.join("")):n.join("")},toString:function(){return"#"},isSameColor:function(){var e=(this.getObjects()[0].get("fill")||"").toLowerCase();return this.getObjects().every(function(t){return(t.get("fill")||"").toLowerCase()===e})},complexity:function(){return this.paths.reduce(function(e,t){return e+(t&&t.complexity?t.complexity():0)},0)},getObjects:function(){return this.paths}}),t.PathGroup.fromObject=function(e,n){typeof e.paths=="string"?t.loadSVGFromURL(e.paths,function(r){var i=e.paths;delete e.paths;var s=t.util.groupSVGElements(r,e,i);n(s)}):t.util.enlivenObjects(e.paths,function(r){delete e.paths,n(new t.PathGroup(r,e))})},t.PathGroup.async=!0}(typeof exports!="undefined"?exports:this),function(e){"use strict";var t=e.fabric||(e.fabric={}),n=t.util.object.extend,r=t.util.array.min,i=t.util.array.max,s=t.util.array.invoke;if(t.Group)return;var o={lockMovementX:!0,lockMovementY:!0,lockRotation:!0,lockScalingX:!0,lockScalingY:!0,lockUniScaling:!0};t.Group=t.util.createClass(t.Object,t.Collection,{type:"group",initialize:function(e,t){t=t||{},this._objects=e||[];for(var r=this._objects.length;r--;)this._objects[r].group=this;this.originalState={},this.callSuper("initialize"),this._calcBounds(),this._updateObjectsCoords(),t&&n(this,t),this._setOpacityIfSame(),this.setCoords(!0),this.saveCoords()},_updateObjectsCoords:function(){this.forEachObject(this._updateObjectCoords,this)},_updateObjectCoords:function(e){var t=e.getLeft(),n=e.getTop();e.set({originalLeft:t,originalTop:n,left:t-this.left,top:n-this.top}),e.setCoords(),e.__origHasControls=e.hasControls,e.hasControls=!1},toString:function(){return"#"},addWithUpdate:function(e){return this._restoreObjectsState(),this._objects.push(e),e.group=this,this.forEachObject(this._setObjectActive,this),this._calcBounds(),this._updateObjectsCoords(),this},_setObjectActive:function(e){e.set("active",!0),e.group=this},removeWithUpdate:function(e){return this._moveFlippedObject(e),this._restoreObjectsState(),this.forEachObject(this._setObjectActive,this),this.remove(e),this._calcBounds(),this._updateObjectsCoords(),this},_onObjectAdded:function(e){e.group=this},_onObjectRemoved:function(e){delete e.group,e.set("active",!1)},delegatedProperties:{fill:!0,opacity:!0,fontFamily:!0,fontWeight:!0,fontSize:!0,fontStyle:!0,lineHeight:!0,textDecoration:!0,textAlign:!0,backgroundColor:!0},_set:function(e,t){if(e in this.delegatedProperties){var n=this._objects.length;this[e]=t;while(n--)this._objects[n].set(e,t)}else this[e]=t},toObject:function(e){return n(this.callSuper("toObject",e),{objects:s(this._objects,"toObject",e)})},render:function(e,n){if(!this.visible)return;e.save(),this.transform(e),this.clipTo&&t.util.clipContext(this,e);for(var r=0,i=this._objects.length;r'];for(var n=0,r=this._objects.length;n"),e?e(t.join("")):t.join("")},get:function(e){if(e in o){if(this[e])return this[e];for(var t=0,n=this._objects.length;t','");if(this.stroke||this.strokeDashArray){var n=this.fill;this.fill=null,t.push("'),this.fill=n}return t.push(""),e?e(t.join("")):t.join("")},getSrc:function(){if(this.getElement())return this.getElement().src||this.getElement()._src},toString:function(){return'#'},clone:function(e,t){this.constructor.fromObject(this.toObject(t),e)},applyFilters:function(e){if(!this._originalElement)return;if(this.filters.length===0){this._element=this._originalElement,e&&e();return}var t=this._originalElement,n=fabric.util.createCanvasElement(),r=fabric.util.createImage(),i=this;return n.width=t.width,n.height=t.height,n.getContext("2d").drawImage(t,0,0,t.width,t.height),this.filters.forEach(function(e){e&&e.applyTo(n)}),r.width=t.width,r.height=t.height,fabric.isLikelyNode?(r.src=n.toBuffer(undefined,fabric.Image.pngCompression),i._element=r,e&&e()):(r.onload=function(){i._element=r,e&&e(),r.onload=n=t=null},r.src=n.toDataURL("image/png")),this},_render:function(e){this._element&&e.drawImage(this._element,-this.width/2,-this.height/2,this.width,this.height)},_resetWidthHeight:function(){var e=this.getElement();this.set("width",e.width),this.set("height",e.height)},_initElement:function(e){this.setElement(fabric.util.getById(e)),fabric.util.addClass(this.getElement(),fabric.Image.CSS_CANVAS)},_initConfig:function(e){e||(e={}),this.setOptions(e),this._setWidthHeight(e),this._element&&this.crossOrigin&&(this._element.crossOrigin=this.crossOrigin)},_initFilters:function(e,t){e.filters&&e.filters.length?fabric.util.enlivenObjects(e.filters,function(e){t&&t(e)},"fabric.Image.filters"):t&&t()},_setWidthHeight:function(e){this.width="width"in e?e.width:this.getElement()?this.getElement().width||0:0,this.height="height"in e?e.height:this.getElement()?this.getElement().height||0:0},complexity:function(){return 1}}),fabric.Image.CSS_CANVAS="canvas-img",fabric.Image.prototype.getSvgSrc=fabric.Image.prototype.getSrc,fabric.Image.fromObject=function(e,t){fabric.util.loadImage(e.src,function(n){fabric.Image.prototype._initFilters.call(e,e,function(r){e.filters=r||[];var i=new fabric.Image(n,e);t&&t(i)})},null,e.crossOrigin)},fabric.Image.fromURL=function(e,t,n){fabric.util.loadImage(e,function(e){t(new fabric.Image(e,n))},null,n&&n.crossOrigin)},fabric.Image.ATTRIBUTE_NAMES=fabric.SHARED_ATTRIBUTES.concat("x y width height xlink:href".split(" ")),fabric.Image.fromElement=function(e,n,r){var i=fabric.parseAttributes(e,fabric.Image.ATTRIBUTE_NAMES);fabric.Image.fromURL(i["xlink:href"],n,t(r?fabric.util.object.clone(r):{},i))},fabric.Image.async=!0,fabric.Image.pngCompression=1}(typeof exports!="undefined"?exports:this),fabric.util.object.extend(fabric.Object.prototype,{_getAngleValueForStraighten:function(){var e=this.getAngle()%360;return e>0?Math.round((e-1)/90)*90:Math.round(e/90)*90},straighten:function(){return this.setAngle(this._getAngleValueForStraighten()),this},fxStraighten:function(e){e=e||{};var t=function(){},n=e.onComplete||t,r=e.onChange||t,i=this;return fabric.util.animate({startValue:this.get("angle"),endValue:this._getAngleValueForStraighten(),duration:this.FX_DURATION,onChange:function(e){i.setAngle(e),r()},onComplete:function(){i.setCoords(),n()},onStart:function(){i.set("active",!1)}}),this}}),fabric.util.object.extend(fabric.StaticCanvas.prototype,{straightenObject:function(e){return e.straighten(),this.renderAll(),this},fxStraightenObject:function(e){return e.fxStraighten({onChange:this.renderAll.bind(this)}),this}}),fabric.Image.filters=fabric.Image.filters||{},fabric.Image.filters.BaseFilter=fabric.util.createClass({type:"BaseFilter",toObject:function(){return{type:this.type}},toJSON:function(){return this.toObject()}}),function(e){"use strict";var t=e.fabric||(e.fabric={}),n=t.util.object.extend;t.Image.filters.Brightness=t.util.createClass(t.Image.filters.BaseFilter,{type:"Brightness",initialize:function(e){e=e||{},this.brightness=e.brightness||0},applyTo:function(e){var t=e.getContext("2d"),n=t.getImageData(0,0,e.width,e.height),r=n.data,i=this.brightness;for(var s=0,o=r.length;sa||C<0||C>u)continue;var k=(N*u+C)*4,L=t[x*i+T];b+=o[k]*L,w+=o[k+1]*L,E+=o[k+2]*L,S+=o[k+3]*L}h[y]=b,h[y+1]=w,h[y+2]=E,h[y+3]=S+p*(255-S)}n.putImageData(c,0,0)},toObject:function(){return n(this.callSuper("toObject"),{opaque:this.opaque,matrix:this.matrix})}}),t.Image.filters.Convolute.fromObject=function(e){return new t.Image.filters.Convolute(e)}}(typeof exports!="undefined"?exports:this),function(e){"use strict";var t=e.fabric||(e.fabric={}),n=t.util.object.extend;t.Image.filters.GradientTransparency=t.util.createClass(t.Image.filters.BaseFilter,{type:"GradientTransparency",initialize:function(e){e=e||{},this.threshold=e.threshold||100},applyTo:function(e){var t=e.getContext("2d"),n=t.getImageData(0,0,e.width,e.height),r=n.data,i=this.threshold,s=r.length;for(var o=0,u=r.length;o-1?e.channel:0},applyTo:function(e){if(!this.mask)return;var n=e.getContext("2d"),r=n.getImageData(0,0,e.width,e.height),i=r.data,s=this.mask.getElement(),o=t.util.createCanvasElement(),u=this.channel,a,f=r.width*r.height*4;o.width=s.width,o.height=s.height,o.getContext("2d").drawImage(s,0,0,s.width,s.height);var l=o.getContext("2d").getImageData(0,0,s.width,s.height),c=l.data;for(a=0;ao&&f>o&&l>o&&u(a-f)'},_render:function(e){var t=this.group&&this.group.type==="path-group";t&&!this.transformMatrix?e.translate(-this.group.width/2+this.left,-this.group.height/2+this.top):t&&this.transformMatrix&&e.translate(-this.group.width/2,-this.group.height/2),typeof Cufon=="undefined"||this.useNative===!0?this._renderViaNative(e):this._renderViaCufon(e)},_renderViaNative:function(e){var n=this.text.split(this._reNewline);this.transform(e,t.isLikelyNode),this._setTextStyles(e),this.width=this._getTextWidth(e,n),this.height=this._getTextHeight(e,n),this.clipTo&&t.util.clipContext(this,e),this._renderTextBackground(e,n),this._translateForTextAlign(e),this._renderText(e,n),this.textAlign!=="left"&&this.textAlign!=="justify"&&e.restore(),this._renderTextDecoration(e,n),this.clipTo&&e.restore(),this._setBoundaries(e,n),this._totalLineHeight=0},_renderText:function(e,t){e.save(),this._setShadow(e),this._renderTextFill(e,t),this._renderTextStroke(e,t),this._removeShadow(e),e.restore()},_translateForTextAlign:function(e){this.textAlign!=="left"&&this.textAlign!=="justify"&&(e.save(),e.translate(this.textAlign==="center"?this.width/2:this.width,0))},_setBoundaries:function(e,t){this._boundaries=[];for(var n=0,r=t.length;nn&&(n=s)}return n},_renderChars:function(e,t,n,r,i){t[e](n,r,i)},_renderTextLine:function(e,t,n,r,i,s){i-=this.fontSize/4;if(this.textAlign!=="justify"){this._renderChars(e,t,n,r,i,s);return}var o=t.measureText(n).width,u=this.width;if(u>o){var a=n.split(/\s+/),f=t.measureText(n.replace(/\s+/g,"")).width,l=u-f,c=a.length-1,h=l/c,p=0;for(var d=0,v=a.length;d-1&&i(this.fontSize*this.lineHeight),this.textDecoration.indexOf("line-through")>-1&&i(this.fontSize*this.lineHeight-this.fontSize/2),this.textDecoration.indexOf("overline")>-1&&i(this.fontSize*this.lineHeight-this.fontSize)},_getFontDeclaration:function(){return[t.isLikelyNode?this.fontWeight:this.fontStyle,t.isLikelyNode?this.fontStyle:this.fontWeight,this.fontSize+"px",t.isLikelyNode?'"'+this.fontFamily+'"':this.fontFamily].join(" ")},render:function(e,t){if(!this.visible)return;e.save();var n=this.transformMatrix;n&&(!this.group||this.group.type==="path-group")&&e.transform(n[0],n[1],n[2],n[3],n[4],n[5]),this._render(e),!t&&this.active&&(this.drawBorders(e),this.drawControls(e)),e.restore()},toObject:function(e){var t=n(this.callSuper("toObject",e),{text:this.text,fontSize:this.fontSize,fontWeight:this.fontWeight,fontFamily:this.fontFamily,fontStyle:this.fontStyle,lineHeight:this.lineHeight,textDecoration:this.textDecoration,textAlign:this.textAlign,path:this.path,textBackgroundColor:this.textBackgroundColor,useNative:this.useNative});return this.includeDefaultValues||this._removeDefaultValues(t),t},toSVG:function(e){var t=[],n=this.text.split(this._reNewline),r=this._getSVGLeftTopOffsets(n),i=this._getSVGTextAndBg(r.lineTop,r.textLeft,n),s=this._getSVGShadows(r.lineTop,n);return r.textTop+=this._fontAscent?this._fontAscent/5*this.lineHeight:0,this._wrapSVGTextAndBg(t,i,s,r),e?e(t.join("")):t.join("")},_getSVGLeftTopOffsets:function(e){var t=this.useNative?this.fontSize*this.lineHeight:-this._fontAscent-this._fontAscent/5*this.lineHeight,n=-(this.width/2),r=this.useNative?this.fontSize-1:this.height/2-e.length*this.fontSize-this._totalLineHeight;return{textLeft:n,textTop:r,lineTop:t}},_wrapSVGTextAndBg:function(e,t,n,r){e.push('',t.textBgRects.join(""),"',n.join(""),t.textSpans.join(""),"","")},_getSVGShadows:function(e,n){var r=[],s,o,u=1;if(!this.shadow||!this._boundaries)return r;for(s=0,o=n.length;s",t.util.string.escapeXml(n[s]),""),u=1}else u++;return r},_getSVGTextAndBg:function(e,t,n){var r=[],i=[],s=1;this._setSVGBg(i);for(var o=0,u=n.length;o",t.util.string.escapeXml(e),"")},_setSVGTextLineBg:function(e,t,n,r){e.push("')},_setSVGBg:function(e){this.backgroundColor&&this._boundaries&&e.push("')},_getFillAttributes:function(e){var n=e&&typeof e=="string"?new t.Color(e):"";return!n||!n.getSource()||n.getAlpha()===1?'fill="'+e+'"':'opacity="'+n.getAlpha()+'" fill="'+n.setAlpha(1).toRgb()+'"'},_set:function(e,t){e==="fontFamily"&&this.path&&(this.path=this.path.replace(/(.*?)([^\/]*)(\.font\.js)/,"$1"+t+"$3")),this.callSuper("_set",e,t),e in this._dimensionAffectingProps&&(this._initDimensions(),this.setCoords())},complexity:function(){return 1}}),t.Text.ATTRIBUTE_NAMES=t.SHARED_ATTRIBUTES.concat("x y dx dy font-family font-style font-weight font-size text-decoration text-anchor".split(" ")),t.Text.DEFAULT_SVG_FONT_SIZE=16,t.Text.fromElement=function(e,n){if(!e)return null;var r=t.parseAttributes(e,t.Text.ATTRIBUTE_NAMES);n=t.util.object.extend(n?t.util.object.clone(n):{},r),"dx"in r&&(n.left+=r.dx),"dy"in r&&(n.top+=r.dy),"fontSize"in n||(n.fontSize=t.Text.DEFAULT_SVG_FONT_SIZE),n.originX||(n.originX="center");var i=new t.Text(e.textContent,n);return i.set({left:i.getLeft()+i.getWidth()/2,top:i.getTop()-i.getHeight()/2}),i},t.Text.fromObject=function(e){return new t.Text(e.text,r(e))},t.util.createAccessors(t.Text)}(typeof exports!="undefined"?exports:this),function(){var e=fabric.util.object.clone;fabric.IText=fabric.util.createClass(fabric.Text,fabric.Observable,{type:"i-text",selectionStart:0,selectionEnd:0,selectionColor:"rgba(17,119,255,0.3)",isEditing:!1,editable:!0,editingBorderColor:"rgba(102,153,255,0.25)",cursorWidth:2,cursorColor:"#333",cursorDelay:1e3,cursorDuration:600,styles:null,caching:!0,_skipFillStrokeCheck:!0,_reSpace:/\s|\n/,_fontSizeFraction:4,_currentCursorOpacity:0,_selectionDirection:null,_abortCursorAnimation:!1,_charWidthsCache:{},initialize:function(e,t){this.styles=t?t.styles||{}:{},this.callSuper("initialize",e,t),this.initBehavior(),fabric.IText.instances.push(this),this.__lineWidths={},this.__lineHeights={},this.__lineOffsets={}},isEmptyStyles:function(){if(!this.styles)return!0;var e=this.styles;for(var t in e)for(var n in e[t])for(var r in e[t][n])return!1;return!0},setSelectionStart:function(e){this.selectionStart!==e&&this.canvas&&this.canvas.fire("text:selection:changed",{target:this}),this.selectionStart=e,this.hiddenTextarea&&(this.hiddenTextarea.selectionStart=e)},setSelectionEnd:function(e){this.selectionEnd!==e&&this.canvas&&this.canvas.fire("text:selection:changed",{target:this}),this.selectionEnd=e,this.hiddenTextarea&&(this.hiddenTextarea.selectionEnd=e)},getSelectionStyles:function(e,t){if(arguments.length===2){var n=[];for(var r=e;r=r.charIndex&&(a!==o||hs&&a-1&&this._renderCharDecorationAtOffset(e,n,r+this.fontSize/this._fontSizeFraction,i,0,this.fontSize/20),u.indexOf("line-through")>-1&&this._renderCharDecorationAtOffset(e,n,r+this.fontSize/this._fontSizeFraction,i,o/2,a/20),u.indexOf("overline")>-1&&this._renderCharDecorationAtOffset(e,n,r,i,s-this.fontSize/this._fontSizeFraction,this.fontSize/20)},_renderCharDecorationAtOffset:function(e,t,n,r,i,s){e.fillRect(t,n-i,r,s)},_renderTextLine:function(e,t,n,r,i,s){i+=this.fontSize/4,this.callSuper("_renderTextLine",e,t,n,r,i,s)},_renderTextDecoration:function(e,t){if(this.isEmptyStyles())return this.callSuper("_renderTextDecoration",e,t)},_renderTextLinesBackground:function(e,t){if(!this.textBackgroundColor&&!this.styles)return;e.save(),this.textBackgroundColor&&(e.fillStyle=this.textBackgroundColor);var n=0,r=this.fontSize/this._fontSizeFraction;for(var i=0,s=t.length;in&&(n=s)}return n},_getHeightOfLine:function(e,t,n){n=n||this.text.split(this._reNewline);var r=this._getHeightOfChar(e,n[t][0],t,0),i=n[t],s=i.split("");for(var o=1,u=s.length;or&&(r=a)}return r*this.lineHeight},_getTextHeight:function(e,t){var n=0;for(var r=0,i=t.length;r-1)t++,n--;return e-t},findWordBoundaryRight:function(e){var t=0,n=e;if(this._reSpace.test(this.text.charAt(n)))while(this._reSpace.test(this.text.charAt(n)))t++,n++;while(/\S/.test(this.text.charAt(n))&&n-1)t++,n--;return e-t},findLineBoundaryRight:function(e){var t=0,n=e;while(!/\n/.test(this.text.charAt(n))&&n0&&nr;s?this.removeStyleObject(s,n+1):this.removeStyleObject(this.get2DCursorLocation(n).charIndex===0,n)}this.text=this.text.slice(0,e)+this.text.slice(t)},insertChars:function(e){var t=this.text.slice(this.selectionStart,this.selectionStart+1)==="\n";this.text=this.text.slice(0,this.selectionStart)+e+this.text.slice(this.selectionEnd),this.selectionStart===this.selectionEnd&&this.insertStyleObjects(e,t,this.copiedStyles),this.selectionStart+=e.length,this.selectionEnd=this.selectionStart,this.canvas&&this.canvas.renderAll().renderAll(),this.setCoords(),this.fire("changed"),this.canvas&&this.canvas.fire("text:changed",{target:this})},insertNewlineStyleObject:function(t,n,r){this.shiftLineStyles(t,1),this.styles[t+1]||(this.styles[t+1]={});var i=this.styles[t][n-1],s={};if(r)s[0]=e(i),this.styles[t+1]=s;else{for(var o in this.styles[t])parseInt(o,10)>=n&&(s[parseInt(o,10)-n]=this.styles[t][o],delete this.styles[t][o]);this.styles[t+1]=s}},insertCharStyleObject:function(t,n,r){var i=this.styles[t],s=e(i);n===0&&!r&&(n=1);for(var o in s){var u=parseInt(o,10);u>=n&&(i[u+1]=s[u])}this.styles[t][n]=r||e(i[n-1])},insertStyleObjects:function(e,t,n){if(this.isEmptyStyles())return;var r=this.get2DCursorLocation(),i=r.lineIndex,s=r.charIndex;this.styles[i]||(this.styles[i]={}),e==="\n"?this.insertNewlineStyleObject(i,s,t):n?this._insertStyles(n):this.insertCharStyleObject(i,s)},_insertStyles:function(e){for(var t=0,n=e.length;tt&&(this.styles[s+n]=r[s])}},removeStyleObject:function(t,n){var r=this.get2DCursorLocation(n),i=r.lineIndex,s=r.charIndex;if(t){var o=this.text.split(this._reNewline),u=o[i-1],a=u?u.length:0;this.styles[i-1]||(this.styles[i-1]={});for(s in this.styles[i])this.styles[i-1][parseInt(s,10)+a]=this.styles[i][s];this.shiftLineStyles(i,-1)}else{var f=this.styles[i];if(f){var l=this.selectionStart===this.selectionEnd?-1:0;delete f[s+l]}var c=e(f);for(var h in c){var p=parseInt(h,10);p>=s&&p!==0&&(f[p-1]=c[p],delete f[p])}}},insertNewline:function(){this.insertChars("\n")}})}(),fabric.util.object.extend(fabric.IText.prototype,{initDoubleClickSimulation:function(){this.__lastClickTime=+(new Date),this.__lastLastClickTime=+(new Date),this.__lastPointer={},this.on("mousedown",this.onMouseDown.bind(this))},onMouseDown:function(e){this.__newClickTime=+(new Date);var t=this.canvas.getPointer(e.e);this.isTripleClick(t)?(this.fire("tripleclick",e),this._stopEvent(e.e)):this.isDoubleClick(t)&&(this.fire("dblclick",e),this._stopEvent(e.e)),this.__lastLastClickTime=this.__lastClickTime,this.__lastClickTime=this.__newClickTime,this.__lastPointer=t,this.__lastIsEditing=this.isEditing},isDoubleClick:function(e){return this.__newClickTime-this.__lastClickTime<500&&this.__lastPointer.x===e.x&&this.__lastPointer.y===e.y&&this.__lastIsEditing},isTripleClick:function(e){return this.__newClickTime-this.__lastClickTime<500&&this.__lastClickTime-this.__lastLastClickTime<500&&this.__lastPointer.x===e.x&&this.__lastPointer.y===e.y},_stopEvent:function(e){e.preventDefault&&e.preventDefault(),e.stopPropagation&&e.stopPropagation()},initCursorSelectionHandlers:function(){this.initSelectedHandler(),this.initMousedownHandler(),this.initMousemoveHandler(),this.initMouseupHandler(),this.initClicks()},initClicks:function(){this.on("dblclick",function(e){this.selectWord(this.getSelectionStartFromPointer(e.e))}),this.on("tripleclick",function(e){this.selectLine +(this.getSelectionStartFromPointer(e.e))})},initMousedownHandler:function(){this.on("mousedown",function(e){var t=this.canvas.getPointer(e.e);this.__mousedownX=t.x,this.__mousedownY=t.y,this.__isMousedown=!0,this.hiddenTextarea&&this.canvas&&this.canvas.wrapperEl.appendChild(this.hiddenTextarea),this.selected&&this.setCursorByClick(e.e),this.isEditing&&(this.__selectionStartOnMouseDown=this.selectionStart,this.initDelayedCursor(!0))})},initMousemoveHandler:function(){this.on("mousemove",function(e){if(!this.__isMousedown||!this.isEditing)return;var t=this.getSelectionStartFromPointer(e.e);t>=this.__selectionStartOnMouseDown?(this.setSelectionStart(this.__selectionStartOnMouseDown),this.setSelectionEnd(t)):(this.setSelectionStart(t),this.setSelectionEnd(this.__selectionStartOnMouseDown))})},_isObjectMoved:function(e){var t=this.canvas.getPointer(e);return this.__mousedownX!==t.x||this.__mousedownY!==t.y},initMouseupHandler:function(){this.on("mouseup",function(e){this.__isMousedown=!1;if(this._isObjectMoved(e.e))return;this.selected&&(this.enterEditing(),this.initDelayedCursor(!0)),this.selected=!0})},setCursorByClick:function(e){var t=this.getSelectionStartFromPointer(e);e.shiftKey?to?0:1,f=r+a;return this.flipX&&(f=i-f),f>this.text.length&&(f=this.text.length),s===i&&f--,f}}),fabric.util.object.extend(fabric.IText.prototype,{initHiddenTextarea:function(){this.hiddenTextarea=fabric.document.createElement("textarea"),this.hiddenTextarea.setAttribute("autocapitalize","off"),this.hiddenTextarea.style.cssText="position: absolute; top: 0; left: -9999px",fabric.document.body.appendChild(this.hiddenTextarea),fabric.util.addListener(this.hiddenTextarea,"keydown",this.onKeyDown.bind(this)),fabric.util.addListener(this.hiddenTextarea,"keypress",this.onKeyPress.bind(this)),!this._clickHandlerInitialized&&this.canvas&&(fabric.util.addListener(this.canvas.upperCanvasEl,"click",this.onClick.bind(this)),this._clickHandlerInitialized=!0)},_keysMap:{8:"removeChars",13:"insertNewline",37:"moveCursorLeft",38:"moveCursorUp",39:"moveCursorRight",40:"moveCursorDown",46:"forwardDelete"},_ctrlKeysMap:{65:"selectAll",67:"copy",86:"paste",88:"cut"},onClick:function(){this.hiddenTextarea&&this.hiddenTextarea.focus()},onKeyDown:function(e){if(!this.isEditing)return;if(e.keyCode in this._keysMap)this[this._keysMap[e.keyCode]](e);else{if(!(e.keyCode in this._ctrlKeysMap&&(e.ctrlKey||e.metaKey)))return;this[this._ctrlKeysMap[e.keyCode]](e)}e.preventDefault(),e.stopPropagation(),this.canvas&&this.canvas.renderAll()},forwardDelete:function(e){this.selectionStart===this.selectionEnd&&this.moveCursorRight(e),this.removeChars(e)},copy:function(){var e=this.getSelectedText();this.copiedText=e,this.copiedStyles=this.getSelectionStyles(this.selectionStart,this.selectionEnd)},paste:function(){this.copiedText&&this.insertChars(this.copiedText)},cut:function(e){this.copy(),this.removeChars(e)},onKeyPress:function(e){if(!this.isEditing||e.metaKey||e.ctrlKey||e.keyCode===8||e.keyCode===13)return;this.insertChars(String.fromCharCode(e.which)),e.preventDefault(),e.stopPropagation()},getDownCursorOffset:function(e,t){var n=t?this.selectionEnd:this.selectionStart,r=this.text.split(this._reNewline),i,s,o=this.text.slice(0,n),u=this.text.slice(n),a=o.slice(o.lastIndexOf("\n")+1),f=u.match(/(.*)\n?/)[1],l=(u.match(/.*\n(.*)\n?/)||{})[1]||"",c=this.get2DCursorLocation(n);if(c.lineIndex===r.length-1||e.metaKey)return this.text.length-n;var h=this._getWidthOfLine(this.ctx,c.lineIndex,r);s=this._getLineLeftOffset(h);var p=s,d=c.lineIndex;for(var v=0,m=a.length;vn){f=!0;var d=u-p,v=u,m=Math.abs(d-n),g=Math.abs(v-n);a=gthis.text.length&&(this.selectionStart=this.text.length),this.selectionEnd=this.selectionStart},moveCursorDownWithShift:function(e){if(this._selectionDirection==="left"&&this.selectionStart!==this.selectionEnd){this.selectionStart+=e,this._selectionDirection="left";return}this._selectionDirection="right",this.selectionEnd+=e,this.selectionEnd>this.text.length&&(this.selectionEnd=this.text.length)},getUpCursorOffset:function(e,t){var n=t?this.selectionEnd:this.selectionStart,r=this.get2DCursorLocation(n);if(r.lineIndex===0||e.metaKey)return n;var i=this.text.slice(0,n),s=i.slice(i.lastIndexOf("\n")+1),o=(i.match(/\n?(.*)\n.*$/)||{})[1]||"",u=this.text.split(this._reNewline),a,f=this._getWidthOfLine(this.ctx,r.lineIndex,u),l=this._getLineLeftOffset(f),c=l,h=r.lineIndex;for(var p=0,d=s.length;pn){f=!0;var d=u-p,v=u,m=Math.abs(d-n),g=Math.abs(v-n);a=g=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("changed"),this.canvas&&this.canvas.fire("text:changed",{target:this})},_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 requestFs(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=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){function r(r){i.src=new Buffer(r,"binary"),i._src=e,t&&t.call(n,i)}var 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?requestFs(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?requestFs(e,function(e){fabric.loadSVGFromString(e.toString(),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,n,r){r=r||n;var i=fabric.document.createElement("canvas"),s=new Canvas(e||600,t||600,r);i.style={},i.width=s.width,i.height=s.height;var o=fabric.Canvas||fabric.StaticCanvas,u=new o(i,n);return u.contextContainer=s.getContext("2d"),u.nodeCanvas=s,u.Font=Canvas.Font,u},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/fabric.min.js.gz b/dist/fabric.min.js.gz new file mode 100644 index 00000000..f65a6018 Binary files /dev/null and b/dist/fabric.min.js.gz differ diff --git a/dist/all.js b/dist/fabric.require.js similarity index 90% rename from dist/all.js rename to dist/fabric.require.js index 962a3e37..320db6ac 100644 --- a/dist/all.js +++ b/dist/fabric.require.js @@ -1,7 +1,7 @@ /* build: `node build.js modules=ALL minifier=uglifyjs` */ -/*! Fabric.js Copyright 2008-2013, Printio (Juriy Zaytsev, Maxim Chernyak) */ +/*! Fabric.js Copyright 2008-2014, Printio (Juriy Zaytsev, Maxim Chernyak) */ -var fabric = fabric || { version: "1.4.0" }; +var fabric = fabric || { version: "1.4.6" }; if (typeof exports !== 'undefined') { exports.fabric = fabric; } @@ -37,6 +37,7 @@ fabric.isLikelyNode = typeof Buffer !== 'undefined' && * @type array */ fabric.SHARED_ATTRIBUTES = [ + "display", "transform", "fill", "fill-opacity", "fill-rule", "opacity", @@ -1276,7 +1277,7 @@ if (typeof exports != 'undefined') { /* json2.js - 2011-10-19 + 2014-02-04 Public Domain. @@ -1435,8 +1436,7 @@ if (typeof exports != 'undefined') { // 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) { +if (typeof JSON !== 'object') { JSON = {}; } @@ -1450,8 +1450,7 @@ if (!JSON) { if (typeof Date.prototype.toJSON !== 'function') { - /** @ignore */ - Date.prototype.toJSON = function (key) { + Date.prototype.toJSON = function () { return isFinite(this.valueOf()) ? this.getUTCFullYear() + '-' + @@ -1465,25 +1464,16 @@ if (!JSON) { String.prototype.toJSON = Number.prototype.toJSON = - /** @ignore */ - Boolean.prototype.toJSON = function (key) { + Boolean.prototype.toJSON = function () { 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, + var cx, + escapable, gap, indent, - meta = { // table of character substitutions - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '"' : '\\"', - '\\': '\\\\' - }, + meta, rep; @@ -1635,7 +1625,16 @@ if (!JSON) { // If the JSON object does not yet have a stringify method, give it one. if (typeof JSON.stringify !== 'function') { - /** @ignore */ + escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }; JSON.stringify = function (value, replacer, space) { // The stringify method takes a value and an optional replacer, and an optional @@ -1683,7 +1682,7 @@ if (!JSON) { // If the JSON object does not yet have a parse method, give it one. if (typeof JSON.parse !== 'function') { - /** @ignore */ + cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; JSON.parse = function (text, reviver) { // The parse method takes a text and an optional reviver function, and returns @@ -1766,6 +1765,7 @@ if (!JSON) { } }()); + /* ---------------------------------------------------- Event.js : 1.1.3 : 2013/07/17 : MIT License @@ -1795,10 +1795,10 @@ if (typeof(eventjs) === "undefined") var eventjs = Event; (function(root) { "use strict"; // Add custom *EventListener commands to HTMLElements (set false to prevent funkiness). -root.modifyEventListener = true; +root.modifyEventListener = false; // Add bulk *EventListener commands on NodeLists from querySelectorAll and others (set false to prevent funkiness). -root.modifySelectors = true; +root.modifySelectors = false; // Event maintenance. root.add = function(target, type, listener, configure) { @@ -3721,12 +3721,12 @@ 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 + * @param {...fabric.Object} object Zero or more fabric instances * @return {Self} thisArg */ add: function () { this._objects.push.apply(this._objects, arguments); - for (var i = arguments.length; i--; ) { + for (var i = 0, length = arguments.length; i < length; i++) { this._onObjectAdded(arguments[i]); } this.renderOnAddRemove && this.renderAll(); @@ -3740,6 +3740,7 @@ fabric.Collection = { * @param {Number} index Index to insert object at * @param {Boolean} nonSplicing When `true`, no splicing (shifting) of objects occurs * @return {Self} thisArg + * @chainable */ insertAt: function (object, index, nonSplicing) { var objects = this.getObjects(); @@ -3755,22 +3756,27 @@ fabric.Collection = { }, /** - * Removes an object from a collection, then renders canvas (if `renderOnAddRemove` is not `false`) - * @param {Object} object Object to remove + * Removes objects from a collection, then renders canvas (if `renderOnAddRemove` is not `false`) + * @param {...fabric.Object} object Zero or more fabric instances * @return {Self} thisArg + * @chainable */ - remove: function(object) { + remove: function() { var objects = this.getObjects(), - index = objects.indexOf(object); + index; - // only call onObjectRemoved if an object was actually removed - if (index !== -1) { - objects.splice(index, 1); - this._onObjectRemoved(object); + for (var i = 0, length = arguments.length; i < length; i++) { + index = objects.indexOf(arguments[i]); + + // only call onObjectRemoved if an object was actually removed + if (index !== -1) { + objects.splice(index, 1); + this._onObjectRemoved(arguments[i]); + } } this.renderOnAddRemove && this.renderAll(); - return object; + return this; }, /** @@ -4020,7 +4026,9 @@ fabric.Collection = { * @return {Object} Object for given namespace (default fabric) */ resolveNamespace: function(namespace) { - if (!namespace) return fabric; + if (!namespace) { + return fabric; + } var parts = namespace.split('.'), len = parts.length, @@ -4182,7 +4190,7 @@ fabric.Collection = { drawDashedLine: function(ctx, x, y, x2, y2, da) { var dx = x2 - x, dy = y2 - y, - len = sqrt(dx*dx + dy*dy), + len = sqrt(dx * dx + dy * dy), rot = atan2(dy, dx), dc = da.length, di = 0, @@ -4290,22 +4298,23 @@ fabric.Collection = { var a = [ [matrixA[0], matrixA[2], matrixA[4]], [matrixA[1], matrixA[3], matrixA[5]], - [0 , 0 , 1 ] - ]; + [0, 0, 1 ] + ], - var b = [ + b = [ [matrixB[0], matrixB[2], matrixB[4]], [matrixB[1], matrixB[3], matrixB[5]], - [0 , 0 , 1 ] - ]; + [0, 0, 1 ] + ], - var result = []; - for (var r=0; r<3; r++) { + result = []; + + for (var r = 0; r < 3; r++) { result[r] = []; - for (var c=0; c<3; c++) { + for (var c = 0; c < 3; c++) { var sum = 0; - for (var k=0; k<3; k++) { - sum += a[r][k]*b[k][c]; + for (var k = 0; k < 3; k++) { + sum += a[r][k] * b[k][c]; } result[r][c] = sum; @@ -4378,9 +4387,8 @@ fabric.Collection = { } } - var _isTransparent = true; - var imageData = ctx.getImageData( - x, y, (tolerance * 2) || 1, (tolerance * 2) || 1); + var _isTransparent = true, + imageData = ctx.getImageData(x, y, (tolerance * 2) || 1, (tolerance * 2) || 1); // Split image data - for tolerance > 1, pixelDataSize = 4; for (var i = 3, l = imageData.data.length; i < l; i += 4) { @@ -4414,37 +4422,43 @@ fabric.Collection = { return arcToSegmentsCache[argsString]; } - var coords = getXYCoords(rotateX, rx, ry, ox, oy, x, y); + var coords = getXYCoords(rotateX, rx, ry, ox, oy, x, y), - var d = (coords.x1-coords.x0) * (coords.x1-coords.x0) + - (coords.y1-coords.y0) * (coords.y1-coords.y0); + 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; + sfactorSq = 1 / d - 0.25; - 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; + if (sfactorSq < 0) { + sfactorSq = 0; } - var segments = Math.ceil(Math.abs(th_arc / (Math.PI * 0.5 + 0.001))); - var result = []; - for (var i=0; i 0 && sweep === 0) { + thArc -= 2 * Math.PI; + } + + var segments = Math.ceil(Math.abs(thArc / (Math.PI * 0.5 + 0.001))), + result = []; + + for (var i = 0; i < segments; i++) { + var th2 = th0 + i * thArc / segments, + th3 = th0 + (i + 1) * thArc / segments; + + result[i] = [xc, yc, th2, th3, rx, ry, coords.sinTh, coords.cosTh]; } arcToSegmentsCache[argsString] = result; @@ -4453,56 +4467,59 @@ fabric.Collection = { function getXYCoords(rotateX, rx, ry, ox, oy, x, y) { - var th = rotateX * (Math.PI/180); - var sin_th = Math.sin(th); - var cos_th = Math.cos(th); + var th = rotateX * (Math.PI / 180), + sinTh = Math.sin(th), + cosTh = Math.cos(th); + rx = Math.abs(rx); ry = Math.abs(ry); - var px = cos_th * (ox - x) * 0.5 + sin_th * (oy - y) * 0.5; - var py = cos_th * (oy - y) * 0.5 - sin_th * (ox - x) * 0.5; - var pl = (px*px) / (rx*rx) + (py*py) / (ry*ry); + + var px = cosTh * (ox - x) * 0.5 + sinTh * (oy - y) * 0.5, + py = cosTh * (oy - y) * 0.5 - sinTh * (ox - x) * 0.5, + pl = (px * px) / (rx * rx) + (py * py) / (ry * ry); + if (pl > 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 a00 = cosTh / rx, + a01 = sinTh / rx, + a10 = (-sinTh) / ry, + a11 = (cosTh) / ry; 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 + sinTh: sinTh, + cosTh: cosTh }; } - function segmentToBezier(cx, cy, th0, th1, rx, ry, sin_th, cos_th) { + function segmentToBezier(cx, cy, th0, th1, rx, ry, sinTh, cosTh) { argsString = _join.call(arguments); + if (segmentToBezierCache[argsString]) { return segmentToBezierCache[argsString]; } - var a00 = cos_th * rx; - var a01 = -sin_th * ry; - var a10 = sin_th * rx; - var a11 = cos_th * ry; + var a00 = cosTh * rx, + a01 = -sinTh * ry, + a10 = sinTh * rx, + a11 = cosTh * ry, + thHalf = 0.5 * (th1 - th0), + t = (8 / 3) * Math.sin(thHalf * 0.5) * + Math.sin(thHalf * 0.5) / Math.sin(thHalf), - 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); + x1 = cx + Math.cos(th0) - t * Math.sin(th0), + y1 = cy + Math.sin(th0) + t * Math.cos(th0), + x3 = cx + Math.cos(th1), + y3 = cy + Math.sin(th1), + x2 = x3 + t * Math.sin(th1), + y2 = y3 - t * Math.cos(th1); segmentToBezierCache[argsString] = [ a00 * x1 + a01 * y1, a10 * x1 + a11 * y1, @@ -4521,17 +4538,18 @@ fabric.Collection = { * @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; iString#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_ */ + /** - * Trims a string (removing whitespace from the beginning and the end) - * @function external:String#trim - * @see String#trim on MDN + * Camelizes a string + * @memberOf fabric.util.string + * @param {String} string String to camelize + * @return {String} Camelized version of a string */ - 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]+$/, ''); + 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 }; -} -/* _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 -}; }()); @@ -4907,16 +4925,16 @@ fabric.util.string = { * @return {Function} */ Function.prototype.bind = function(thisArg) { - var fn = this, args = slice.call(arguments, 1), bound; + var _this = 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))); + return apply.call(_this, this instanceof Dummy ? this : thisArg, args.concat(slice.call(arguments))); }; } else { /** @ignore */ bound = function() { - return apply.call(fn, this instanceof Dummy ? this : thisArg, arguments); + return apply.call(_this, this instanceof Dummy ? this : thisArg, arguments); }; } Dummy.prototype = this.prototype; @@ -4932,51 +4950,51 @@ fabric.util.string = { (function() { - var slice = Array.prototype.slice, emptyFunction = 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; - })(); + 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) { + /** @ignore */ + 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) { + if (property in klass.prototype && + typeof klass.prototype[property] === 'function' && + (source[property] + '').indexOf('callSuper') > -1) { - klass.prototype[property] = (function(property) { - return function() { + 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; + 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; + 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; } - }; - })(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; + } + } } - if (source.valueOf !== Object.prototype.valueOf) { - klass.prototype.valueOf = source.valueOf; - } - } - } - }; + }; function Subclass() { } @@ -5043,15 +5061,16 @@ fabric.util.string = { } return true; } - var getUniqueId = (function () { - var uid = 0; - return function (element) { - return element.__uniqueID || (element.__uniqueID = 'uniqueID__' + uid++); - }; - })(); /** @ignore */ - var getElement, setElement; + var getElement, + setElement, + getUniqueId = (function () { + var uid = 0; + return function (element) { + return element.__uniqueID || (element.__uniqueID = 'uniqueID__' + uid++); + }; + })(); (function () { var elements = { }; @@ -5207,9 +5226,9 @@ fabric.util.string = { event || (event = fabric.window.event); var element = event.target || - (typeof event.srcElement !== unknown ? event.srcElement : null); + (typeof event.srcElement !== unknown ? event.srcElement : null), - var scroll = fabric.util.getScrollLeftTop(element, upperCanvasEl); + scroll = fabric.util.getScrollLeftTop(element, upperCanvasEl); return { x: pointerX(event) + scroll.left, @@ -5222,9 +5241,9 @@ fabric.util.string = { // 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); - }; + }, - var pointerY = function(event) { + pointerY = function(event) { return (typeof event.clientY !== unknown ? event.clientY : 0); }; @@ -5339,21 +5358,21 @@ fabric.util.string = { return typeof id === 'string' ? fabric.document.getElementById(id) : id; } - /** - * Converts an array-like object (e.g. arguments or NodeList) to an array - * @memberOf fabric.util - * @param {Object} arrayLike - * @return {Array} - */ - var toArray = function(arrayLike) { - return _slice.call(arrayLike, 0); - }; + var sliceCanConvertNodelists, + /** + * Converts an array-like object (e.g. arguments or NodeList) to an array + * @memberOf fabric.util + * @param {Object} arrayLike + * @return {Array} + */ + toArray = function(arrayLike) { + return _slice.call(arrayLike, 0); + }; - var sliceCanConvertNodelists; try { sliceCanConvertNodelists = toArray(fabric.document.childNodes) instanceof Array; } - catch(err) { } + catch (err) { } if (!sliceCanConvertNodelists) { toArray = function(arrayLike) { @@ -5395,7 +5414,7 @@ fabric.util.string = { * @param {String} className Class to add to an element */ function addClass(element, className) { - if ((' ' + element.className + ' ').indexOf(' ' + className + ' ') === -1) { + if (element && (' ' + element.className + ' ').indexOf(' ' + className + ' ') === -1) { element.className += (element.className ? ' ' : '') + className; } } @@ -5418,7 +5437,14 @@ fabric.util.string = { wrapper.appendChild(element); return wrapper; } - + + /** + * Returns element scroll offsets + * @memberOf fabric.util + * @param {HTMLElement} element Element to operate on + * @param {HTMLElement} upperCanvasEl Upper canvas element + * @return {Object} Object with left/top values + */ function getScrollLeftTop(element, upperCanvasEl) { var firstFixedAncestor, @@ -5469,19 +5495,19 @@ fabric.util.string = { */ function getElementOffset(element) { var docElem, - box = {left: 0, top: 0}, doc = element && element.ownerDocument, - offset = {left: 0, top: 0}, + box = { left: 0, top: 0 }, + offset = { left: 0, top: 0 }, scrollLeftTop, offsetAttributes = { - 'borderLeftWidth': 'left', - 'borderTopWidth': 'top', - 'paddingLeft': 'left', - 'paddingTop': 'top' + borderLeftWidth: 'left', + borderTopWidth: 'top', + paddingLeft: 'left', + paddingTop: 'top' }; - if (!doc){ - return {left: 0, top: 0}; + if (!doc) { + return { left: 0, top: 0 }; } for (var attr in offsetAttributes) { @@ -5489,7 +5515,7 @@ fabric.util.string = { } docElem = doc.documentElement; - if ( typeof element.getBoundingClientRect !== "undefined" ) { + if ( typeof element.getBoundingClientRect !== 'undefined' ) { box = element.getBoundingClientRect(); } @@ -5508,33 +5534,33 @@ fabric.util.string = { * @param {String} attr Style attribute to get for element * @return {String} Style attribute value of the given element. */ - function getElementStyle(element, attr) { - if (!element.style) { - element.style = { }; - } - - if (fabric.document.defaultView && fabric.document.defaultView.getComputedStyle) { + var getElementStyle; + if (fabric.document.defaultView && fabric.document.defaultView.getComputedStyle) { + getElementStyle = function(element, attr) { return fabric.document.defaultView.getComputedStyle(element, null)[attr]; - } - else { + }; + } + else { + getElementStyle = function(element, attr) { var value = element.style[attr]; - if (!value && element.currentStyle) value = element.currentStyle[attr]; + if (!value && element.currentStyle) { + value = element.currentStyle[attr]; + } return value; - } + }; } (function () { - var style = fabric.document.documentElement.style; - - var selectProp = 'userSelect' in style - ? 'userSelect' - : 'MozUserSelect' in style - ? 'MozUserSelect' - : 'WebkitUserSelect' in style - ? 'WebkitUserSelect' - : 'KhtmlUserSelect' in style - ? 'KhtmlUserSelect' - : ''; + var style = fabric.document.documentElement.style, + selectProp = 'userSelect' in style + ? 'userSelect' + : 'MozUserSelect' in style + ? 'MozUserSelect' + : 'WebkitUserSelect' in style + ? 'WebkitUserSelect' + : 'KhtmlUserSelect' in style + ? 'KhtmlUserSelect' + : ''; /** * Makes element unselectable @@ -5587,7 +5613,7 @@ fabric.util.string = { * @param {Function} callback Callback to execute when script is finished loading */ function getScript(url, callback) { - var headEl = fabric.document.getElementsByTagName("head")[0], + var headEl = fabric.document.getElementsByTagName('head')[0], scriptEl = fabric.document.createElement('script'), loading = true; @@ -5631,9 +5657,9 @@ fabric.util.string = { var makeXHR = (function() { var factories = [ - function() { return new ActiveXObject("Microsoft.XMLHTTP"); }, - function() { return new ActiveXObject("Msxml2.XMLHTTP"); }, - function() { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); }, + function() { return new ActiveXObject('Microsoft.XMLHTTP'); }, + function() { return new ActiveXObject('Msxml2.XMLHTTP'); }, + function() { return new ActiveXObject('Msxml2.XMLHTTP.3.0'); }, function() { return new XMLHttpRequest(); } ]; for (var i = factories.length; i--; ) { @@ -5731,7 +5757,7 @@ if (typeof console !== 'undefined') { * @param {Number} [options.endValue=100] Ending value * @param {Number} [options.byValue=100] Value to modify the property by * @param {Function} [options.easing] Easing function - * @param {Number} [options.duration=500] Duration of change + * @param {Number} [options.duration=500] Duration of change (in ms) */ function animate(options) { @@ -5743,7 +5769,7 @@ if (typeof console !== 'undefined') { 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;}, + 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; @@ -5783,9 +5809,9 @@ if (typeof console !== 'undefined') { * @param {Function} callback Callback to invoke * @param {DOMElement} element optional Element to associate with animation */ - var requestAnimFrame = function() { + function requestAnimFrame() { return _requestAnimFrame.apply(fabric.window, arguments); - }; + } fabric.util.animate = animate; fabric.util.requestAnimFrame = requestAnimFrame; @@ -5796,8 +5822,13 @@ if (typeof console !== 'undefined') { (function() { 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); + 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 }; } @@ -5812,7 +5843,7 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeOutCubic(t, b, c, d) { - return c*((t=t/d-1)*t*t + 1) + b; + return c * ((t = t / d - 1) * t * t + 1) + b; } /** @@ -5821,8 +5852,10 @@ if (typeof console !== 'undefined') { */ function easeInOutCubic(t, b, c, d) { t /= d/2; - if (t < 1) return c/2*t*t*t + b; - return c/2*((t-=2)*t*t + 2) + b; + if (t < 1) { + return c / 2 * t * t * t + b; + } + return c / 2 * ((t -= 2) * t * t + 2) + b; } /** @@ -5830,7 +5863,7 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeInQuart(t, b, c, d) { - return c*(t/=d)*t*t*t + b; + return c * (t /= d) * t * t * t + b; } /** @@ -5838,7 +5871,7 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeOutQuart(t, b, c, d) { - return -c * ((t=t/d-1)*t*t*t - 1) + b; + return -c * ((t = t / d - 1) * t * t * t - 1) + b; } /** @@ -5846,9 +5879,11 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeInOutQuart(t, b, c, d) { - t /= d/2; - if (t < 1) return c/2*t*t*t*t + b; - return -c/2 * ((t-=2)*t*t*t - 2) + b; + t /= d / 2; + if (t < 1) { + return c / 2 * t * t * t * t + b; + } + return -c / 2 * ((t -= 2) * t * t * t - 2) + b; } /** @@ -5856,7 +5891,7 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeInQuint(t, b, c, d) { - return c*(t/=d)*t*t*t*t + b; + return c * (t /= d) * t * t * t * t + b; } /** @@ -5864,7 +5899,7 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeOutQuint(t, b, c, d) { - return c*((t=t/d-1)*t*t*t*t + 1) + b; + return c * ((t = t / d - 1) * t * t * t * t + 1) + b; } /** @@ -5872,9 +5907,11 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeInOutQuint(t, b, c, d) { - t /= d/2; - if (t < 1) return c/2*t*t*t*t*t + b; - return c/2*((t-=2)*t*t*t*t + 2) + b; + t /= d / 2; + if (t < 1) { + return c / 2 * t * t * t * t * t + b; + } + return c / 2 * ((t -= 2) * t * t * t * t + 2) + b; } /** @@ -5882,7 +5919,7 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeInSine(t, b, c, d) { - return -c * Math.cos(t/d * (Math.PI/2)) + c + b; + return -c * Math.cos(t / d * (Math.PI / 2)) + c + b; } /** @@ -5890,7 +5927,7 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeOutSine(t, b, c, d) { - return c * Math.sin(t/d * (Math.PI/2)) + b; + return c * Math.sin(t / d * (Math.PI / 2)) + b; } /** @@ -5898,7 +5935,7 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeInOutSine(t, b, c, d) { - return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b; + return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b; } /** @@ -5906,7 +5943,7 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeInExpo(t, b, c, d) { - return (t===0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b; + return (t === 0) ? b : c * Math.pow(2, 10 * (t / d - 1)) + b; } /** @@ -5914,7 +5951,7 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeOutExpo(t, b, c, d) { - return (t===d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b; + return (t === d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b; } /** @@ -5922,11 +5959,17 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeInOutExpo(t, b, c, d) { - if (t===0) return b; - if (t===d) return b+c; - t /= d/2; - if (t < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b; - return c/2 * (-Math.pow(2, -10 * --t) + 2) + b; + if (t === 0) { + return b; + } + if (t === d) { + return b + c; + } + t /= d / 2; + if (t < 1) { + return c / 2 * Math.pow(2, 10 * (t - 1)) + b; + } + return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b; } /** @@ -5934,7 +5977,7 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeInCirc(t, b, c, d) { - return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b; + return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b; } /** @@ -5942,7 +5985,7 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeOutCirc(t, b, c, d) { - return c * Math.sqrt(1 - (t=t/d-1)*t) + b; + return c * Math.sqrt(1 - (t = t / d - 1) * t) + b; } /** @@ -5950,9 +5993,11 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeInOutCirc(t, b, c, d) { - t /= d/2; - if (t < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b; - return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b; + t /= d / 2; + if (t < 1) { + return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b; + } + return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b; } /** @@ -5960,11 +6005,17 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeInElastic(t, b, c, d) { - var s=1.70158;var p=0;var a=c; - if (t===0) return b; + var s = 1.70158, p = 0, a = c; + if (t === 0) { + return b; + } t /= d; - if (t===1) return b+c; - if (!p) p=d*0.3; + if (t === 1) { + return b + c; + } + if (!p) { + p = d * 0.3; + } var opts = normalize(a, c, p, s); return -elastic(opts, t, d) + b; } @@ -5974,13 +6025,19 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeOutElastic(t, b, c, d) { - var s=1.70158;var p=0;var a=c; - if (t===0) return b; + var s = 1.70158, p = 0, a = c; + if (t === 0) { + return b; + } t /= d; - if (t===1) return b+c; - if (!p) p=d*0.3; + if (t === 1) { + return b + c; + } + if (!p) { + p = d * 0.3; + } 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; + return opts.a * Math.pow(2, -10 * t) * Math.sin((t * d - opts.s) * (2 * Math.PI) / opts.p ) + opts.c + b; } /** @@ -5988,14 +6045,22 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeInOutElastic(t, b, c, d) { - var s=1.70158;var p=0;var a=c; - if (t===0) return b; - t /= d/2; - if (t===2) return b+c; - if (!p) p=d*(0.3*1.5); + var s = 1.70158, p = 0, a = c; + if (t === 0) { + return b; + } + t /= d / 2; + if (t === 2) { + return b + c; + } + if (!p) { + p = d * (0.3 * 1.5); + } 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; + 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; } /** @@ -6003,8 +6068,10 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeInBack(t, b, c, d, s) { - if (s === undefined) s = 1.70158; - return c*(t/=d)*t*((s+1)*t - s) + b; + if (s === undefined) { + s = 1.70158; + } + return c * (t /= d) * t * ((s + 1) * t - s) + b; } /** @@ -6012,8 +6079,10 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeOutBack(t, b, c, d, s) { - if (s === undefined) s = 1.70158; - return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b; + if (s === undefined) { + s = 1.70158; + } + return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; } /** @@ -6021,10 +6090,14 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeInOutBack(t, b, c, d, s) { - if (s === undefined) s = 1.70158; - t /= d/2; - if (t < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b; - return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b; + if (s === undefined) { + s = 1.70158; + } + t /= d / 2; + if (t < 1) { + return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b; + } + return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b; } /** @@ -6032,7 +6105,7 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeInBounce(t, b, c, d) { - return c - easeOutBounce (d-t, 0, c, d) + b; + return c - easeOutBounce (d - t, 0, c, d) + b; } /** @@ -6040,14 +6113,17 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeOutBounce(t, b, c, d) { - if ((t/=d) < (1/2.75)) { - return c*(7.5625*t*t) + b; - } else if (t < (2/2.75)) { - return c*(7.5625*(t-=(1.5/2.75))*t + 0.75) + b; - } else if (t < (2.5/2.75)) { - return c*(7.5625*(t-=(2.25/2.75))*t + 0.9375) + b; - } else { - return c*(7.5625*(t-=(2.625/2.75))*t + 0.984375) + b; + if ((t /= d) < (1 / 2.75)) { + return c * (7.5625 * t * t) + b; + } + else if (t < (2/2.75)) { + return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b; + } + else if (t < (2.5/2.75)) { + return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b; + } + else { + return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b; } } @@ -6056,8 +6132,10 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ function easeInOutBounce(t, b, c, d) { - if (t < d/2) return easeInBounce (t*2, 0, c, d) * 0.5 + b; - return easeOutBounce (t*2-d, 0, c, d) * 0.5 + c*0.5 + b; + if (t < d / 2) { + return easeInBounce (t * 2, 0, c, d) * 0.5 + b; + } + return easeOutBounce(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b; } /** @@ -6072,7 +6150,7 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ easeInQuad: function(t, b, c, d) { - return c*(t/=d)*t + b; + return c * (t /= d) * t + b; }, /** @@ -6080,7 +6158,7 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ easeOutQuad: function(t, b, c, d) { - return -c *(t/=d)*(t-2) + b; + return -c * (t /= d) * (t - 2) + b; }, /** @@ -6088,9 +6166,11 @@ if (typeof console !== 'undefined') { * @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; + t /= (d / 2); + if (t < 1) { + return c / 2 * t * t + b; + } + return -c / 2 * ((--t) * (t - 2) - 1) + b; }, /** @@ -6098,7 +6178,7 @@ if (typeof console !== 'undefined') { * @memberOf fabric.util.ease */ easeInCubic: function(t, b, c, d) { - return c*(t/=d)*t*t + b; + return c * (t /= d) * t * t + b; }, easeOutCubic: easeOutCubic, @@ -6134,7 +6214,7 @@ if (typeof console !== 'undefined') { (function(global) { - "use strict"; + 'use strict'; /** * @name fabric @@ -6146,34 +6226,37 @@ if (typeof console !== 'undefined') { capitalize = fabric.util.string.capitalize, clone = fabric.util.object.clone, toFixed = fabric.util.toFixed, - multiplyTransformMatrices = fabric.util.multiplyTransformMatrices; + multiplyTransformMatrices = fabric.util.multiplyTransformMatrices, - var attributesMap = { - 'fill-opacity': 'fillOpacity', - 'fill-rule': 'fillRule', - 'font-family': 'fontFamily', - 'font-size': 'fontSize', - 'font-style': 'fontStyle', - 'font-weight': 'fontWeight', - 'cx': 'left', - 'x': 'left', - 'r': 'radius', - 'stroke-dasharray': 'strokeDashArray', - 'stroke-linecap': 'strokeLineCap', - 'stroke-linejoin': 'strokeLineJoin', - 'stroke-miterlimit':'strokeMiterLimit', - 'stroke-opacity': 'strokeOpacity', - 'stroke-width': 'strokeWidth', - 'text-decoration': 'textDecoration', - 'cy': 'top', - 'y': 'top', - 'transform': 'transformMatrix' - }; + attributesMap = { + cx: 'left', + x: 'left', + r: 'radius', + cy: 'top', + y: 'top', + display: 'visible', + visibility: 'visible', + transform: 'transformMatrix', + 'fill-opacity': 'fillOpacity', + 'fill-rule': 'fillRule', + 'font-family': 'fontFamily', + 'font-size': 'fontSize', + 'font-style': 'fontStyle', + 'font-weight': 'fontWeight', + 'stroke-dasharray': 'strokeDashArray', + 'stroke-linecap': 'strokeLineCap', + 'stroke-linejoin': 'strokeLineJoin', + 'stroke-miterlimit': 'strokeMiterLimit', + 'stroke-opacity': 'strokeOpacity', + 'stroke-width': 'strokeWidth', + 'text-decoration': 'textDecoration', + 'text-anchor': 'originX' + }, - var colorAttributes = { - 'stroke': 'strokeOpacity', - 'fill': 'fillOpacity' - }; + colorAttributes = { + stroke: 'strokeOpacity', + fill: 'fillOpacity' + }; function normalizeAttr(attr) { // transform attribute names @@ -6204,6 +6287,16 @@ if (typeof console !== 'undefined') { value = fabric.parseTransformAttribute(value); } } + else if (attr === 'visible') { + value = (value === 'none' || value === 'hidden') ? false : true; + // display=none on parent element always takes precedence over child element + if (parentAttributes.visible === false) { + value = false; + } + } + else if (attr === 'originX' /* text-anchor */) { + value = value === 'start' ? 'left' : value === 'end' ? 'right' : 'center'; + } isArray = Object.prototype.toString.call(value) === '[object Array]'; @@ -6284,30 +6377,30 @@ if (typeof console !== 'undefined') { ], // == begin transform regexp - number = '(?:[-+]?\\d+(?:\\.\\d+)?(?:e[-+]?\\d+)?)', + number = '(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)', - comma_wsp = '(?:\\s+,?\\s*|,\\s*)', + commaWsp = '(?:\\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*\\))', + commaWsp + '(' + number + ')' + + commaWsp + '(' + number + '))?\\s*\\))', scale = '(?:(scale)\\s*\\(\\s*(' + number + ')(?:' + - comma_wsp + '(' + number + '))?\\s*\\))', + commaWsp + '(' + number + '))?\\s*\\))', translate = '(?:(translate)\\s*\\(\\s*(' + number + ')(?:' + - comma_wsp + '(' + number + '))?\\s*\\))', + commaWsp + '(' + number + '))?\\s*\\))', matrix = '(?:(matrix)\\s*\\(\\s*' + - '(' + number + ')' + comma_wsp + - '(' + number + ')' + comma_wsp + - '(' + number + ')' + comma_wsp + - '(' + number + ')' + comma_wsp + - '(' + number + ')' + comma_wsp + + '(' + number + ')' + commaWsp + + '(' + number + ')' + commaWsp + + '(' + number + ')' + commaWsp + + '(' + number + ')' + commaWsp + + '(' + number + ')' + commaWsp + '(' + number + ')' + '\\s*\\))', @@ -6320,12 +6413,12 @@ if (typeof console !== 'undefined') { skewY + ')', - transforms = '(?:' + transform + '(?:' + comma_wsp + transform + ')*' + ')', + transforms = '(?:' + transform + '(?:' + commaWsp + transform + ')*' + ')', - transform_list = '^\\s*(?:' + transforms + '?)\\s*$', + transformList = '^\\s*(?:' + transforms + '?)\\s*$', // http://www.w3.org/TR/SVG/coords.html#TransformAttribute - reTransformList = new RegExp(transform_list), + reTransformList = new RegExp(transformList), // == end transform regexp reTransform = new RegExp(transform, 'g'); @@ -6333,8 +6426,8 @@ if (typeof console !== 'undefined') { return function(attributeValue) { // start with identity matrix - var matrix = iMatrix.concat(); - var matrices = [ ]; + var matrix = iMatrix.concat(), + matrices = [ ]; // return if no argument was given or // an argument does not match transform attribute regexp @@ -6350,11 +6443,12 @@ if (typeof console !== 'undefined') { operation = m[1], args = m.slice(2).map(parseFloat); - switch(operation) { + switch (operation) { case 'translate': translateMatrix(matrix, args); break; case 'rotate': + args[0] = fabric.util.degreesToRadians(args[0]); rotateMatrix(matrix, args); break; case 'scale': @@ -6393,19 +6487,19 @@ if (typeof console !== 'undefined') { if (!match) return; - var fontStyle = match[1]; - // Font variant is not used - // var fontVariant = match[2]; - var fontWeight = match[3]; - var fontSize = match[4]; - var lineHeight = match[5]; - var fontFamily = match[6]; + var fontStyle = match[1], + // font variant is not used + // fontVariant = match[2], + fontWeight = match[3], + fontSize = match[4], + lineHeight = match[5], + fontFamily = match[6]; if (fontStyle) { oStyle.fontStyle = fontStyle; } if (fontWeight) { - oStyle.fontSize = isNaN(parseFloat(fontWeight)) ? fontWeight : parseFloat(fontWeight); + oStyle.fontWeight = isNaN(parseFloat(fontWeight)) ? fontWeight : parseFloat(fontWeight); } if (fontSize) { oStyle.fontSize = parseFloat(fontSize); @@ -6493,22 +6587,22 @@ if (typeof console !== 'undefined') { */ fabric.parseSVGDocument = (function() { - var reAllowedSVGTagNames = /^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/; + var reAllowedSVGTagNames = /^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/, - // http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute - // \d doesn't quite cut it (as we need to match an actual float number) + // http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute + // \d doesn't quite cut it (as we need to match an actual float number) - // matches, e.g.: +14.56e-12, etc. - var reNum = '(?:[-+]?\\d+(?:\\.\\d+)?(?:e[-+]?\\d+)?)'; + // matches, e.g.: +14.56e-12, etc. + reNum = '(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)', - var reViewBoxAttrValue = new RegExp( - '^' + - '\\s*(' + reNum + '+)\\s*,?' + - '\\s*(' + reNum + '+)\\s*,?' + - '\\s*(' + reNum + '+)\\s*,?' + - '\\s*(' + reNum + '+)\\s*' + - '$' - ); + reViewBoxAttrValue = new RegExp( + '^' + + '\\s*(' + reNum + '+)\\s*,?' + + '\\s*(' + reNum + '+)\\s*,?' + + '\\s*(' + reNum + '+)\\s*,?' + + '\\s*(' + reNum + '+)\\s*' + + '$' + ); function hasAncestorWithNodeName(element, nodeName) { while (element && (element = element.parentNode)) { @@ -6525,10 +6619,10 @@ if (typeof console !== 'undefined') { var startTime = new Date(), descendants = fabric.util.toArray(doc.getElementsByTagName('*')); - if (descendants.length === 0) { + if (descendants.length === 0 && fabric.isLikelyNode) { // we're likely in node, where "o3-xml" library fails to gEBTN("*") // https://github.com/ajaxorg/node-o3-xml/issues/21 - descendants = doc.selectNodes("//*[name(.)!='svg']"); + descendants = doc.selectNodes('//*[name(.)!="svg"]'); var arr = [ ]; for (var i = 0, len = descendants.length; i < len; i++) { arr[i] = descendants[i]; @@ -6541,30 +6635,43 @@ if (typeof console !== 'undefined') { !hasAncestorWithNodeName(el, /^(?:pattern|defs)$/); // http://www.w3.org/TR/SVG/struct.html#DefsElement }); - if (!elements || (elements && !elements.length)) return; + if (!elements || (elements && !elements.length)) { + callback && callback([], {}); + return; + } var viewBoxAttr = doc.getAttribute('viewBox'), - widthAttr = doc.getAttribute('width'), - heightAttr = doc.getAttribute('height'), + widthAttr = parseFloat(doc.getAttribute('width')), + heightAttr = parseFloat(doc.getAttribute('height')), width = null, height = null, + viewBoxWidth, + viewBoxHeight, minX, minY; if (viewBoxAttr && (viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))) { - minX = parseInt(viewBoxAttr[1], 10); - minY = parseInt(viewBoxAttr[2], 10); - width = parseInt(viewBoxAttr[3], 10); - height = parseInt(viewBoxAttr[4], 10); + minX = parseFloat(viewBoxAttr[1]); + minY = parseFloat(viewBoxAttr[2]); + viewBoxWidth = parseFloat(viewBoxAttr[3]); + viewBoxHeight = parseFloat(viewBoxAttr[4]); } - // values of width/height attributes overwrite those extracted from viewbox attribute - width = widthAttr ? parseFloat(widthAttr) : width; - height = heightAttr ? parseFloat(heightAttr) : height; + if (viewBoxWidth && widthAttr && viewBoxWidth !== widthAttr) { + width = viewBoxWidth; + height = viewBoxHeight; + } + else { + // values of width/height attributes overwrite those extracted from viewbox attribute + width = widthAttr ? widthAttr : viewBoxWidth; + height = heightAttr ? heightAttr : viewBoxHeight; + } var options = { width: width, - height: height + height: height, + widthAttr: widthAttr, + heightAttr: heightAttr }; fabric.gradientDefs = fabric.getGradientDefs(doc); @@ -6748,7 +6855,7 @@ if (typeof console !== 'undefined') { * @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); + new fabric.ElementsParser(elements, callback, options, reviver).parse(); }, /** @@ -6762,7 +6869,9 @@ if (typeof console !== 'undefined') { var oStyle = { }, style = element.getAttribute('style'); - if (!style) return oStyle; + if (!style) { + return oStyle; + } if (typeof style === 'string') { parseStyleString(style, oStyle); @@ -6798,21 +6907,27 @@ if (typeof console !== 'undefined') { len = points.length; for (; i < len; i++) { var pair = points[i].split(','); - parsedPoints.push({ x: parseFloat(pair[0]), y: parseFloat(pair[1]) }); + 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]) }); + parsedPoints.push({ + x: parseFloat(points[i]), + y: parseFloat(points[i + 1]) + }); } } // odd number of points is an error - if (parsedPoints.length % 2 !== 0) { + // if (parsedPoints.length % 2 !== 0) { // return null; - } + // } return parsedPoints; }, @@ -6892,13 +7007,13 @@ if (typeof console !== 'undefined') { function onComplete(r) { var xml = r.responseXML; - if (!xml.documentElement && fabric.window.ActiveXObject && r.responseText) { + if (xml && !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; + if (!xml || !xml.documentElement) return; fabric.parseSVGDocument(xml.documentElement, function (results, options) { svgCache.set(url, { @@ -6988,78 +7103,80 @@ if (typeof console !== 'undefined') { })(typeof exports !== 'undefined' ? exports : this); -fabric.ElementsParser = { +fabric.ElementsParser = function(elements, callback, options, reviver) { + this.elements = elements; + this.callback = callback; + this.options = options; + this.reviver = reviver; +}; - parse: function(elements, callback, options, reviver) { +fabric.ElementsParser.prototype.parse = function() { + this.instances = new Array(this.elements.length); + this.numElements = this.elements.length; - this.elements = elements; - this.callback = callback; - this.options = options; - this.reviver = reviver; + this.createObjects(); +}; - this.instances = new Array(elements.length); - this.numElements = elements.length; +fabric.ElementsParser.prototype.createObjects = function() { + for (var i = 0, len = this.elements.length; i < len; i++) { + (function(_this, i) { + setTimeout(function() { + _this.createObject(_this.elements[i], i); + }, 0); + })(this, i); + } +}; - this.createObjects(); - }, - - createObjects: function() { - for (var i = 0, len = this.elements.length; i < len; i++) { - this.createObject(this.elements[i], i); +fabric.ElementsParser.prototype.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(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(); - } - }, +fabric.ElementsParser.prototype._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(); + } +}; - _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(); - } - }, +fabric.ElementsParser.prototype.createCallback = function(index, el) { + var _this = this; + return function(obj) { + _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); - } +fabric.ElementsParser.prototype.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"; + 'use strict'; /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */ @@ -7309,7 +7426,7 @@ fabric.ElementsParser = { * @return {String} */ toString: function () { - return this.x + "," + this.y; + return this.x + ',' + this.y; }, /** @@ -7348,172 +7465,171 @@ fabric.ElementsParser = { })(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, + uaT = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x), + ubT = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x), + uB = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y); + if (uB !== 0) { + var ua = uaT / uB, + ub = ubT / uB; + 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 (uaT === 0 || ubT === 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"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }); @@ -7556,6 +7672,11 @@ fabric.ElementsParser = { color = Color.colorNameMap[color]; } + if (color === 'transparent') { + this.setSource([255,255,255,0]); + return; + } + source = Color.sourceFromHex(color); if (!source) { @@ -7674,15 +7795,15 @@ fabric.ElementsParser = { * @return {String} ex: FF5555 */ toHex: function() { - var source = this.getSource(); + var source = this.getSource(), r, g, b; - var r = source[0].toString(16); + r = source[0].toString(16); r = (r.length === 1) ? ('0' + r) : r; - var g = source[1].toString(16); + g = source[1].toString(16); g = (g.length === 1) ? ('0' + g) : g; - var b = source[2].toString(16); + b = source[2].toString(16); b = (b.length === 1) ? ('0' + b) : b; return r.toUpperCase() + g.toUpperCase() + b.toUpperCase(); @@ -7769,7 +7890,7 @@ fabric.ElementsParser = { * @field * @memberOf fabric.Color */ - fabric.Color.reRGBa = /^rgba?\(\s*(\d{1,3}\%?)\s*,\s*(\d{1,3}\%?)\s*,\s*(\d{1,3}\%?)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/; + fabric.Color.reRGBa = /^rgba?\(\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/; /** * Regex matching color in HSL or HSLA formats (ex: hsl(200, 80%, 10%), hsla(300, 50%, 80%, 0.5), hsla( 300 , 50% , 80% , 0.5 )) @@ -7795,23 +7916,23 @@ fabric.ElementsParser = { * @see: http://www.w3.org/TR/CSS2/syndata.html#color-units */ fabric.Color.colorNameMap = { - 'aqua': '#00FFFF', - 'black': '#000000', - 'blue': '#0000FF', - 'fuchsia': '#FF00FF', - 'gray': '#808080', - 'green': '#008000', - 'lime': '#00FF00', - 'maroon': '#800000', - 'navy': '#000080', - 'olive': '#808000', - 'orange': '#FFA500', - 'purple': '#800080', - 'red': '#FF0000', - 'silver': '#C0C0C0', - 'teal': '#008080', - 'white': '#FFFFFF', - 'yellow': '#FFFF00' + aqua: '#00FFFF', + black: '#000000', + blue: '#0000FF', + fuchsia: '#FF00FF', + gray: '#808080', + green: '#008000', + lime: '#00FF00', + maroon: '#800000', + navy: '#000080', + olive: '#808000', + orange: '#FFA500', + purple: '#800080', + red: '#FF0000', + silver: '#C0C0C0', + teal: '#008080', + white: '#FFFFFF', + yellow: '#FFFF00' }; /** @@ -7822,11 +7943,21 @@ fabric.ElementsParser = { * @return {Number} */ function hue2rgb(p, q, t){ - if (t < 0) t += 1; - if (t > 1) t -= 1; - if (t < 1/6) return p + (q - p) * 6 * t; - if (t < 1/2) return q; - if (t < 2/3) return p + (q - p) * (2/3 - t) * 6; + if (t < 0) { + t += 1; + } + if (t > 1) { + t -= 1; + } + if (t < 1/6) { + return p + (q - p) * 6 * t; + } + if (t < 1/2) { + return q; + } + if (t < 2/3) { + return p + (q - p) * (2/3 - t) * 6; + } return p; } @@ -7903,8 +8034,8 @@ fabric.ElementsParser = { r = g = b = l; } else { - var q = l <= 0.5 ? l * (s + 1) : l + s - l * s; - var p = l * 2 - q; + var q = l <= 0.5 ? l * (s + 1) : l + s - l * s, + p = l * 2 - q; r = hue2rgb(p, q, h + 1/3); g = hue2rgb(p, q, h); @@ -7994,7 +8125,7 @@ fabric.ElementsParser = { if (style) { var keyValuePairs = style.split(/\s*;\s*/); - if (keyValuePairs[keyValuePairs.length-1] === '') { + if (keyValuePairs[keyValuePairs.length - 1] === '') { keyValuePairs.pop(); } @@ -8097,7 +8228,11 @@ fabric.ElementsParser = { addColorStop: function(colorStop) { for (var position in colorStop) { var color = new fabric.Color(colorStop[position]); - this.colorStops.push({offset: position, color: color.toRgb(), opacity: color.getAlpha()}); + this.colorStops.push({ + offset: position, + color: color.toRgb(), + opacity: color.getAlpha() + }); } return this; }, @@ -8461,10 +8596,10 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ * @return {String} SVG representation of a pattern */ toSVG: function(object) { - var patternSource = typeof this.source === 'function' ? this.source() : this.source; - var patternWidth = patternSource.width / object.getWidth(); - var patternHeight = patternSource.height / object.getHeight(); - var patternImgSrc = ''; + var patternSource = typeof this.source === 'function' ? this.source() : this.source, + patternWidth = patternSource.width / object.getWidth(), + patternHeight = patternSource.height / object.getHeight(), + patternImgSrc = ''; if (patternSource.src) { patternImgSrc = patternSource.src; @@ -8493,11 +8628,23 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ * @return {CanvasPattern} */ toLive: function(ctx) { - var source = typeof this.source === 'function' ? this.source() : this.source; + var source = typeof this.source === 'function' + ? this.source() + : this.source; + + // if the image failed to load, return, and allow rest to continue loading + if (!source) { + return ''; + } + // if an image if (typeof source.src !== 'undefined') { - if (!source.complete) return ''; - if (source.naturalWidth === 0 || source.naturalHeight === 0) return ''; + if (!source.complete) { + return ''; + } + if (source.naturalWidth === 0 || source.naturalHeight === 0) { + return ''; + } } return ctx.createPattern(source, this.repeat); } @@ -8506,7 +8653,7 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }); @@ -8587,9 +8734,8 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ * @return {Object} Shadow object with color, offsetX, offsetY and blur */ _parseShadow: function(shadow) { - var shadowStr = shadow.trim(); - - var offsetsAndBlur = fabric.Shadow.reOffsetsAndBlur.exec(shadowStr) || [ ], + var shadowStr = shadow.trim(), + offsetsAndBlur = fabric.Shadow.reOffsetsAndBlur.exec(shadowStr) || [ ], color = shadowStr.replace(fabric.Shadow.reOffsetsAndBlur, '') || 'rgb(0,0,0)'; return { @@ -8679,7 +8825,7 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ (function () { - "use strict"; + 'use strict'; if (fabric.StaticCanvas) { fabric.warn('fabric.StaticCanvas is already defined.'); @@ -8803,7 +8949,14 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ * @type Boolean * @default */ - allowTouchScrolling: false, + allowTouchScrolling: false, + + /** + * Indicates whether this canvas will use image smoothing, this is on by default in browsers + * @type Boolean + * @default + */ + imageSmoothingEnabled: true, /** * The transformation (in the format of Canvas transform) which focuses the viewport @@ -8830,6 +8983,7 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ this._createLowerCanvas(el); this._initOptions(options); + this._setImageSmoothing(); if (options.overlayImage) { this.setOverlayImage(options.overlayImage, this.renderAll.bind(this)); @@ -8989,6 +9143,20 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ return this.__setBgOverlayColor('backgroundColor', backgroundColor, callback); }, + /** + * @private + * @see {@link http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-imagesmoothingenabled|WhatWG Canvas Standard} + */ + _setImageSmoothing: function(){ + var ctx = this.getContext(); + + ctx.imageSmoothingEnabled = this.imageSmoothingEnabled; + ctx.webkitImageSmoothingEnabled = this.imageSmoothingEnabled; + ctx.mozImageSmoothingEnabled = this.imageSmoothingEnabled; + ctx.msImageSmoothingEnabled = this.imageSmoothingEnabled; + ctx.oImageSmoothingEnabled = this.imageSmoothingEnabled; + }, + /** * @private * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundImage|backgroundImage} @@ -9076,11 +9244,14 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ this[prop] = options[prop]; } - this.width = parseInt(this.lowerCanvasEl.width, 10) || 0; - this.height = parseInt(this.lowerCanvasEl.height, 10) || 0; + this.width = this.width || parseInt(this.lowerCanvasEl.width, 10) || 0; + this.height = this.height || parseInt(this.lowerCanvasEl.height, 10) || 0; if (!this.lowerCanvasEl.style) return; + this.lowerCanvasEl.width = this.width; + this.lowerCanvasEl.height = this.height; + this.lowerCanvasEl.style.width = this.width + 'px'; this.lowerCanvasEl.style.height = this.height + 'px'; }, @@ -9402,8 +9573,8 @@ 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(); + var canvasToDrawOn = this[(allOnTop === true && this.interactive) ? 'contextTop' : 'contextContainer'], + activeGroup = this.getActiveGroup(); if (this.contextTop && this.selection && !this._groupSelector) { this.clearContext(this.contextTop); @@ -9444,12 +9615,21 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ * @param {fabric.Group} activeGroup */ _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]))) { + var i, length; + + // fast path + if (!activeGroup) { + for (i = 0, length = this._objects.length; i < length; ++i) { this._draw(ctx, this._objects[i]); } } + else { + for (i = 0, length = this._objects.length; i < length; ++i) { + if (this._objects[i] && !activeGroup.contains(this._objects[i])) { + this._draw(ctx, this._objects[i]); + } + } + } }, /** @@ -9538,9 +9718,7 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ activeGroup.render(ctx); } - if (this.overlayImage) { - ctx.drawImage(this.overlayImage, this.overlayImageLeft, this.overlayImageTop); - } + this._renderOverlay(ctx); this.fire('after:render'); @@ -9657,11 +9835,20 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ fabric.util.populateWithProperties(this, data, propertiesToInclude); if (activeGroup) { - this.setActiveGroup(new fabric.Group(activeGroup.getObjects())); + this.setActiveGroup(new fabric.Group(activeGroup.getObjects(), { + originX: 'center', + originY: 'center' + })); activeGroup.forEachObject(function(o) { o.set('active', true); }); + + if (this._currentTransform) { + this._currentTransform.target = this.getActiveGroup(); + } } + + return data; }, @@ -9798,7 +9985,7 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ 'width="', (options.viewBox ? options.viewBox.width : this.width), '" ', 'height="', (options.viewBox ? options.viewBox.height : this.height), '" ', (this.backgroundColor && !this.backgroundColor.toLive - ? 'style="background-color: ' + this.backgroundColor +'" ' + ? 'style="background-color: ' + this.backgroundColor + '" ' : null), (options.viewBox ? 'viewBox="' + @@ -9930,7 +10117,7 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ newIdx = idx; // traverse down the stack looking for the nearest intersecting object - for (var i=idx-1; i>=0; --i) { + for (var i = idx - 1; i >= 0; --i) { var isIntersecting = object.intersectsWithObject(this._objects[i]) || object.isContainedWithinObject(this._objects[i]) || @@ -9960,7 +10147,7 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ var idx = this._objects.indexOf(object); // if object is not on top of stack (last item in an array) - if (idx !== this._objects.length-1) { + if (idx !== this._objects.length - 1) { var newIdx = this._findNewUpperIndex(object, idx, intersecting); removeFromArray(this._objects, object); @@ -9993,7 +10180,7 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ } } else { - newIdx = idx+1; + newIdx = idx + 1; } return newIdx; @@ -10028,7 +10215,7 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ * @return {String} string representation of an instance */ toString: function () { - return '#'; } }); @@ -10314,32 +10501,32 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype */ _render: function() { var ctx = this.canvas.contextTop; - var v = this.canvas.viewportTransform; + var v = this.getViewportTransform(); ctx.save(); ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); ctx.beginPath(); - var p1 = this._points[0]; - var p2 = this._points[1]; + var p1 = this._points[0], + p2 = this._points[1]; //if we only have 2 points in the path and they are the same //it means that the user only clicked the canvas without moving the mouse //then we should be drawing a dot. A path isn't drawn between two identical dots //that's why we set them apart a bit if (this._points.length === 2 && p1.x === p2.x && p1.y === p2.y) { - p1.x -= 0.5; - p2.x += 0.5; + p1.x -= 0.5; + p2.x += 0.5; } ctx.moveTo(p1.x, p1.y); for (var i = 1, len = this._points.length; i < len; i++) { - // we pick the point between pi+1 & pi+2 as the + // we pick the point between pi + 1 & pi + 2 as the // end point and p1 as our control point. var midPoint = p1.midPointFrom(p2); ctx.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y); p1 = this._points[i]; - p2 = this._points[i+1]; + p2 = this._points[i + 1]; } // Draw last line as a straight line while // we wait for the next point to be able to calculate @@ -10365,7 +10552,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype * @param {Array} points * @return {Object} object with minx, miny, maxx, maxy */ - getPathBoundingBox: function(points) { + getPathBoundingBox: function(points) { var xBounds = [], yBounds = [], p1 = points[0], @@ -10381,19 +10568,19 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype yBounds.push(midPoint.y); p1 = points[i]; - p2 = points[i+1]; + p2 = points[i + 1]; startPoint = midPoint; - } // end for + } - xBounds.push(p1.x); - yBounds.push(p1.y); + xBounds.push(p1.x); + yBounds.push(p1.y); - return { - minx: utilMin(xBounds), - miny: utilMin(yBounds), - maxx: utilMax(xBounds), - maxy: utilMax(yBounds) - }; + return { + minx: utilMin(xBounds), + miny: utilMin(yBounds), + maxx: utilMax(xBounds), + maxy: utilMax(yBounds) + }; }, /** @@ -10402,9 +10589,9 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype * @return {String} SVG path */ convertPointsToSVGPath: function(points, minX, maxX, minY) { - var path = []; - var p1 = new fabric.Point(points[0].x - minX, points[0].y - minY); - var p2 = new fabric.Point(points[1].x - minX, points[1].y - minY); + var path = [], + p1 = new fabric.Point(points[0].x - minX, points[0].y - minY), + p2 = new fabric.Point(points[1].x - minX, points[1].y - minY); path.push('M ', points[0].x - minX, ' ', points[0].y - minY, ' '); for (var i = 1, len = points.length; i < len; i++) { @@ -10414,8 +10601,8 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype // start point is p(i-1) value. path.push('Q ', p1.x, ' ', p1.y, ' ', midPoint.x, ' ', midPoint.y, ' '); p1 = new fabric.Point(points[i].x - minX, points[i].y - minY); - if ((i+1) < points.length) { - p2 = new fabric.Point(points[i+1].x - minX, points[i+1].y - minY); + if ((i + 1) < points.length) { + p2 = new fabric.Point(points[i + 1].x - minX, points[i + 1].y - minY); } } path.push('L ', p1.x, ' ', p1.y, ' '); @@ -10454,7 +10641,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype ctx.closePath(); var pathData = this._getSVGPathData().join(''); - if (pathData === "M 0 0 Q 0 0 0 0 L 0 0") { + if (pathData === 'M 0 0 Q 0 0 0 0 L 0 0') { // do not create 0 width/height paths, as they are // rendered inconsistently across browsers // Firefox 4, for example, renders a dot, @@ -10464,8 +10651,8 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype } // set path origin coordinates based on our bounding box - var originLeft = this.box.minx + (this.box.maxx - this.box.minx) /2; - var originTop = this.box.miny + (this.box.maxy - this.box.miny) /2; + var originLeft = this.box.minx + (this.box.maxx - this.box.minx) / 2, + originTop = this.box.miny + (this.box.maxy - this.box.miny) / 2; this.canvas.contextTop.arc(originLeft, originTop, 3, 0, Math.PI * 2, false); @@ -10518,10 +10705,10 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric * @param {Object} pointer */ drawDot: function(pointer) { - var point = this.addPoint(pointer); - var ctx = this.canvas.contextTop; + var point = this.addPoint(pointer), + ctx = this.canvas.contextTop; - var v = this.canvas.viewportTransform; + var v = this.getViewportTransform(); ctx.save(); ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); @@ -10562,15 +10749,15 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric var circles = [ ]; for (var i = 0, len = this.points.length; i < len; i++) { - var point = this.points[i]; - var circle = new fabric.Circle({ - radius: this.points[i].radius, - left: point.x, - top: point.y, - originX: 'center', - originY: 'center', - fill: this.points[i].fill - }); + var point = this.points[i], + circle = new fabric.Circle({ + radius: point.radius, + left: point.x, + top: point.y, + originX: 'center', + originY: 'center', + fill: point.fill + }); this.shadow && circle.setShadow(this.shadow); @@ -10593,12 +10780,12 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric * @return {fabric.Point} Just added pointer point */ addPoint: function(pointer) { - var pointerPoint = new fabric.Point(pointer.x, pointer.y); + var pointerPoint = new fabric.Point(pointer.x, pointer.y), - var circleRadius = fabric.util.getRandomInt( - Math.max(0, this.width - 20), this.width + 20) / 2; + circleRadius = fabric.util.getRandomInt( + Math.max(0, this.width - 20), this.width + 20) / 2, - var circleColor = new fabric.Color(this.color) + circleColor = new fabric.Color(this.color) .setAlpha(fabric.util.getRandomInt(0, 100) / 100) .toRgba(); @@ -10763,7 +10950,7 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric var ctx = this.canvas.contextTop; ctx.fillStyle = this.color; - var v = this.canvas.viewportTransform; + var v = this.getViewportTransform(); ctx.save(); ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); @@ -10903,6 +11090,8 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab * @fires mouse:down * @fires mouse:move * @fires mouse:up + * @fires mouse:over + * @fires mouse:out * */ fabric.Canvas = fabric.util.createClass(fabric.StaticCanvas, /** @lends fabric.Canvas.prototype */ { @@ -11077,10 +11266,10 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab var t = this._currentTransform; t.target.set({ - 'scaleX': t.original.scaleX, - 'scaleY': t.original.scaleY, - 'left': t.original.left, - 'top': t.original.top + scaleX: t.original.scaleX, + scaleY: t.original.scaleY, + left: t.original.left, + top: t.original.top }); if (this._shouldCenterTransform(e, t.target)) { @@ -11127,7 +11316,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab // http://www.geog.ubc.ca/courses/klink/gis.notes/ncgia/u32.html // http://idav.ucdavis.edu/~okreylos/TAship/Spring2000/PointInPolygon.html - return (target.containsPoint(xy) || target._findTargetCorner(e, this._offset)); + return (target.containsPoint(xy) || target._findTargetCorner(pointer)); }, /** @@ -11137,14 +11326,12 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab var activeGroup = this.getActiveGroup(), x = pointer.x, y = pointer.y, + isObjectInGroup = ( + activeGroup && + object.type !== 'group' && + activeGroup.contains(object)), lt; - var isObjectInGroup = ( - activeGroup && - object.type !== 'group' && - activeGroup.contains(object) - ); - if (isObjectInGroup) { lt = new fabric.Point(activeGroup.left, activeGroup.top); lt = fabric.util.transformPoint(lt, this.viewportTransform, true); @@ -11279,11 +11466,8 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab _setupCurrentTransform: function (e, target) { if (!target) return; - var corner = target._findTargetCorner(e, this._offset), - pointer = fabric.util.transformPoint( - getPointer(e, this.upperCanvasEl), - fabric.util.invertTransform(this.viewportTransform) - ), + var pointer = this.getPointer(e), + corner = target._findTargetCorner(this.getPointer(e, true)), action = this._getActionFromCorner(target, corner), origin = this._getOriginFromCorner(target, corner); @@ -11345,7 +11529,6 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab */ _scaleObject: function (x, y, by) { var t = this._currentTransform, - offset = this._offset, target = t.target, lockScalingX = target.get('lockScalingX'), lockScalingY = target.get('lockScalingY'); @@ -11353,8 +11536,8 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab if (lockScalingX && lockScalingY) return; // Get the constraint point - var constraintPosition = target.translateToOriginPoint(target.getCenterPoint(), t.originX, t.originY); - var localMouse = target.toLocalPoint(new fabric.Point(x - offset.left, y - offset.top), t.originX, t.originY); + var constraintPosition = target.translateToOriginPoint(target.getCenterPoint(), t.originX, t.originY), + localMouse = target.toLocalPoint(new fabric.Point(x, y), t.originX, t.originY); this._setLocalMouse(localMouse, t); @@ -11401,9 +11584,8 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab */ _scaleObjectEqually: function(localMouse, target, transform) { - var dist = localMouse.y + localMouse.x; - - var lastDist = (target.height + (target.strokeWidth)) * transform.original.scaleY + + var dist = localMouse.y + localMouse.x, + lastDist = (target.height + (target.strokeWidth)) * transform.original.scaleY + (target.width + (target.strokeWidth)) * transform.original.scaleX; // We use transform.scaleX/Y instead of target.scaleX/Y @@ -11500,13 +11682,12 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab */ _rotateObject: function (x, y) { - var t = this._currentTransform, - o = this._offset; + var t = this._currentTransform; if (t.target.get('lockRotation')) return; - var lastAngle = atan2(t.ey - t.top - o.top, t.ex - t.left - o.left), - curAngle = atan2(y - t.top - o.top, x - t.left - o.left), + var lastAngle = atan2(t.ey - t.top, t.ex - t.left), + curAngle = atan2(y - t.top, x - t.left), angle = radiansToDegrees(curAngle - lastAngle + t.theta); // normalize angle to positive value @@ -11559,15 +11740,15 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab // selection border if (this.selectionDashArray.length > 1) { - var px = groupSelector.ex + STROKE_OFFSET - ((left > 0) ? 0: aleft); - var py = groupSelector.ey + STROKE_OFFSET - ((top > 0) ? 0: atop); + var px = groupSelector.ex + STROKE_OFFSET - ((left > 0) ? 0: aleft), + py = groupSelector.ey + STROKE_OFFSET - ((top > 0) ? 0: atop); ctx.beginPath(); - fabric.util.drawDashedLine(ctx, px, py, px+aleft, py, this.selectionDashArray); - fabric.util.drawDashedLine(ctx, px, py+atop-1, px+aleft, py+atop-1, this.selectionDashArray); - fabric.util.drawDashedLine(ctx, px, py, px, py+atop, this.selectionDashArray); - fabric.util.drawDashedLine(ctx, px+aleft-1, py, px+aleft-1, py+atop, this.selectionDashArray); + fabric.util.drawDashedLine(ctx, px, py, px + aleft, py, this.selectionDashArray); + fabric.util.drawDashedLine(ctx, px, py + atop - 1, px + aleft, py + atop - 1, this.selectionDashArray); + fabric.util.drawDashedLine(ctx, px, py, px, py + atop, this.selectionDashArray); + fabric.util.drawDashedLine(ctx, px + aleft - 1, py, px + aleft - 1, py + atop, this.selectionDashArray); ctx.closePath(); ctx.stroke(); @@ -11591,7 +11772,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab this.lastRenderedObjectWithControlsAboveOverlay && this.lastRenderedObjectWithControlsAboveOverlay.visible && this.containsPoint(e, this.lastRenderedObjectWithControlsAboveOverlay) && - this.lastRenderedObjectWithControlsAboveOverlay._findTargetCorner(e, this._offset)); + this.lastRenderedObjectWithControlsAboveOverlay._findTargetCorner(this.getPointer(e, true))); }, /** @@ -11612,7 +11793,51 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab return activeGroup; } - return this._searchPossibleTargets(e); + var target = this._searchPossibleTargets(e); + this._fireOverOutEvents(target); + return target; + }, + + /** + * @private + */ + _fireOverOutEvents: function(target) { + if (target) { + if (this._hoveredTarget !== target) { + this.fire('mouse:over', { target: target }); + target.fire('mouseover'); + if (this._hoveredTarget) { + this.fire('mouse:out', { target: this._hoveredTarget }); + this._hoveredTarget.fire('mouseout'); + } + this._hoveredTarget = target; + } + } + else if (this._hoveredTarget) { + this.fire('mouse:out', { target: this._hoveredTarget }); + this._hoveredTarget.fire('mouseout'); + this._hoveredTarget = null; + } + }, + + /** + * @private + */ + _checkTarget: function(e, obj, pointer) { + if (obj && + obj.visible && + obj.evented && + this.containsPoint(e, obj)){ + if ((this.perPixelTargetFind || obj.perPixelTargetFind) && !obj.isEditing) { + var isTransparent = this.isTargetTransparent(obj, pointer.x, pointer.y); + if (!isTransparent) { + return true; + } + } + else { + return true; + } + } }, /** @@ -11621,33 +11846,15 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab _searchPossibleTargets: function(e) { // Cache all targets where their bounding box contains point. - var possibleTargets = [], - target, + var target, pointer = this.getPointer(e, true); - for (var i = this._objects.length; i--; ) { - if (this._objects[i] && - this._objects[i].visible && - this._objects[i].evented && - this.containsPoint(e, this._objects[i])) { + var i = this._objects.length; - if (this.perPixelTargetFind || this._objects[i].perPixelTargetFind) { - possibleTargets[possibleTargets.length] = this._objects[i]; - } - else { - target = this._objects[i]; - this.relatedTarget = target; - break; - } - } - } - - for (var j = 0, len = possibleTargets.length; j < len; j++) { - pointer = this.getPointer(e, true); - var isTransparent = this.isTargetTransparent(possibleTargets[j], pointer.x, pointer.y); - if (!isTransparent) { - target = possibleTargets[j]; - this.relatedTarget = target; + while (i--) { + if (this._checkTarget(e, this._objects[i], pointer)){ + this.relatedTarget = this._objects[i]; + target = this._objects[i]; break; } } @@ -11664,7 +11871,12 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab if (!upperCanvasEl) { upperCanvasEl = this.upperCanvasEl; } - var pointer = getPointer(e, upperCanvasEl); + var pointer = getPointer(e, upperCanvasEl), + bounds = upperCanvasEl.getBoundingClientRect(), + cssScale; + + pointer.x = pointer.x - this._offset.left; + pointer.y = pointer.y - this._offset.top; if (!ignoreZoom) { pointer = fabric.util.transformPoint( pointer, @@ -11672,9 +11884,19 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab ); } + if (bounds.width === 0 || bounds.height === 0) { + // If bounds are not available (i.e. not visible), do not apply scale. + cssScale = { width: 1, height: 1 }; + } + else { + cssScale = { + width: upperCanvasEl.width / bounds.width, + height: upperCanvasEl.height / bounds.height + }; + } return { - x: pointer.x - this._offset.left, - y: pointer.y - this._offset.top + x: pointer.x * cssScale.width, + y: pointer.y * cssScale.height }; }, @@ -11985,32 +12207,36 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab (function(){ - var cursorMap = [ - 'n-resize', - 'ne-resize', - 'e-resize', - 'se-resize', - 's-resize', - 'sw-resize', - 'w-resize', - 'nw-resize' - ], - cursorOffset = { - 'mt': 0, // n - 'tr': 1, // ne - 'mr': 2, // e - 'br': 3, // se - 'mb': 4, // s - 'bl': 5, // sw - 'ml': 6, // w - 'tl': 7 // nw + var cursorOffset = { + mt: 0, // n + tr: 1, // ne + mr: 2, // e + br: 3, // se + mb: 4, // s + bl: 5, // sw + ml: 6, // w + tl: 7 // nw }, addListener = fabric.util.addListener, - removeListener = fabric.util.removeListener, - getPointer = fabric.util.getPointer; + removeListener = fabric.util.removeListener; fabric.util.object.extend(fabric.Canvas.prototype, /** @lends fabric.Canvas.prototype */ { + /** + * Map of cursor style values for each of the object controls + * @private + */ + cursorMap: [ + 'n-resize', + 'ne-resize', + 'e-resize', + 'se-resize', + 's-resize', + 'sw-resize', + 'w-resize', + 'nw-resize' + ], + /** * Adds mouse listeners to canvas * @private @@ -12126,14 +12352,20 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab _onMouseDown: function (e) { this.__onMouseDown(e); - addListener(fabric.document, 'mouseup', this._onMouseUp); addListener(fabric.document, 'touchend', this._onMouseUp); - - addListener(fabric.document, 'mousemove', this._onMouseMove); addListener(fabric.document, 'touchmove', this._onMouseMove); removeListener(this.upperCanvasEl, 'mousemove', this._onMouseMove); removeListener(this.upperCanvasEl, 'touchmove', this._onMouseMove); + + if (e.type === 'touchstart') { + // Unbind mousedown to prevent double triggers from touch devices + removeListener(this.upperCanvasEl, 'mousedown', this._onMouseDown); + } + else { + addListener(fabric.document, 'mouseup', this._onMouseUp); + addListener(fabric.document, 'mousemove', this._onMouseMove); + } }, /** @@ -12151,6 +12383,15 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab addListener(this.upperCanvasEl, 'mousemove', this._onMouseMove); addListener(this.upperCanvasEl, 'touchmove', this._onMouseMove); + + if (e.type === 'touchend') { + // Wait 400ms before rebinding mousedown to prevent double triggers + // from touch devices + var _this = this; + setTimeout(function() { + addListener(_this.upperCanvasEl, 'mousedown', _this._onMouseDown); + }, 400); + } }, /** @@ -12249,8 +12490,8 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab */ _finalizeCurrentTransform: function() { - var transform = this._currentTransform; - var target = transform.target; + var transform = this._currentTransform, + target = transform.target; if (target._scaling) { target._scaling = false; @@ -12392,7 +12633,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab this.stateful && target.saveState(); // determine if it's a drag or rotate case - if ((corner = target._findTargetCorner(e, this._offset))) { + if ((corner = target._findTargetCorner(this.getPointer(e)))) { this.onBeforeScaleRotate(target); } @@ -12514,11 +12755,11 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab * @param {Event} e Event fired on mousemove */ _transformObject: function(e) { - var pointer = fabric.util.transformPoint( - getPointer(e, this.upperCanvasEl), + fabric.util.getPointer(e, this.upperCanvasEl), fabric.util.invertTransform(this.viewportTransform) ), + pointer = this.getPointer(e), transform = this._currentTransform; transform.reset = false, @@ -12566,7 +12807,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab * @private */ _fire: function(eventName, target, e) { - this.fire('object:' + eventName, { target: target, e: e}); + this.fire('object:' + eventName, { target: target, e: e }); target.fire(eventName, { e: e }); }, @@ -12623,11 +12864,11 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab return false; } else { - var activeGroup = this.getActiveGroup(); - // only show proper corner when group selection is not active - var corner = target._findTargetCorner + var activeGroup = this.getActiveGroup(), + // only show proper corner when group selection is not active + corner = target._findTargetCorner && (!activeGroup || !activeGroup.contains(target)) - && target._findTargetCorner(e, this._offset); + && target._findTargetCorner(this.getPointer(e, true)); if (!corner) { style.cursor = target.hoverCursor || this.hoverCursor; @@ -12670,7 +12911,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab // normalize n to be from 0 to 7 n %= 8; - return cursorMap[n]; + return this.cursorMap[n]; } }); })(); @@ -12777,13 +13018,11 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab */ _createGroup: function(target) { - var objects = this.getObjects(); - - var isActiveLower = objects.indexOf(this._activeObject) < objects.indexOf(target); - - var groupObjects = isActiveLower - ? [ this._activeObject, target ] - : [ target, this._activeObject ]; + var objects = this.getObjects(), + isActiveLower = objects.indexOf(this._activeObject) < objects.indexOf(target), + groupObjects = isActiveLower + ? [ this._activeObject, target ] + : [ target, this._activeObject ]; return new fabric.Group(groupObjects, { originX: 'center', @@ -12935,8 +13174,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati this.renderAll(true); - var canvasEl = this.upperCanvasEl || this.lowerCanvasEl; - var croppedCanvasEl = this.__getCroppedCanvas(canvasEl, cropping); + var canvasEl = this.upperCanvasEl || this.lowerCanvasEl, + croppedCanvasEl = this.__getCroppedCanvas(canvasEl, cropping); // to avoid common confusion https://github.com/kangax/fabric.js/issues/806 if (format === 'jpg') { @@ -12963,9 +13202,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati __getCroppedCanvas: function(canvasEl, cropping) { var croppedCanvasEl, - croppedCtx; - - var shouldCrop = 'left' in cropping || + croppedCtx, + shouldCrop = 'left' in cropping || 'top' in cropping || 'width' in cropping || 'height' in cropping; @@ -12998,7 +13236,9 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati ctx = this.contextTop || this.contextContainer; - this.setWidth(scaledWidth).setHeight(scaledHeight); + if (multiplier > 1) { + this.setWidth(scaledWidth).setHeight(scaledHeight); + } ctx.scale(multiplier, multiplier); if (cropping.left) { @@ -13010,9 +13250,15 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati if (cropping.width) { cropping.width *= multiplier; } + else if (multiplier < 1) { + cropping.width = scaledWidth; + } if (cropping.height) { cropping.height *= multiplier; } + else if (multiplier < 1) { + cropping.height = scaledHeight; + } if (activeGroup) { // not removing group due to complications with restoring it with correct state afterwords @@ -13225,8 +13471,9 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati _enlivenObjects: function (objects, callback, reviver) { var _this = this; - if (objects.length === 0) { + if (!objects || objects.length === 0) { callback && callback(); + return; } var renderOnAddRemove = this.renderOnAddRemove; @@ -13324,7 +13571,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati */ __onTransformGesture: function(e, self) { - if (this.isDrawingMode || e.touches.length !== 2 || 'gesture' !== self.gesture) { + if (this.isDrawingMode || !e.touches || e.touches.length !== 2 || 'gesture' !== self.gesture) { return; } @@ -13335,7 +13582,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati this._scaleObjectBy(self.scale); } - this.fire('touch:gesture', {target: target, e: e, self: self}); + this.fire('touch:gesture', { target: target, e: e, self: self }); }, /** @@ -13345,7 +13592,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati * @param self Event proxy object by Event.js */ __onDrag: function(e, self) { - this.fire('touch:drag', {e: e, self: self}); + this.fire('touch:drag', { e: e, self: self }); }, /** @@ -13355,7 +13602,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati * @param self Event proxy object by Event.js */ __onOrientationChange: function(e, self) { - this.fire('touch:orientation', {e: e, self: self}); + this.fire('touch:orientation', { e: e, self: self }); }, /** @@ -13365,7 +13612,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati * @param self Event proxy object by Event.js */ __onShake: function(e, self) { - this.fire('touch:shake', {e: e, self: self}); + this.fire('touch:shake', { e: e, self: self }); }, /** @@ -13376,10 +13623,9 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati */ _scaleObjectBy: function(s, by) { var t = this._currentTransform, - target = t.target; - - var lockScalingX = target.get('lockScalingX'), - lockScalingY = target.get('lockScalingY'); + target = t.target, + lockScalingX = target.get('lockScalingX'), + lockScalingY = target.get('lockScalingY'); if (lockScalingX && lockScalingY) return; @@ -13417,7 +13663,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend, @@ -14028,6 +14274,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati /** * Function that determines clipping of an object (context is passed as a first argument) + * Note that context origin is at the object's center point (not left/top corner) * @type Function */ clipTo: null, @@ -14169,34 +14416,34 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati */ toObject: function(propertiesToInclude) { - var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS; + var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, - var object = { - type: this.type, - originX: this.originX, - originY: this.originY, - left: toFixed(this.left, NUM_FRACTION_DIGITS), - top: toFixed(this.top, NUM_FRACTION_DIGITS), - width: toFixed(this.width, NUM_FRACTION_DIGITS), - height: toFixed(this.height, NUM_FRACTION_DIGITS), - fill: (this.fill && this.fill.toObject) ? this.fill.toObject() : this.fill, - stroke: (this.stroke && this.stroke.toObject) ? this.stroke.toObject() : this.stroke, - strokeWidth: toFixed(this.strokeWidth, NUM_FRACTION_DIGITS), - strokeDashArray: this.strokeDashArray, - strokeLineCap: this.strokeLineCap, - strokeLineJoin: this.strokeLineJoin, - strokeMiterLimit: toFixed(this.strokeMiterLimit, NUM_FRACTION_DIGITS), - scaleX: toFixed(this.scaleX, NUM_FRACTION_DIGITS), - scaleY: toFixed(this.scaleY, NUM_FRACTION_DIGITS), - angle: toFixed(this.getAngle(), NUM_FRACTION_DIGITS), - flipX: this.flipX, - flipY: this.flipY, - opacity: toFixed(this.opacity, NUM_FRACTION_DIGITS), - shadow: (this.shadow && this.shadow.toObject) ? this.shadow.toObject() : this.shadow, - visible: this.visible, - clipTo: this.clipTo && String(this.clipTo), - backgroundColor: this.backgroundColor - }; + object = { + type: this.type, + originX: this.originX, + originY: this.originY, + left: toFixed(this.left, NUM_FRACTION_DIGITS), + top: toFixed(this.top, NUM_FRACTION_DIGITS), + width: toFixed(this.width, NUM_FRACTION_DIGITS), + height: toFixed(this.height, NUM_FRACTION_DIGITS), + fill: (this.fill && this.fill.toObject) ? this.fill.toObject() : this.fill, + stroke: (this.stroke && this.stroke.toObject) ? this.stroke.toObject() : this.stroke, + strokeWidth: toFixed(this.strokeWidth, NUM_FRACTION_DIGITS), + strokeDashArray: this.strokeDashArray, + strokeLineCap: this.strokeLineCap, + strokeLineJoin: this.strokeLineJoin, + strokeMiterLimit: toFixed(this.strokeMiterLimit, NUM_FRACTION_DIGITS), + scaleX: toFixed(this.scaleX, NUM_FRACTION_DIGITS), + scaleY: toFixed(this.scaleY, NUM_FRACTION_DIGITS), + angle: toFixed(this.getAngle(), NUM_FRACTION_DIGITS), + flipX: this.flipX, + flipY: this.flipY, + opacity: toFixed(this.opacity, NUM_FRACTION_DIGITS), + shadow: (this.shadow && this.shadow.toObject) ? this.shadow.toObject() : this.shadow, + visible: this.visible, + clipTo: this.clipTo && String(this.clipTo), + backgroundColor: this.backgroundColor + }; if (!this.includeDefaultValues) { object = this._removeDefaultValues(object); @@ -14222,8 +14469,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati * @param {Object} object */ _removeDefaultValues: function(object) { - var prototype = fabric.util.getKlass(object.type).prototype; - var stateProperties = prototype.stateProperties; + var prototype = fabric.util.getKlass(object.type).prototype, + stateProperties = prototype.stateProperties; stateProperties.forEach(function(prop) { if (object[prop] === prototype[prop]) { @@ -14239,7 +14486,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati * @return {String} */ toString: function() { - return "#"; + return '#'; }, /** @@ -14251,6 +14498,15 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati return this[property]; }, + /** + * @private + */ + _setObject: function(obj) { + for (var prop in obj) { + this._set(prop, obj[prop]); + } + }, + /** * Sets property to a given value. When changing position/dimension -related properties (left, top, scale, angle, etc.) `set` does not update position of object's borders/controls. If you need to update those, call `setCoords()`. * @param {String|Object} key Property name or object (if object, iterate over the object properties) @@ -14260,9 +14516,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati */ set: function(key, value) { if (typeof key === 'object') { - for (var prop in key) { - this._set(prop, key[prop]); - } + this._setObject(key); } else { if (typeof value === 'function' && key !== 'clipTo') { @@ -14332,6 +14586,18 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati return this; }, + /** + * Retrieves viewportTransform from Object's canvas if possible + * @method getViewportTransform + * @memberOf fabric.Object.prototype + * @return {Boolean} flipY value // TODO + */ + getViewportTransform: function() { + if (this.canvas && this.getViewportTransform()) + return this.canvas.viewportTransform; + return [1, 0, 0, 1, 0, 0]; + }, + /** * Renders an object on a specified context * @param {CanvasRenderingContext2D} ctx Context to render on @@ -14343,6 +14609,9 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati ctx.save(); + //setup fill rule for current object + this._setupFillRule(ctx); + this._transform(ctx, noTransform); this._setStrokeStyles(ctx); this._setFillStyles(ctx); @@ -14358,6 +14627,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati this._render(ctx, noTransform); this.clipTo && ctx.restore(); this._removeShadow(ctx); + this._restoreFillRule(ctx); + ctx.restore(); this._renderControls(ctx, noTransform); @@ -14365,7 +14636,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati _transform: function(ctx, noTransform) { var m = this.transformMatrix; - var v = this.canvas.viewportTransform; + var v = this.getViewportTransform(); ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); @@ -14403,7 +14674,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati * @param {Boolean} [noTransform] When true, context is not transformed */ _renderControls: function(ctx, noTransform) { - var v = this.canvas.viewportTransform; + var v = this.getViewportTransform(); ctx.save(); if (this.active && !noTransform) { @@ -14444,6 +14715,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati * @param {CanvasRenderingContext2D} ctx Context to render on */ _removeShadow: function(ctx) { + if (!this.shadow) return; + ctx.shadowColor = ''; ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0; }, @@ -14461,7 +14734,12 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati -this.width / 2 + this.fill.offsetX || 0, -this.height / 2 + this.fill.offsetY || 0); } - ctx.fill(); + if (this.fillRule === 'destination-over') { + ctx.fill('evenodd'); + } + else { + ctx.fill(); + } if (this.fill.toLive) { ctx.restore(); } @@ -14660,7 +14938,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati setGradient: function(property, options) { options || (options = { }); - var gradient = {colorStops: []}; + var gradient = { colorStops: [] }; gradient.type = options.type || (options.r1 || options.r2 ? 'radial' : 'linear'); gradient.coords = { @@ -14677,7 +14955,11 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati for (var position in options.colorStops) { var color = new fabric.Color(options.colorStops[position]); - gradient.colorStops.push({offset: position, color: color.toRgb(), opacity: color.getAlpha()}); + gradient.colorStops.push({ + offset: position, + color: color.toRgb(), + opacity: color.getAlpha() + }); } return this.set(property, fabric.Gradient.forObject(this, gradient)); @@ -14735,7 +15017,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati /** * Sets "color" of an instance (alias of `set('fill', …)`) * @param {String} color Color value - * @return {fabric.Text} thisArg + * @return {fabric.Object} thisArg * @chainable */ setColor: function(color) { @@ -14743,6 +15025,28 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati return this; }, + /** + * Sets "angle" of an instance + * @param {Number} angle Angle value + * @return {fabric.Object} thisArg + * @chainable + */ + setAngle: function(angle) { + var shouldCenterOrigin = (this.originX !== 'center' || this.originY !== 'center') && this.centeredRotation; + + if (shouldCenterOrigin) { + this._setOriginToCenter(); + } + + this.set('angle', angle); + + if (shouldCenterOrigin) { + this._resetOrigin(); + } + + return this; + }, + /** * Centers object horizontally on canvas to which it was added last. * You might need to call `setCoords` on an object after centering, to update controls area. @@ -14782,7 +15086,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati * @chainable */ remove: function() { - return this.canvas.remove(this); + this.canvas.remove(this); + return this; }, /** @@ -14798,6 +15103,28 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati x: pointer.x - objectLeftTop.x, y: pointer.y - objectLeftTop.y }; + }, + + /** + * Sets canvas globalCompositeOperation for specific object + * custom composition operation for the particular object can be specifed using fillRule property + * @param {CanvasRenderingContext2D} ctx Rendering canvas context + */ + _setupFillRule: function (ctx) { + if (this.fillRule) { + this._prevFillRule = ctx.globalCompositeOperation; + ctx.globalCompositeOperation = this.fillRule; + } + }, + + /** + * Restores previously saved canvas globalCompositeOperation after obeject rendering + * @param {CanvasRenderingContext2D} ctx Rendering canvas context + */ + _restoreFillRule: function (ctx) { + if (this.fillRule && this._prevFillRule) { + ctx.globalCompositeOperation = this._prevFillRule; + } } }); @@ -14851,17 +15178,17 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati cy = point.y, strokeWidth = this.stroke ? this.strokeWidth : 0; - if (originX === "left") { + if (originX === 'left') { cx = point.x + (this.getWidth() + strokeWidth * this.scaleX) / 2; } - else if (originX === "right") { + else if (originX === 'right') { cx = point.x - (this.getWidth() + strokeWidth * this.scaleX) / 2; } - if (originY === "top") { + if (originY === 'top') { cy = point.y + (this.getHeight() + strokeWidth * this.scaleY) / 2; } - else if (originY === "bottom") { + else if (originY === 'bottom') { cy = point.y - (this.getHeight() + strokeWidth * this.scaleY) / 2; } @@ -14882,16 +15209,16 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati strokeWidth = this.stroke ? this.strokeWidth : 0; // Get the point coordinates - if (originX === "left") { + if (originX === 'left') { x = center.x - (this.getWidth() + strokeWidth * this.scaleX) / 2; } - else if (originX === "right") { + else if (originX === 'right') { x = center.x + (this.getWidth() + strokeWidth * this.scaleX) / 2; } - if (originY === "top") { + if (originY === 'top') { y = center.y - (this.getHeight() + strokeWidth * this.scaleY) / 2; } - else if (originY === "bottom") { + else if (originY === 'bottom') { y = center.y + (this.getHeight() + strokeWidth * this.scaleY) / 2; } @@ -14941,20 +15268,20 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati x, y; if (originX && originY) { - if (originX === "left") { + if (originX === 'left') { x = center.x - (this.getWidth() + strokeWidth * this.scaleX) / 2; } - else if (originX === "right") { + else if (originX === 'right') { x = center.x + (this.getWidth() + strokeWidth * this.scaleX) / 2; } else { x = center.x; } - if (originY === "top") { + if (originY === 'top') { y = center.y - (this.getHeight() + strokeWidth * this.scaleY) / 2; } - else if (originY === "bottom") { + else if (originY === 'bottom') { y = center.y + (this.getHeight() + strokeWidth * this.scaleY) / 2; } else { @@ -14987,8 +15314,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati * @return {void} */ setPositionByOrigin: function(pos, originX, originY) { - var center = this.translateToCenterPoint(pos, originX, originY); - var position = this.translateToOriginPoint(center, this.originX, this.originY); + var center = this.translateToCenterPoint(pos, originX, originY), + position = this.translateToOriginPoint(center, this.originX, this.originY); this.set('left', position.x); this.set('top', position.y); @@ -14998,13 +15325,13 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati * @param {String} to One of 'left', 'center', 'right' */ adjustPosition: function(to) { - var angle = degreesToRadians(this.angle); - var hypotHalf = this.getWidth() / 2; - var xHalf = Math.cos(angle) * hypotHalf; - var yHalf = Math.sin(angle) * hypotHalf; - var hypotFull = this.getWidth(); - var xFull = Math.cos(angle) * hypotFull; - var yFull = Math.sin(angle) * hypotFull; + var angle = degreesToRadians(this.angle), + hypotHalf = this.getWidth() / 2, + xHalf = Math.cos(angle) * hypotHalf, + yHalf = Math.sin(angle) * hypotHalf, + hypotFull = this.getWidth(), + xFull = Math.cos(angle) * hypotFull, + yFull = Math.sin(angle) * hypotFull; if (this.originX === 'center' && to === 'left' || this.originX === 'right' && to === 'center') { @@ -15033,6 +15360,45 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati this.originX = to; }, + /** + * @private + * Sets the origin/position of the object to it's center point + * @return {void} + */ + _setOriginToCenter: function() { + this._originalOriginX = this.originX; + this._originalOriginY = this.originY; + + var center = this.getCenterPoint(); + + this.originX = 'center'; + this.originY = 'center'; + + this.left = center.x; + this.top = center.y; + }, + + /** + * @private + * Resets the origin/position of the object to it's original origin + * @return {void} + */ + _resetOrigin: function() { + var originPoint = this.translateToOriginPoint( + this.getCenterPoint(), + this._originalOriginX, + this._originalOriginY); + + this.originX = this._originalOriginX; + this.originY = this._originalOriginY; + + this.left = originPoint.x; + this.top = originPoint.y; + + this._originalOriginX = null; + this._originalOriginY = null; + }, + /** * @private */ @@ -15068,13 +15434,12 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati tl = new fabric.Point(oCoords.tl.x, oCoords.tl.y), tr = new fabric.Point(oCoords.tr.x, oCoords.tr.y), bl = new fabric.Point(oCoords.bl.x, oCoords.bl.y), - br = new fabric.Point(oCoords.br.x, oCoords.br.y); - - var intersection = fabric.Intersection.intersectPolygonRectangle( - [tl, tr, br, bl], - pointTL, - pointBR - ); + br = new fabric.Point(oCoords.br.x, oCoords.br.y), + intersection = fabric.Intersection.intersectPolygonRectangle( + [tl, tr, br, bl], + pointTL, + pointBR + ); return intersection.status === 'Intersection'; }, @@ -15094,12 +15459,11 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati }; } var thisCoords = getCoords(this.oCoords), - otherCoords = getCoords(other.oCoords); - - var intersection = fabric.Intersection.intersectPolygonPolygon( - [thisCoords.tl, thisCoords.tr, thisCoords.br, thisCoords.bl], - [otherCoords.tl, otherCoords.tr, otherCoords.br, otherCoords.bl] - ); + otherCoords = getCoords(other.oCoords), + intersection = fabric.Intersection.intersectPolygonPolygon( + [thisCoords.tl, thisCoords.tr, thisCoords.br, thisCoords.bl], + [otherCoords.tl, otherCoords.tr, otherCoords.br, otherCoords.bl] + ); return intersection.status === 'Intersection'; }, @@ -15127,10 +15491,10 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati var boundingRect = this.getBoundingRect(); return ( - boundingRect.left > pointTL.x && - boundingRect.left + boundingRect.width < pointBR.x && - boundingRect.top > pointTL.y && - boundingRect.top + boundingRect.height < pointBR.y + boundingRect.left >= pointTL.x && + boundingRect.left + boundingRect.width <= pointBR.x && + boundingRect.top >= pointTL.y && + boundingRect.top + boundingRect.height <= pointBR.y ); }, @@ -15204,7 +15568,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati else { b1 = 0; b2 = (iLine.d.y - iLine.o.y) / (iLine.d.x - iLine.o.x); - a1 = point.y- b1 * point.x; + a1 = point.y - b1 * point.x; a2 = iLine.o.y - b2 * iLine.o.x; xi = - (a1 - a2) / (b1 - b2); @@ -15247,15 +15611,15 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati getBoundingRect: function() { this.oCoords || this.setCoords(); - var xCoords = [this.oCoords.tl.x, this.oCoords.tr.x, this.oCoords.br.x, this.oCoords.bl.x]; - var minX = fabric.util.array.min(xCoords); - var maxX = fabric.util.array.max(xCoords); - var width = Math.abs(minX - maxX); + var xCoords = [this.oCoords.tl.x, this.oCoords.tr.x, this.oCoords.br.x, this.oCoords.bl.x], + minX = fabric.util.array.min(xCoords), + maxX = fabric.util.array.max(xCoords), + width = Math.abs(minX - maxX), - var yCoords = [this.oCoords.tl.y, this.oCoords.tr.y, this.oCoords.br.y, this.oCoords.bl.y]; - var minY = fabric.util.array.min(yCoords); - var maxY = fabric.util.array.max(yCoords); - var height = Math.abs(minY - maxY); + yCoords = [this.oCoords.tl.y, this.oCoords.tr.y, this.oCoords.br.y, this.oCoords.bl.y], + minY = fabric.util.array.min(yCoords), + maxY = fabric.util.array.max(yCoords), + height = Math.abs(minY - maxY); return { left: minX, @@ -15289,12 +15653,13 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati */ _constrainScale: function(value) { if (Math.abs(value) < this.minScaleLimit) { - if (value < 0) + if (value < 0) { return -this.minScaleLimit; - else + } + else { return this.minScaleLimit; + } } - return value; }, @@ -15355,7 +15720,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati theta = degreesToRadians(this.angle), vpt; // TODO: ideally we should never setCoords an object which lacks a canvas - vpt = this.canvas ? this.canvas.viewportTransform : [1, 0, 0, 1, 0, 0]; + vpt = this.getViewportTransform(); var f = function (p) { return fabric.util.transformPoint(p, vpt); @@ -15370,13 +15735,13 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati } var _hypotenuse = Math.sqrt( - Math.pow(this.currentWidth / 2, 2) + - Math.pow(this.currentHeight / 2, 2)); + Math.pow(this.currentWidth / 2, 2) + + Math.pow(this.currentHeight / 2, 2)), - var _angle = Math.atan(isFinite(this.currentHeight / this.currentWidth) ? this.currentHeight / this.currentWidth : 0); + _angle = Math.atan(isFinite(this.currentHeight / this.currentWidth) ? this.currentHeight / this.currentWidth : 0), - // offset added for rotate and scale actions - var offsetX = Math.cos(_angle + theta) * _hypotenuse, + // offset added for rotate and scale actions + offsetX = Math.cos(_angle + theta) * _hypotenuse, offsetY = Math.sin(_angle + theta) * _hypotenuse, sinTh = Math.sin(theta), cosTh = Math.cos(theta), @@ -15533,32 +15898,32 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot getSvgStyles: function() { var fill = this.fill - ? (this.fill.toLive ? 'url(#SVGID_' + this.fill.id + ')' : this.fill) - : 'none'; + ? (this.fill.toLive ? 'url(#SVGID_' + this.fill.id + ')' : this.fill) + : 'none', - var stroke = this.stroke - ? (this.stroke.toLive ? 'url(#SVGID_' + this.stroke.id + ')' : this.stroke) - : 'none'; + stroke = this.stroke + ? (this.stroke.toLive ? 'url(#SVGID_' + this.stroke.id + ')' : this.stroke) + : 'none', - var strokeWidth = this.strokeWidth ? this.strokeWidth : '0'; - var strokeDashArray = this.strokeDashArray ? this.strokeDashArray.join(' ') : ''; - var strokeLineCap = this.strokeLineCap ? this.strokeLineCap : 'butt'; - var strokeLineJoin = this.strokeLineJoin ? this.strokeLineJoin : 'miter'; - var strokeMiterLimit = this.strokeMiterLimit ? this.strokeMiterLimit : '4'; - var opacity = typeof this.opacity !== 'undefined' ? this.opacity : '1'; + strokeWidth = this.strokeWidth ? this.strokeWidth : '0', + strokeDashArray = this.strokeDashArray ? this.strokeDashArray.join(' ') : '', + strokeLineCap = this.strokeLineCap ? this.strokeLineCap : 'butt', + strokeLineJoin = this.strokeLineJoin ? this.strokeLineJoin : 'miter', + strokeMiterLimit = this.strokeMiterLimit ? this.strokeMiterLimit : '4', + opacity = typeof this.opacity !== 'undefined' ? this.opacity : '1', - var visibility = this.visible ? '' : " visibility: hidden;"; - var filter = this.shadow && this.type !== 'text' ? 'filter: url(#SVGID_' + this.shadow.id + ');' : ''; + visibility = this.visible ? '' : ' visibility: hidden;', + filter = this.shadow && this.type !== 'text' ? 'filter: url(#SVGID_' + this.shadow.id + ');' : ''; return [ - "stroke: ", stroke, "; ", - "stroke-width: ", strokeWidth, "; ", - "stroke-dasharray: ", strokeDashArray, "; ", - "stroke-linecap: ", strokeLineCap, "; ", - "stroke-linejoin: ", strokeLineJoin, "; ", - "stroke-miterlimit: ", strokeMiterLimit, "; ", - "fill: ", fill, "; ", - "opacity: ", opacity, ";", + 'stroke: ', stroke, '; ', + 'stroke-width: ', strokeWidth, '; ', + 'stroke-dasharray: ', strokeDashArray, '; ', + 'stroke-linecap: ', strokeLineCap, '; ', + 'stroke-linejoin: ', strokeLineJoin, '; ', + 'stroke-miterlimit: ', strokeMiterLimit, '; ', + 'fill: ', fill, '; ', + 'opacity: ', opacity, ';', filter, visibility ].join(''); @@ -15569,34 +15934,37 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot * @return {String} */ getSvgTransform: function() { - var toFixed = fabric.util.toFixed; - var angle = this.getAngle(); - var center = this.getCenterPoint(); + var toFixed = fabric.util.toFixed, + angle = this.getAngle(), + center = this.getCenterPoint(), - var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS; + NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, - var translatePart = "translate(" + + translatePart = 'translate(' + toFixed(center.x, NUM_FRACTION_DIGITS) + - " " + + ' ' + toFixed(center.y, NUM_FRACTION_DIGITS) + - ")"; + ')', - var anglePart = angle !== 0 - ? (" rotate(" + toFixed(angle, NUM_FRACTION_DIGITS) + ")") - : ''; + anglePart = angle !== 0 + ? (' rotate(' + toFixed(angle, NUM_FRACTION_DIGITS) + ')') + : '', - var scalePart = (this.scaleX === 1 && this.scaleY === 1) - ? '' : - (" scale(" + - toFixed(this.scaleX, NUM_FRACTION_DIGITS) + - " " + - toFixed(this.scaleY, NUM_FRACTION_DIGITS) + - ")"); + scalePart = (this.scaleX === 1 && this.scaleY === 1) + ? '' : + (' scale(' + + toFixed(this.scaleX, NUM_FRACTION_DIGITS) + + ' ' + + toFixed(this.scaleY, NUM_FRACTION_DIGITS) + + ')'), - var flipXPart = this.flipX ? "matrix(-1 0 0 1 0 0) " : ""; - var flipYPart = this.flipY ? "matrix(1 0 0 -1 0 0)" : ""; + flipXPart = this.flipX ? 'matrix(-1 0 0 1 0 0) ' : '', - return [ translatePart, anglePart, scalePart, flipXPart, flipYPart ].join(''); + flipYPart = this.flipY ? 'matrix(1 0 0 -1 0 0)' : ''; + + return [ + translatePart, anglePart, scalePart, flipXPart, flipYPart + ].join(''); }, /** @@ -15669,8 +16037,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot (function(){ - var getPointer = fabric.util.getPointer, - degreesToRadians = fabric.util.degreesToRadians, + var degreesToRadians = fabric.util.degreesToRadians, isVML = typeof G_vmlCanvasManager !== 'undefined'; fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { @@ -15684,15 +16051,13 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot /** * Determines which corner has been clicked * @private - * @param {Event} e Event object - * @param {Object} offset Canvas offset + * @param {Object} pointer The pointer indicating the mouse position * @return {String|Boolean} corner code (tl, tr, bl, br, etc.), or false if nothing is found */ - _findTargetCorner: function(e, offset) { + _findTargetCorner: function(pointer) { if (!this.hasControls || !this.active) return false; - var pointer = this.canvas.getPointer(e, true), - ex = pointer.x, + var ex = pointer.x, ey = pointer.y, xPoints, lines; @@ -15707,7 +16072,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot continue; } - if (this.get('lockUniScaling') && (i === 'mt' || i === 'mr' || i === 'mb' || i === 'ml')) { + if (this.get('lockUniScaling') && + (i === 'mt' || i === 'mr' || i === 'mb' || i === 'ml')) { continue; } @@ -15727,7 +16093,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot // canvas.contextTop.fillRect(lines.rightline.d.x, lines.rightline.d.y, 2, 2); // canvas.contextTop.fillRect(lines.rightline.o.x, lines.rightline.o.y, 2, 2); - xPoints = this._findCrossPoints({x: ex, y: ey}, lines); + xPoints = this._findCrossPoints({ x: ex, y: ey }, lines); if (xPoints !== 0 && xPoints % 2 === 1) { this.__corner = i; return i; @@ -15947,7 +16313,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot ctx.lineWidth = 1 / this.borderScaleFactor; - var vpt = this.canvas.viewportTransform, + var vpt = this.getViewportTransform(), wh = fabric.util.transformPoint(new fabric.Point(this.getWidth(), this.getHeight()), vpt, true), sxy = fabric.util.transformPoint(new fabric.Point(scaleX, scaleY), vpt, true), w = wh.x, @@ -15999,7 +16365,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot var size = this.cornerSize, size2 = size / 2, strokeWidth2 = ~~(this.strokeWidth / 2), // half strokeWidth rounded down - wh = fabric.util.transformPoint(new fabric.Point(this.getWidth(), this.getHeight()), this.canvas.viewportTransform, true), + wh = fabric.util.transformPoint(new fabric.Point(this.getWidth(), this.getHeight()), this.getViewportTransform(), true), width = wh.x, height = wh.y, left = -(width / 2), @@ -16029,7 +16395,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot top - scaleOffset - strokeWidth2 - padding); // bottom-left - this._drawControl('tr', ctx, methodName, + this._drawControl('bl', ctx, methodName, left - scaleOffset - strokeWidth2 - padding, top + height + scaleOffsetSize + strokeWidth2 + padding); @@ -16051,7 +16417,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot top + height + scaleOffsetSize + strokeWidth2 + padding); // middle-right - this._drawControl('mb', ctx, methodName, + this._drawControl('mr', ctx, methodName, left + width + scaleOffsetSize + strokeWidth2 + padding, top + height/2 - scaleOffset); @@ -16140,15 +16506,15 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot _getControlsVisibility: function() { if (!this._controlsVisibility) { this._controlsVisibility = { - tl: true, - tr: true, - br: true, - bl: true, - ml: true, - mt: true, - mr: true, - mb: true, - mtr: true + tl: true, + tr: true, + br: true, + bl: true, + ml: true, + mt: true, + mr: true, + mb: true, + mtr: true }; } return this._controlsVisibility; @@ -16301,7 +16667,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot for (prop in arguments[0]) { propsToAnimate.push(prop); } - for (var i = 0, len = propsToAnimate.length; i' - ); + '"/>'); return reviver ? reviver(markup.join('')) : markup.join(''); }, @@ -17400,7 +17886,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), toFixed = fabric.util.toFixed; @@ -17425,6 +17911,13 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot */ type: 'polyline', + /** + * Points array + * @type Array + * @default + */ + points: null, + /** * Constructor * @param {Array} points Array of points (where each point is an object with x and y) @@ -17522,7 +18015,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot ctx.beginPath(); for (var i = 0, len = this.points.length; i < len; i++) { p1 = this.points[i]; - p2 = this.points[i+1] || p1; + p2 = this.points[i + 1] || p1; fabric.util.drawDashedLine(ctx, p1.x, p1.y, p2.x, p2.y, this.strokeDashArray); } }, @@ -17585,7 +18078,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend, @@ -17613,6 +18106,13 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot */ type: 'polygon', + /** + * Points array + * @type Array + * @default + */ + points: null, + /** * Constructor * @param {Array} points Array of points @@ -17723,7 +18223,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot ctx.beginPath(); for (var i = 0, len = this.points.length; i < len; i++) { p1 = this.points[i]; - p2 = this.points[i+1] || this.points[0]; + p2 = this.points[i + 1] || this.points[0]; fabric.util.drawDashedLine(ctx, p1.x, p1.y, p2.x, p2.y, this.strokeDashArray); } ctx.closePath(); @@ -17786,26 +18286,25 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot (function(global) { - var commandLengths = { - m: 2, - l: 2, - h: 1, - v: 1, - c: 6, - s: 4, - q: 4, - t: 2, - a: 7 - }; - - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), min = fabric.util.array.min, max = fabric.util.array.max, extend = fabric.util.object.extend, _toString = Object.prototype.toString, - drawArc = fabric.util.drawArc; + drawArc = fabric.util.drawArc, + commandLengths = { + m: 2, + l: 2, + h: 1, + v: 1, + c: 6, + s: 4, + q: 4, + t: 2, + a: 7 + }; if (fabric.Path) { fabric.warn('fabric.Path is already defined'); @@ -17848,6 +18347,13 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot */ type: 'path', + /** + * Array of path points + * @type Array + * @default + */ + path: null, + /** * Constructor * @param {Array|String} path Path data (sequence of coordinates and corresponding "command" tokens) @@ -17911,7 +18417,9 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot this.left = this.width / 2; } } - this.pathOffset = this.pathOffset || this._calculatePathOffset(origLeft, origTop); //Save top-left coords as offset + this.pathOffset = this.pathOffset || + // Save top-left coords as offset + this._calculatePathOffset(origLeft, origTop); }, /** @@ -18067,8 +18575,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot tempX = current[3]; tempY = current[4]; // calculate reflection of previous control points - controlX = 2*x - controlX; - controlY = 2*y - controlY; + controlX = 2 * x - controlX; + controlY = 2 * y - controlY; ctx.bezierCurveTo( controlX + l, controlY + t, @@ -18129,7 +18637,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot tempX = x + current[1]; tempY = y + current[2]; - if (previous[0].match(/[QqTt]/) === null) { // If there is no previous command or if the previous command was not a Q, q, T or t, // assume the control point is coincident with the current point @@ -18230,7 +18737,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot ctx.save(); var m = this.transformMatrix; - var v = this.canvas.viewportTransform; + var v = this.getViewportTransform(); ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); if (m) { @@ -18271,7 +18778,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot */ toObject: function(propertiesToInclude) { var o = extend(this.callSuper('toObject', propertiesToInclude), { - path: this.path, + path: this.path.map(function(item) { return item.slice() }), pathOffset: this.pathOffset }); if (this.sourcePath) { @@ -18343,7 +18850,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot coords = [ ], currentPath, parsed, - re = /(-?\.\d+)|(-?\d+(\.\d+)?)/g, + re = /([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/ig, match, coordsStr; @@ -18399,14 +18906,14 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot maxX = max(aX), maxY = max(aY), deltaX = maxX - minX, - deltaY = maxY - minY; + deltaY = maxY - minY, - var o = { - left: this.left + (minX + deltaX / 2), - top: this.top + (minY + deltaY / 2), - width: deltaX, - height: deltaY - }; + o = { + left: this.left + (minX + deltaX / 2), + top: this.top + (minY + deltaY / 2), + width: deltaX, + height: deltaY + }; return o; }, @@ -18427,13 +18934,18 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot isLowerCase = true; } - var xy = this._getXY(item, isLowerCase, previous); + var xy = this._getXY(item, isLowerCase, previous), + val; - var val = parseInt(xy.x, 10); - if (!isNaN(val)) aX.push(val); + val = parseInt(xy.x, 10); + if (!isNaN(val)) { + aX.push(val); + } val = parseInt(xy.y, 10); - if (!isNaN(val)) aY.push(val); + if (!isNaN(val)) { + aY.push(val); + } }, _getXY: function(item, isLowerCase, previous) { @@ -18445,13 +18957,13 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot ? previous.x + getX(item) : item[0] === 'V' ? previous.x - : getX(item); + : getX(item), - var y = isLowerCase - ? previous.y + getY(item) - : item[0] === 'H' - ? previous.y - : getY(item); + y = isLowerCase + ? previous.y + getY(item) + : item[0] === 'H' + ? previous.y + : getY(item); return { x: x, y: y }; } @@ -18467,9 +18979,9 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot fabric.Path.fromObject = function(object, callback) { if (typeof object.path === 'string') { fabric.loadSVGFromURL(object.path, function (elements) { - var path = elements[0]; + var path = elements[0], + pathUrl = object.path; - var pathUrl = object.path; delete object.path; fabric.util.object.extend(path, object); @@ -18520,7 +19032,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend, @@ -18572,6 +19084,15 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot this.setOptions(options); + if (options.widthAttr) { + this.scaleX = options.widthAttr / options.width; + } + if (options.heightAttr) { + this.scaleY = options.heightAttr / options.height; + } + + this.setCoords(); + if (options.sourcePath) { this.setSourcePath(options.sourcePath); } @@ -18589,7 +19110,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot var m = this.transformMatrix; - var v = this.canvas.viewportTransform; + var v = this.getViewportTransform(); ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); if (m) { @@ -18663,13 +19184,13 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot * @return {String} svg representation of an instance */ toSVG: function(reviver) { - var objects = this.getObjects(); - var markup = [ - '' - ]; + var objects = this.getObjects(), + markup = [ + '' + ]; for (var i = 0, len = objects.length; i < len; i++) { markup.push(objects[i].toSVG(reviver)); @@ -18694,9 +19215,9 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot * @return {Boolean} true if all paths are of the same color (`fill`) */ isSameColor: function() { - var firstPathFill = this.getObjects()[0].get('fill'); + var firstPathFill = (this.getObjects()[0].get('fill') || '').toLowerCase(); return this.getObjects().every(function(path) { - return path.get('fill') === firstPathFill; + return (path.get('fill') || '').toLowerCase() === firstPathFill; }); }, @@ -18760,7 +19281,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot (function(global){ - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend, @@ -18992,7 +19513,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot * @private */ _renderObject: function(object, ctx) { - var v = this.canvas.viewportTransform, + var v = this.getViewportTransform(), sxy = fabric.util.transformPoint( new fabric.Point(this.scaleX, this.scaleY), v, @@ -19169,11 +19690,10 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot */ _setOpacityIfSame: function() { var objects = this.getObjects(), - firstValue = objects[0] ? objects[0].get('opacity') : 1; - - var isSameOpacity = objects.every(function(o) { - return o.get('opacity') === firstValue; - }); + firstValue = objects[0] ? objects[0].get('opacity') : 1, + isSameOpacity = objects.every(function(o) { + return o.get('opacity') === firstValue; + }); if (isSameOpacity) { this.opacity = firstValue; @@ -19183,7 +19703,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot /** * @private */ - _calcBounds: function() { + _calcBounds: function(onlyWidthHeight) { var aX = [], aY = [], o; @@ -19197,26 +19717,29 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot } } - this.set(this._getBounds(aX, aY)); + this.set(this._getBounds(aX, aY, onlyWidthHeight)); }, /** * @private */ - _getBounds: function(aX, aY) { + _getBounds: function(aX, aY, onlyWidthHeight) { var ivt; if (this.canvas) { - ivt = fabric.util.invertTransform(this.canvas.viewportTransform); + ivt = fabric.util.invertTransform(this.getViewportTransform()); } var minXY = fabric.util.transformPoint(new fabric.Point(min(aX), min(aY)), ivt), - maxXY = fabric.util.transformPoint(new fabric.Point(max(aX), max(aY)), ivt); + maxXY = fabric.util.transformPoint(new fabric.Point(max(aX), max(aY)), ivt), + obj = { + width: (maxXY.x - minXY.x) || 0, + height: (maxXY.y - minXY.y) || 0 + }; - return { - width: (maxXY.x - minXY.x) || 0, - height: (maxXY.y - minXY.y) || 0, - left: (minXY.x + maxXY.x) / 2 || 0, - top: (minXY.y + maxXY.y) / 2 || 0, - }; + if (!onlyWidthHeight) { + obj.left = (minXY.x + maxXY.x) / 2 || 0; + obj.top = (minXY.y + maxXY.y) / 2 || 0; + } + return obj; }, /* _TO_SVG_START_ */ @@ -19299,7 +19822,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot (function(global) { - "use strict"; + 'use strict'; var extend = fabric.util.object.extend; @@ -19421,17 +19944,15 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot if (!this.visible) return; ctx.save(); - var m = this.transformMatrix; - var v; - v = this.canvas.viewportTransform; - - var isInPathGroup = this.group && this.group.type === 'path-group'; + var m = this.transformMatrix, + v = this.getViewportTransform(), + isInPathGroup = this.group && this.group.type === 'path-group'; ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); // this._resetWidthHeight(); if (isInPathGroup) { - ctx.translate(-this.group.width/2 + this.width/2, -this.group.height/2 + this.height/2); + ctx.translate(-this.group.width/2, -this.group.height/2); } if (m) { ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); @@ -19439,6 +19960,9 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot if (!noTransform) { this.transform(ctx); } + if (isInPathGroup) { + ctx.translate(this.width/2, this.height/2); + } ctx.save(); this._setShadow(ctx); @@ -19482,10 +20006,10 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot this._setStrokeStyles(ctx); ctx.beginPath(); - fabric.util.drawDashedLine(ctx, x, y, x+w, y, this.strokeDashArray); - fabric.util.drawDashedLine(ctx, x+w, y, x+w, y+h, this.strokeDashArray); - fabric.util.drawDashedLine(ctx, x+w, y+h, x, y+h, this.strokeDashArray); - fabric.util.drawDashedLine(ctx, x, y+h, x, y, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, x, y, x + w, y, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, x + w, y, x + w, y + h, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, x + w, y + h, x, y + h, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, x, y + h, x, y, this.strokeDashArray); ctx.closePath(); ctx.restore(); }, @@ -19552,7 +20076,9 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot * @return {String} Source of an image */ getSrc: function() { - return this.getElement().src || this.getElement()._src; + if (this.getElement()) { + return this.getElement().src || this.getElement()._src; + } }, /** @@ -19581,6 +20107,10 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot */ applyFilters: function(callback) { + if (!this._originalElement) { + return; + } + if (this.filters.length === 0) { this._element = this._originalElement; callback && callback(); @@ -19630,13 +20160,13 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot * @param {CanvasRenderingContext2D} ctx Context to render on */ _render: function(ctx) { - ctx.drawImage( - this._element, - -this.width / 2, - -this.height / 2, - this.width, - this.height - ); + this._element && ctx.drawImage( + this._element, + -this.width / 2, + -this.height / 2, + this.width, + this.height + ); }, /** @@ -19668,7 +20198,9 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot options || (options = { }); this.setOptions(options); this._setWidthHeight(options); - this._element.crossOrigin = this.crossOrigin; + if (this._element && this.crossOrigin) { + this._element.crossOrigin = this.crossOrigin; + } }, /** @@ -19694,11 +20226,15 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot _setWidthHeight: function(options) { this.width = 'width' in options ? options.width - : (this.getElement().width || 0); + : (this.getElement() + ? this.getElement().width || 0 + : 0); this.height = 'height' in options ? options.height - : (this.getElement().height || 0); + : (this.getElement() + ? this.getElement().height || 0 + : 0); }, /** @@ -19716,7 +20252,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot * @type String * @default */ - fabric.Image.CSS_CANVAS = "canvas-img"; + fabric.Image.CSS_CANVAS = 'canvas-img'; /** * Alias for getSrc @@ -19805,9 +20341,9 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot _getAngleValueForStraighten: function() { var angle = this.getAngle() % 360; if (angle > 0) { - return Math.round((angle-1)/90) * 90; + return Math.round((angle - 1) / 90) * 90; } - return Math.round(angle/90) * 90; + return Math.round(angle / 90) * 90; }, /** @@ -19894,7 +20430,6 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati */ fabric.Image.filters = fabric.Image.filters || { }; - /** * Root filter class from which all filter classes inherit from * @class fabric.Image.filters.BaseFilter @@ -19930,7 +20465,7 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend; @@ -19962,11 +20497,11 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag * Constructor * @memberOf fabric.Image.filters.Brightness.prototype * @param {Object} [options] Options object - * @param {Number} [options.brightness=100] Value to brighten the image up (0..255) + * @param {Number} [options.brightness=0] Value to brighten the image up (0..255) */ initialize: function(options) { options = options || { }; - this.brightness = options.brightness || 100; + this.brightness = options.brightness || 0; }, /** @@ -20014,7 +20549,7 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend; @@ -20080,9 +20615,11 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag options = options || { }; this.opaque = options.opaque; - this.matrix = options.matrix || [ 0, 0, 0, + this.matrix = options.matrix || [ + 0, 0, 0, 0, 1, 0, - 0, 0, 0 ]; + 0, 0, 0 + ]; var canvasEl = fabric.util.createCanvasElement(); this.tmpCtx = canvasEl.getContext('2d'); @@ -20109,49 +20646,49 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag halfSide = Math.floor(side/2), src = pixels.data, sw = pixels.width, - sh = pixels.height; + sh = pixels.height, - // pad output by the convolution matrix - var w = sw; - var h = sh; - var output = this._createImageData(w, h); + // pad output by the convolution matrix + w = sw, + h = sh, + output = this._createImageData(w, h), - var dst = output.data; + dst = output.data, - // go through the destination image pixels - var alphaFac = this.opaque ? 1 : 0; + // go through the destination image pixels + alphaFac = this.opaque ? 1 : 0; - for (var y=0; y sh || scx < 0 || scx > sw) continue; - var srcOff = (scy*sw+scx)*4; - var wt = weights[cy*side+cx]; + var srcOff = (scy * sw + scx) * 4, + wt = weights[cy * side + cx]; r += src[srcOff] * wt; - g += src[srcOff+1] * wt; - b += src[srcOff+2] * wt; - a += src[srcOff+3] * wt; + g += src[srcOff + 1] * wt; + b += src[srcOff + 2] * wt; + a += src[srcOff + 3] * wt; } } dst[dstOff] = r; - dst[dstOff+1] = g; - dst[dstOff+2] = b; - dst[dstOff+3] = a + alphaFac*(255-a); + dst[dstOff + 1] = g; + dst[dstOff + 2] = b; + dst[dstOff + 3] = a + alphaFac * (255 - a); } } @@ -20177,7 +20714,7 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag * @return {fabric.Image.filters.Convolute} Instance of fabric.Image.filters.Convolute */ fabric.Image.filters.Convolute.fromObject = function(object) { - return new fabric.Image.filters.Convolute(object); + return new fabric.Image.filters.Convolute(object); }; })(typeof exports !== 'undefined' ? exports : this); @@ -20185,7 +20722,7 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend; @@ -20268,7 +20805,7 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }); @@ -20331,7 +20868,7 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }); @@ -20390,7 +20927,7 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend; @@ -20495,7 +21032,7 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend; @@ -20527,11 +21064,11 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag * Constructor * @memberOf fabric.Image.filters.Noise.prototype * @param {Object} [options] Options object - * @param {Number} [options.noise=100] Noise value + * @param {Number} [options.noise=0] Noise value */ initialize: function(options) { options = options || { }; - this.noise = options.noise || 100; + this.noise = options.noise || 0; }, /** @@ -20582,7 +21119,7 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend; @@ -20639,9 +21176,9 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag index = (i * 4) * jLen + (j * 4); r = data[index]; - g = data[index+1]; - b = data[index+2]; - a = data[index+3]; + g = data[index + 1]; + b = data[index + 2]; + a = data[index + 3]; /* blocksize: 4 @@ -20694,7 +21231,7 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend; @@ -20752,17 +21289,17 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag for (var i = 0, len = data.length; i < len; i += 4) { r = data[i]; - g = data[i+1]; - b = data[i+2]; + g = data[i + 1]; + b = data[i + 2]; if (r > limit && g > limit && b > limit && - abs(r-g) < distance && - abs(r-b) < distance && - abs(g-b) < distance + abs(r - g) < distance && + abs(r - b) < distance && + abs(g - b) < distance ) { - data[i+3] = 1; + data[i + 3] = 1; } } @@ -20796,7 +21333,7 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }); @@ -20856,7 +21393,7 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }); @@ -20919,7 +21456,7 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend; @@ -21033,7 +21570,101 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag (function(global) { - "use strict"; + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend; + + /** + * Multiply filter class + * Adapted from http://www.laurenscorijn.com/articles/colormath-basics + * @class fabric.Image.filters.Multiply + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @example Multiply filter with hex color + * var filter = new fabric.Image.filters.Multiply({ + * color: '#F0F' + * }); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + * @example Multiply filter with rgb color + * var filter = new fabric.Image.filters.Multiply({ + * color: 'rgb(53, 21, 176)' + * }); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + */ + fabric.Image.filters.Multiply = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Multiply.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Multiply', + + /** + * Constructor + * @memberOf fabric.Image.filters.Multiply.prototype + * @param {Object} [options] Options object + * @param {String} [options.color=#000000] Color to multiply the image pixels with + */ + initialize: function(options) { + options = options || { }; + + this.color = options.color || '#000000'; + }, + + /** + * Applies filter to canvas element + * @param {Object} canvasEl Canvas element to apply filter to + */ + applyTo: function(canvasEl) { + var context = canvasEl.getContext('2d'), + imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), + data = imageData.data, + iLen = data.length, i, + source; + + source = new fabric.Color(this.color).getSource(); + + for (i = 0; i < iLen; i+=4) { + data[i] *= source[0]/255; + data[i + 1] *= source[1]/255; + data[i + 2] *= source[2]/255; + + } + + context.putImageData(imageData, 0, 0); + }, + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + return extend(this.callSuper('toObject'), { + color: this.color + }); + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @return {fabric.Image.filters.Multiply} Instance of fabric.Image.filters.Multiply + */ + fabric.Image.filters.Multiply.fromObject = function(object) { + return new fabric.Image.filters.Multiply(object); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend, @@ -21460,8 +22091,8 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag for (var i = 0, len = textLines.length; i < len; i++) { - var lineWidth = this._getLineWidth(ctx, textLines[i]); - var lineLeftOffset = this._getLineLeftOffset(lineWidth); + var lineWidth = this._getLineWidth(ctx, textLines[i]), + lineLeftOffset = this._getLineLeftOffset(lineWidth); this._boundaries.push({ height: this.fontSize * this.lineHeight, @@ -21544,18 +22175,18 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag return; } - var lineWidth = ctx.measureText(line).width; - var totalWidth = this.width; + var lineWidth = ctx.measureText(line).width, + totalWidth = this.width; if (totalWidth > lineWidth) { // stretch the line - var words = line.split(/\s+/); - var wordsWidth = ctx.measureText(line.replace(/\s+/g, '')).width; - var widthDiff = totalWidth - wordsWidth; - var numSpaces = words.length - 1; - var spaceWidth = widthDiff / numSpaces; + var words = line.split(/\s+/), + wordsWidth = ctx.measureText(line.replace(/\s+/g, '')).width, + widthDiff = totalWidth - wordsWidth, + numSpaces = words.length - 1, + spaceWidth = widthDiff / numSpaces, + leftOffset = 0; - var leftOffset = 0; for (var i = 0, len = words.length; i < len; i++) { this._renderChars(method, ctx, words[i], left + leftOffset, top, lineIndex); leftOffset += ctx.measureText(words[i]).width + spaceWidth; @@ -21697,8 +22328,8 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag if (textLines[i] !== '') { - var lineWidth = this._getLineWidth(ctx, textLines[i]); - var lineLeftOffset = this._getLineLeftOffset(lineWidth); + var lineWidth = this._getLineWidth(ctx, textLines[i]), + lineLeftOffset = this._getLineLeftOffset(lineWidth); ctx.fillRect( this._getLeftOffset() + lineLeftOffset, @@ -21747,15 +22378,15 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag if (!this.textDecoration) return; // var halfOfVerticalBox = this.originY === 'top' ? 0 : this._getTextHeight(ctx, textLines) / 2; - var halfOfVerticalBox = this._getTextHeight(ctx, textLines) / 2; - var _this = this; + var halfOfVerticalBox = this._getTextHeight(ctx, textLines) / 2, + _this = this; /** @ignore */ function renderLinesAtOffset(offset) { for (var i = 0, len = textLines.length; i < len; i++) { - var lineWidth = _this._getLineWidth(ctx, textLines[i]); - var lineLeftOffset = _this._getLineLeftOffset(lineWidth); + var lineWidth = _this._getLineWidth(ctx, textLines[i]), + lineLeftOffset = _this._getLineLeftOffset(lineWidth); ctx.fillRect( _this._getLeftOffset() + lineLeftOffset, @@ -21799,8 +22430,13 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag if (!this.visible) return; ctx.save(); - var v = this.canvas.viewportTransform; + var v = this.getViewportTransform(); ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + + var m = this.transformMatrix; + if (m && (!this.group || this.group.type === 'path-group')) { + ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + } this._render(ctx); ctx.restore(); @@ -21986,7 +22622,7 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag (i === 0 || this.useNative ? 'y' : 'dy'), '="', toFixed(this.useNative ? ((lineHeight * i) - this.height / 2) - : (lineHeight * lineTopOffsetMultiplier), 2) , '" ', + : (lineHeight * lineTopOffsetMultiplier), 2), '" ', // doing this on elements since setting opacity // on containing one doesn't work in Illustrator this._getFillAttributes(this.fill), '>', @@ -22081,7 +22717,14 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag * @see: http://www.w3.org/TR/SVG/text.html#TextElement */ fabric.Text.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat( - 'x y font-family font-style font-weight font-size text-decoration'.split(' ')); + 'x y dx dy font-family font-style font-weight font-size text-decoration text-anchor'.split(' ')); + + /** + * Default SVG font size + * @static + * @memberOf fabric.Text + */ + fabric.Text.DEFAULT_SVG_FONT_SIZE = 16; /** * Returns fabric.Text instance from an SVG element (not yet implemented) @@ -22099,6 +22742,20 @@ fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Imag var parsedAttributes = fabric.parseAttributes(element, fabric.Text.ATTRIBUTE_NAMES); options = fabric.util.object.extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes); + if ('dx' in parsedAttributes) { + options.left += parsedAttributes.dx; + } + if ('dy' in parsedAttributes) { + options.top += parsedAttributes.dy; + } + if (!('fontSize' in options)) { + options.fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE; + } + + if (!options.originX) { + options.originX = 'center'; + } + var text = new fabric.Text(element.textContent, options); /* @@ -22221,9 +22878,9 @@ fabric.util.object.extend(fabric.Text.prototype, { * @extends fabric.Text * @mixes fabric.Observable * - * @fires text:changed - * @fires editing:entered - * @fires editing:exited + * @fires changed ("text:changed" when observing canvas) + * @fires editing:entered ("text:editing:entered" when observing canvas) + * @fires editing:exited ("text:editing:exited" when observing canvas) * * @return {fabric.IText} thisArg * @see {@link fabric.IText#initialize} for constructor definition @@ -22246,6 +22903,7 @@ fabric.util.object.extend(fabric.Text.prototype, { * Copy text: ctrl/cmd + c * Paste text: ctrl/cmd + v * Cut text: ctrl/cmd + x + * Select entire text: ctrl/cmd + a * * *

Supported mouse/touch combination

@@ -22430,6 +23088,9 @@ fabric.util.object.extend(fabric.Text.prototype, { * @param {Number} index Index to set selection start to */ setSelectionStart: function(index) { + if (this.selectionStart !== index) { + this.canvas && this.canvas.fire('text:selection:changed', { target: this }); + } this.selectionStart = index; this.hiddenTextarea && (this.hiddenTextarea.selectionStart = index); }, @@ -22439,6 +23100,9 @@ fabric.util.object.extend(fabric.Text.prototype, { * @param {Number} index Index to set selection end to */ setSelectionEnd: function(index) { + if (this.selectionEnd !== index) { + this.canvas && this.canvas.fire('text:selection:changed', { target: this }); + } this.selectionEnd = index; this.hiddenTextarea && (this.hiddenTextarea.selectionEnd = index); }, @@ -22502,9 +23166,9 @@ fabric.util.object.extend(fabric.Text.prototype, { }, /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ _render: function(ctx) { this.callSuper('_render', ctx); this.ctx = ctx; @@ -22538,8 +23202,8 @@ fabric.util.object.extend(fabric.Text.prototype, { if (typeof selectionStart === 'undefined') { selectionStart = this.selectionStart; } - var textBeforeCursor = this.text.slice(0, selectionStart); - var linesBeforeCursor = textBeforeCursor.split(this._reNewline); + var textBeforeCursor = this.text.slice(0, selectionStart), + linesBeforeCursor = textBeforeCursor.split(this._reNewline); return { lineIndex: linesBeforeCursor.length - 1, @@ -22547,6 +23211,26 @@ fabric.util.object.extend(fabric.Text.prototype, { }; }, + /** + * Returns complete style of char at the current cursor + * @param {Number} lineIndex Line index + * @param {Number} charIndex Char index + * @return {Object} Character style + */ + getCurrentCharStyle: function(lineIndex, charIndex) { + var style = this.styles[lineIndex] && this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)]; + + return { + fontSize: style && style.fontSize || this.fontSize, + fill: style && style.fill || this.fill, + textBackgroundColor: style && style.textBackgroundColor || this.textBackgroundColor, + textDecoration: style && style.textDecoration || this.textDecoration, + fontFamily: style && style.fontFamily || this.fontFamily, + stroke: style && style.stroke || this.stroke, + strokeWidth: style && style.strokeWidth || this.strokeWidth + }; + }, + /** * Returns fontSize of char at the current cursor * @param {Number} lineIndex Line index @@ -22693,13 +23377,16 @@ fabric.util.object.extend(fabric.Text.prototype, { var cursorLocation = this.get2DCursorLocation(), lineIndex = cursorLocation.lineIndex, charIndex = cursorLocation.charIndex, - charHeight = this.getCurrentCharFontSize(lineIndex, charIndex); + charHeight = this.getCurrentCharFontSize(lineIndex, charIndex), + leftOffset = (lineIndex === 0 && charIndex === 0) + ? this._getCachedLineOffset(lineIndex, this.text.split(this._reNewline)) + : boundaries.leftOffset; ctx.fillStyle = this.getCurrentCharColor(lineIndex, charIndex); - ctx.globalAlpha = this._currentCursorOpacity; + ctx.globalAlpha = this.__isMousedown ? 1 : this._currentCursorOpacity; ctx.fillRect( - boundaries.left + boundaries.leftOffset, + boundaries.left + leftOffset, boundaries.top + boundaries.topOffset, this.cursorWidth / this.scaleX, charHeight); @@ -22719,39 +23406,43 @@ fabric.util.object.extend(fabric.Text.prototype, { ctx.fillStyle = this.selectionColor; - var cursorLocation = this.get2DCursorLocation(), - lineIndex = cursorLocation.lineIndex, - charIndex = cursorLocation.charIndex, - textLines = this.text.split(this._reNewline), - origLineIndex = lineIndex; + var start = this.get2DCursorLocation(this.selectionStart), + end = this.get2DCursorLocation(this.selectionEnd), + startLine = start.lineIndex, + endLine = end.lineIndex, + textLines = this.text.split(this._reNewline); - for (var i = this.selectionStart; i < this.selectionEnd; i++) { + for (var i = startLine; i <= endLine; i++) { + var lineOffset = this._getCachedLineOffset(i, textLines) || 0, + lineHeight = this._getCachedLineHeight(i), + boxWidth = 0; - if (chars[i] === '\n') { - boundaries.leftOffset = 0; - boundaries.topOffset += this._getHeightOfLine(ctx, lineIndex); - lineIndex++; - charIndex = 0; - } - else if (i !== this.text.length) { - - var charWidth = this._getWidthOfChar(ctx, chars[i], lineIndex, charIndex), - lineOffset = this._getLineLeftOffset(this._getWidthOfLine(ctx, lineIndex, textLines)) || 0; - - if (lineIndex === origLineIndex) { - // only offset the line if we're rendering selection of 2nd, 3rd, etc. line - lineOffset = 0; + if (i === startLine) { + for (var j = 0, len = textLines[i].length; j < len; j++) { + if (j >= start.charIndex && (i !== endLine || j < end.charIndex)) { + boxWidth += this._getWidthOfChar(ctx, textLines[i][j], i, j); + } + if (j < start.charIndex) { + lineOffset += this._getWidthOfChar(ctx, textLines[i][j], i, j); + } } - - ctx.fillRect( - boundaries.left + boundaries.leftOffset + lineOffset, - boundaries.top + boundaries.topOffset, - charWidth, - this._getHeightOfLine(ctx, lineIndex)); - - boundaries.leftOffset += charWidth; - charIndex++; } + else if (i > startLine && i < endLine) { + boxWidth += this._getCachedLineWidth(i, textLines) || 5; + } + else if (i === endLine) { + for (var j2 = 0, j2len = end.charIndex; j2 < j2len; j2++) { + boxWidth += this._getWidthOfChar(ctx, textLines[i][j2], i, j2); + } + } + + ctx.fillRect( + boundaries.left + lineOffset, + boundaries.top + boundaries.topOffset, + boxWidth, + lineHeight); + + boundaries.topOffset += lineHeight; } ctx.restore(); }, @@ -22781,14 +23472,26 @@ fabric.util.object.extend(fabric.Text.prototype, { lineWidth = this._getWidthOfLine(ctx, lineIndex, textLines), lineHeight = this._getHeightOfLine(ctx, lineIndex, textLines), lineLeftOffset = this._getLineLeftOffset(lineWidth), - chars = line.split(''); + chars = line.split(''), + prevStyle, + charsToRender = ''; left += lineLeftOffset || 0; ctx.save(); - for (var i = 0, len = chars.length; i < len; i++) { - this._renderChar(method, ctx, lineIndex, i, chars[i], left, top, lineHeight); + + for (var i = 0, len = chars.length; i <= len; i++) { + prevStyle = prevStyle || this.getCurrentCharStyle(lineIndex, i); + var thisStyle = this.getCurrentCharStyle(lineIndex, i + 1); + + if (this._hasStyleChanged(prevStyle, thisStyle) || i === len) { + this._renderChar(method, ctx, lineIndex, i - 1, charsToRender, left, top, lineHeight); + charsToRender = ''; + prevStyle = thisStyle; + } + charsToRender += chars[i]; } + ctx.restore(); }, @@ -22851,6 +23554,22 @@ fabric.util.object.extend(fabric.Text.prototype, { } }, + /** + * @private + * @param {Object} prevStyle + * @param {Object} thisStyle + */ + _hasStyleChanged: function(prevStyle, thisStyle) { + return (prevStyle.fill !== thisStyle.fill || + prevStyle.fontSize !== thisStyle.fontSize || + prevStyle.textBackgroundColor !== thisStyle.textBackgroundColor || + prevStyle.textDecoration !== thisStyle.textDecoration || + prevStyle.fontFamily !== thisStyle.fontFamily || + prevStyle.stroke !== thisStyle.stroke || + prevStyle.strokeWidth !== thisStyle.strokeWidth + ); + }, + /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on @@ -22858,10 +23577,10 @@ fabric.util.object.extend(fabric.Text.prototype, { _renderCharDecoration: function(ctx, styleDeclaration, left, top, charWidth, lineHeight, charHeight) { var textDecoration = styleDeclaration - ? (styleDeclaration.textDecoration || this.textDecoration) - : this.textDecoration; + ? (styleDeclaration.textDecoration || this.textDecoration) + : this.textDecoration, - var fontSize = (styleDeclaration ? styleDeclaration.fontSize : null) || this.fontSize; + fontSize = (styleDeclaration ? styleDeclaration.fontSize : null) || this.fontSize; if (!textDecoration) return; @@ -23081,15 +23800,35 @@ fabric.util.object.extend(fabric.Text.prototype, { } }, + /** + * @private + * @param {Number} lineIndex + * @param {Number} charIndex + */ + _getStyleDeclaration: function(lineIndex, charIndex) { + return (this.styles[lineIndex] && this.styles[lineIndex][charIndex]) + ? clone(this.styles[lineIndex][charIndex]) + : { }; + }, + /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _getWidthOfChar: function(ctx, _char, lineIndex, charIndex) { - ctx.save(); - var width = this._applyCharStylesGetWidth(ctx, _char, lineIndex, charIndex); - ctx.restore(); - return width; + var styleDeclaration = this._getStyleDeclaration(lineIndex, charIndex); + this._applyFontStyles(styleDeclaration); + var cacheProp = this._getCacheProp(_char, styleDeclaration); + + if (this._charWidthsCache[cacheProp] && this.caching) { + return this._charWidthsCache[cacheProp]; + } + else if (ctx) { + ctx.save(); + var width = this._applyCharStylesGetWidth(ctx, _char, lineIndex, charIndex); + ctx.restore(); + return width; + } }, /** @@ -23175,10 +23914,9 @@ fabric.util.object.extend(fabric.Text.prototype, { textLines = textLines || this.text.split(this._reNewline); - var maxHeight = this._getHeightOfChar(ctx, textLines[lineIndex][0], lineIndex, 0); - - var line = textLines[lineIndex]; - var chars = line.split(''); + var maxHeight = this._getHeightOfChar(ctx, textLines[lineIndex][0], lineIndex, 0), + line = textLines[lineIndex], + chars = line.split(''); for (var i = 1, len = chars.length; i < len; i++) { var currentCharHeight = this._getHeightOfChar(ctx, chars[i], lineIndex, i); @@ -23211,6 +23949,26 @@ fabric.util.object.extend(fabric.Text.prototype, { return topOffset - (this.fontSize / this._fontSizeFraction); }, + /** + * @private + * This method is overwritten to account for different top offset + */ + _renderTextBoxBackground: function(ctx) { + if (!this.backgroundColor) return; + + ctx.save(); + ctx.fillStyle = this.backgroundColor; + + ctx.fillRect( + this._getLeftOffset(), + this._getTopOffset() + (this.fontSize / this._fontSizeFraction), + this.width, + this.height + ); + + ctx.restore(); + }, + /** * Returns object representation of an instance * @methd toObject @@ -23235,6 +23993,12 @@ fabric.util.object.extend(fabric.Text.prototype, { return new fabric.IText(object.text, clone(object)); }; + /** + * Contains all fabric.IText objects that have been created + * @static + * @memberof fabric.IText + * @type Array + */ fabric.IText.instances = [ ]; })(); @@ -23250,10 +24014,9 @@ fabric.util.object.extend(fabric.Text.prototype, { * Initializes all the interactive behavior of IText */ initBehavior: function() { - this.initKeyHandlers(); + this.initAddedHandler(); this.initCursorSelectionHandlers(); this.initDoubleClickSimulation(); - this.initHiddenTextarea(); }, /** @@ -23266,10 +24029,17 @@ fabric.util.object.extend(fabric.Text.prototype, { setTimeout(function() { _this.selected = true; }, 100); + }); + }, - if (!this._hasCanvasHandlers) { + /** + * Initializes "added" event handler + */ + initAddedHandler: function() { + this.on('added', function() { + if (this.canvas && !this.canvas._hasITextHandlers) { + this.canvas._hasITextHandlers = true; this._initCanvasHandlers(); - this._hasCanvasHandlers = true; } }); }, @@ -23278,25 +24048,18 @@ fabric.util.object.extend(fabric.Text.prototype, { * @private */ _initCanvasHandlers: function() { - var _this = this; - - this.canvas.on('selection:cleared', function(options) { - - // do not exit editing if event fired - // when clicking on an object again (in editing mode) - if (options.e && _this.canvas.containsPoint(options.e, _this)) return; - - _this.exitEditing(); + this.canvas.on('selection:cleared', function() { + fabric.IText.prototype.exitEditingOnOthers.call(); }); this.canvas.on('mouse:up', function() { - this.getObjects('i-text').forEach(function(obj) { + fabric.IText.instances.forEach(function(obj) { obj.__isMousedown = false; }); }); - this.canvas.on('object:selected', function() { - fabric.IText.prototype.exitEditingOnOthers.call(this); + this.canvas.on('object:selected', function(options) { + fabric.IText.prototype.exitEditingOnOthers.call(options.target); }); }, @@ -23356,15 +24119,23 @@ fabric.util.object.extend(fabric.Text.prototype, { /** * Initializes delayed cursor */ - initDelayedCursor: function() { - var _this = this; + initDelayedCursor: function(restart) { + var _this = this, + delay = restart ? 0 : this.cursorDelay; + + if (restart) { + this._abortCursorAnimation = true; + clearTimeout(this._cursorTimeout1); + this._currentCursorOpacity = 1; + this.canvas && this.canvas.renderAll(); + } if (this._cursorTimeout2) { clearTimeout(this._cursorTimeout2); } this._cursorTimeout2 = setTimeout(function() { _this._abortCursorAnimation = false; _this._tick(); - }, this.cursorDelay); + }, delay); }, /** @@ -23391,6 +24162,7 @@ fabric.util.object.extend(fabric.Text.prototype, { selectAll: function() { this.selectionStart = 0; this.selectionEnd = this.text.length; + this.canvas && this.canvas.fire('text:selection:changed', { target: this }); }, /** @@ -23482,8 +24254,9 @@ fabric.util.object.extend(fabric.Text.prototype, { * @return {Number} Number of newlines in selected text */ getNumNewLinesInSelectedText: function() { - var selectedText = this.getSelectedText(); - var numNewLines = 0; + var selectedText = this.getSelectedText(), + numNewLines = 0; + for (var i = 0, chars = selectedText.split(''), len = chars.length; i < len; i++) { if (chars[i] === '\n') { numNewLines++; @@ -23498,9 +24271,9 @@ fabric.util.object.extend(fabric.Text.prototype, { * @param {Number} direction: 1 or -1 */ searchWordBoundary: function(selectionStart, direction) { - var index = selectionStart; - var _char = this.text.charAt(index); - var reNonWord = /[ \n\.,;!\?\-]/; + var index = this._reSpace.test(this.text.charAt(selectionStart)) ? selectionStart - 1 : selectionStart, + _char = this.text.charAt(index), + reNonWord = /[ \n\.,;!\?\-]/; while (!reNonWord.test(_char) && index > 0 && index < this.text.length) { index += direction; @@ -23517,11 +24290,12 @@ fabric.util.object.extend(fabric.Text.prototype, { * @param {Number} selectionStart Index of a character */ selectWord: function(selectionStart) { - var newSelectionStart = this.searchWordBoundary(selectionStart, -1); /* search backwards */ - var newSelectionEnd = this.searchWordBoundary(selectionStart, 1); /* search forward */ + var newSelectionStart = this.searchWordBoundary(selectionStart, -1), /* search backwards */ + newSelectionEnd = this.searchWordBoundary(selectionStart, 1); /* search forward */ this.setSelectionStart(newSelectionStart); this.setSelectionEnd(newSelectionEnd); + this.initDelayedCursor(true); }, /** @@ -23529,11 +24303,12 @@ fabric.util.object.extend(fabric.Text.prototype, { * @param {Number} selectionStart Index of a character */ selectLine: function(selectionStart) { - var newSelectionStart = this.findLineBoundaryLeft(selectionStart); - var newSelectionEnd = this.findLineBoundaryRight(selectionStart); + var newSelectionStart = this.findLineBoundaryLeft(selectionStart), + newSelectionEnd = this.findLineBoundaryRight(selectionStart); this.setSelectionStart(newSelectionStart); this.setSelectionEnd(newSelectionEnd); + this.initDelayedCursor(true); }, /** @@ -23548,6 +24323,7 @@ fabric.util.object.extend(fabric.Text.prototype, { this.isEditing = true; + this.initHiddenTextarea(); this._updateTextarea(); this._saveEditingProps(); this._setEditingProps(); @@ -23556,14 +24332,17 @@ fabric.util.object.extend(fabric.Text.prototype, { this.canvas && this.canvas.renderAll(); this.fire('editing:entered'); + this.canvas && this.canvas.fire('text:editing:entered', { target: this }); return this; }, exitEditingOnOthers: function() { fabric.IText.instances.forEach(function(obj) { - if (obj === this) return; - obj.exitEditing(); + obj.selected = false; + if (obj.isEditing) { + obj.exitEditing(); + } }, this); }, @@ -23591,7 +24370,6 @@ fabric.util.object.extend(fabric.Text.prototype, { this.hiddenTextarea.value = this.text; this.hiddenTextarea.selectionStart = this.selectionStart; - this.hiddenTextarea.focus(); }, /** @@ -23639,13 +24417,15 @@ fabric.util.object.extend(fabric.Text.prototype, { this.selectable = true; this.selectionEnd = this.selectionStart; - this.hiddenTextarea && this.hiddenTextarea.blur(); + this.hiddenTextarea && this.canvas && this.hiddenTextarea.parentNode.removeChild(this.hiddenTextarea); + this.hiddenTextarea = null; this.abortCursorAnimation(); this._restoreEditingProps(); this._currentCursorOpacity = 0; this.fire('editing:exited'); + this.canvas && this.canvas.fire('text:editing:exited', { target: this }); return this; }, @@ -23672,8 +24452,9 @@ fabric.util.object.extend(fabric.Text.prototype, { var prevIndex = this.get2DCursorLocation(i).charIndex; i--; - var index = this.get2DCursorLocation(i).charIndex; - var isNewline = index > prevIndex; + + var index = this.get2DCursorLocation(i).charIndex, + isNewline = index > prevIndex; if (isNewline) { this.removeStyleObject(isNewline, i + 1); @@ -23702,10 +24483,10 @@ fabric.util.object.extend(fabric.Text.prototype, { if (this.selectionStart === this.selectionEnd) { this.insertStyleObjects(_chars, isEndOfLine, this.copiedStyles); } - else if (this.selectionEnd - this.selectionStart > 1) { + // else if (this.selectionEnd - this.selectionStart > 1) { // TODO: replace styles properly - console.log('replacing MORE than 1 char'); - } + // console.log('replacing MORE than 1 char'); + // } this.selectionStart += _chars.length; this.selectionEnd = this.selectionStart; @@ -23717,7 +24498,8 @@ fabric.util.object.extend(fabric.Text.prototype, { } this.setCoords(); - this.fire('text:changed'); + this.fire('changed'); + this.canvas && this.canvas.fire('text:changed', { target: this }); }, /** @@ -23864,7 +24646,9 @@ fabric.util.object.extend(fabric.Text.prototype, { var textLines = this.text.split(this._reNewline), textOnPreviousLine = textLines[lineIndex - 1], - newCharIndexOnPrevLine = textOnPreviousLine.length; + newCharIndexOnPrevLine = textOnPreviousLine + ? textOnPreviousLine.length + : 0; if (!this.styles[lineIndex - 1]) { this.styles[lineIndex - 1] = { }; @@ -23921,7 +24705,7 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot // for triple click this.__lastLastClickTime = +new Date(); - this.lastPointer = { }; + this.__lastPointer = { }; this.on('mousedown', this.onMouseDown.bind(this)); }, @@ -23943,12 +24727,13 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot this.__lastLastClickTime = this.__lastClickTime; this.__lastClickTime = this.__newClickTime; this.__lastPointer = newPointer; + this.__lastIsEditing = this.isEditing; }, isDoubleClick: function(newPointer) { return this.__newClickTime - this.__lastClickTime < 500 && this.__lastPointer.x === newPointer.x && - this.__lastPointer.y === newPointer.y; + this.__lastPointer.y === newPointer.y && this.__lastIsEditing; }, isTripleClick: function(newPointer) { @@ -24005,9 +24790,13 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot this.canvas.wrapperEl.appendChild(this.hiddenTextarea); } - if (this.isEditing) { + if (this.selected) { this.setCursorByClick(options.e); + } + + if (this.isEditing) { this.__selectionStartOnMouseDown = this.selectionStart; + this.initDelayedCursor(true); } }); }, @@ -24048,12 +24837,13 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot initMouseupHandler: function() { this.on('mouseup', function(options) { this.__isMousedown = false; - if (this._isObjectMoved(options.e)) return; if (this.selected) { this.enterEditing(); + this.initDelayedCursor(true); } + this.selected = true; }); }, @@ -24115,10 +24905,10 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot height += this._getHeightOfLine(this.ctx, i) * this.scaleY; - var widthOfLine = this._getWidthOfLine(this.ctx, i, textLines); - var lineLeftOffset = this._getLineLeftOffset(widthOfLine); + var widthOfLine = this._getWidthOfLine(this.ctx, i, textLines), + lineLeftOffset = this._getLineLeftOffset(widthOfLine); - width = lineLeftOffset; + width = lineLeftOffset * this.scaleX; if (this.flipX) { // when oject is horizontally flipped we reverse chars @@ -24141,6 +24931,11 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot return this._getNewSelectionStartFromOffset( mouseOffset, prevWidth, width, charIndex + i, jlen); } + + if (mouseOffset.y < height) { + return this._getNewSelectionStartFromOffset( + mouseOffset, prevWidth, width, charIndex + i, jlen, j); + } } // clicked somewhere after all chars, so set at the end @@ -24152,7 +24947,7 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot /** * @private */ - _getNewSelectionStartFromOffset: function(mouseOffset, prevWidth, width, index, jlen) { + _getNewSelectionStartFromOffset: function(mouseOffset, prevWidth, width, index, jlen, j) { var distanceBtwLastCharAndCursor = mouseOffset.x - prevWidth, distanceBtwNextCharAndCursor = width - mouseOffset.x, @@ -24168,6 +24963,10 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot newSelectionStart = this.text.length; } + if (j === jlen) { + newSelectionStart--; + } + return newSelectionStart; } }); @@ -24175,14 +24974,6 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ { - /** - * Initializes key handlers - */ - initKeyHandlers: function() { - fabric.util.addListener(fabric.document, 'keydown', this.onKeyDown.bind(this)); - fabric.util.addListener(fabric.document, 'keypress', this.onKeyPress.bind(this)); - }, - /** * Initializes hidden textarea (needed to bring up keyboard in iOS) */ @@ -24193,6 +24984,14 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot this.hiddenTextarea.style.cssText = 'position: absolute; top: 0; left: -9999px'; fabric.document.body.appendChild(this.hiddenTextarea); + + fabric.util.addListener(this.hiddenTextarea, 'keydown', this.onKeyDown.bind(this)); + fabric.util.addListener(this.hiddenTextarea, 'keypress', this.onKeyPress.bind(this)); + + if (!this._clickHandlerInitialized && this.canvas) { + fabric.util.addListener(this.canvas.upperCanvasEl, 'click', this.onClick.bind(this)); + this._clickHandlerInitialized = true; + } }, /** @@ -24218,6 +25017,11 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot 88: 'cut' }, + onClick: function() { + // No need to trigger click event here, focus is enough to have the keyboard appear on Android + this.hiddenTextarea && this.hiddenTextarea.focus(); + }, + /** * Handles keyup event * @param {Event} e Event object @@ -24324,8 +25128,8 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot var widthOfSameLineBeforeCursor = this._getWidthOfLine(this.ctx, cursorLocation.lineIndex, textLines); lineLeftOffset = this._getLineLeftOffset(widthOfSameLineBeforeCursor); - var widthOfCharsOnSameLineBeforeCursor = lineLeftOffset; - var lineIndex = cursorLocation.lineIndex; + var widthOfCharsOnSameLineBeforeCursor = lineLeftOffset, + lineIndex = cursorLocation.lineIndex; for (var i = 0, len = textOnSameLineBeforeCursor.length; i < len; i++) { _char = textOnSameLineBeforeCursor[i]; @@ -24343,17 +25147,17 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot */ _getIndexOnNextLine: function(cursorLocation, textOnNextLine, widthOfCharsOnSameLineBeforeCursor, textLines) { - var lineIndex = cursorLocation.lineIndex + 1; - var widthOfNextLine = this._getWidthOfLine(this.ctx, lineIndex, textLines); - var lineLeftOffset = this._getLineLeftOffset(widthOfNextLine); - var widthOfCharsOnNextLine = lineLeftOffset; - var indexOnNextLine = 0; - var foundMatch; + var lineIndex = cursorLocation.lineIndex + 1, + widthOfNextLine = this._getWidthOfLine(this.ctx, lineIndex, textLines), + lineLeftOffset = this._getLineLeftOffset(widthOfNextLine), + widthOfCharsOnNextLine = lineLeftOffset, + indexOnNextLine = 0, + foundMatch; for (var j = 0, jlen = textOnNextLine.length; j < jlen; j++) { - var _char = textOnNextLine[j]; - var widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j); + var _char = textOnNextLine[j], + widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j); widthOfCharsOnNextLine += widthOfChar; @@ -24361,10 +25165,10 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot foundMatch = true; - var leftEdge = widthOfCharsOnNextLine - widthOfChar; - var rightEdge = widthOfCharsOnNextLine; - var offsetFromLeftEdge = Math.abs(leftEdge - widthOfCharsOnSameLineBeforeCursor); - var offsetFromRightEdge = Math.abs(rightEdge - widthOfCharsOnSameLineBeforeCursor); + var leftEdge = widthOfCharsOnNextLine - widthOfChar, + rightEdge = widthOfCharsOnNextLine, + offsetFromLeftEdge = Math.abs(leftEdge - widthOfCharsOnSameLineBeforeCursor), + offsetFromRightEdge = Math.abs(rightEdge - widthOfCharsOnSameLineBeforeCursor); indexOnNextLine = offsetFromRightEdge < offsetFromLeftEdge ? j + 1 : j; @@ -24452,13 +25256,10 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot textOnPreviousLine = (textBeforeCursor.match(/\n?(.*)\n.*$/) || {})[1] || '', textLines = this.text.split(this._reNewline), _char, - lineLeftOffset; - - var widthOfSameLineBeforeCursor = this._getWidthOfLine(this.ctx, cursorLocation.lineIndex, textLines); - lineLeftOffset = this._getLineLeftOffset(widthOfSameLineBeforeCursor); - - var widthOfCharsOnSameLineBeforeCursor = lineLeftOffset; - var lineIndex = cursorLocation.lineIndex; + widthOfSameLineBeforeCursor = this._getWidthOfLine(this.ctx, cursorLocation.lineIndex, textLines), + lineLeftOffset = this._getLineLeftOffset(widthOfSameLineBeforeCursor), + widthOfCharsOnSameLineBeforeCursor = lineLeftOffset, + lineIndex = cursorLocation.lineIndex; for (var i = 0, len = textOnSameLineBeforeCursor.length; i < len; i++) { _char = textOnSameLineBeforeCursor[i]; @@ -24476,17 +25277,17 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot */ _getIndexOnPrevLine: function(cursorLocation, textOnPreviousLine, widthOfCharsOnSameLineBeforeCursor, textLines) { - var lineIndex = cursorLocation.lineIndex - 1; - var widthOfPreviousLine = this._getWidthOfLine(this.ctx, lineIndex, textLines); - var lineLeftOffset = this._getLineLeftOffset(widthOfPreviousLine); - var widthOfCharsOnPreviousLine = lineLeftOffset; - var indexOnPrevLine = 0; - var foundMatch; + var lineIndex = cursorLocation.lineIndex - 1, + widthOfPreviousLine = this._getWidthOfLine(this.ctx, lineIndex, textLines), + lineLeftOffset = this._getLineLeftOffset(widthOfPreviousLine), + widthOfCharsOnPreviousLine = lineLeftOffset, + indexOnPrevLine = 0, + foundMatch; for (var j = 0, jlen = textOnPreviousLine.length; j < jlen; j++) { - var _char = textOnPreviousLine[j]; - var widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j); + var _char = textOnPreviousLine[j], + widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j); widthOfCharsOnPreviousLine += widthOfChar; @@ -24494,10 +25295,10 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot foundMatch = true; - var leftEdge = widthOfCharsOnPreviousLine - widthOfChar; - var rightEdge = widthOfCharsOnPreviousLine; - var offsetFromLeftEdge = Math.abs(leftEdge - widthOfCharsOnSameLineBeforeCursor); - var offsetFromRightEdge = Math.abs(rightEdge - widthOfCharsOnSameLineBeforeCursor); + var leftEdge = widthOfCharsOnPreviousLine - widthOfChar, + rightEdge = widthOfCharsOnPreviousLine, + offsetFromLeftEdge = Math.abs(leftEdge - widthOfCharsOnSameLineBeforeCursor), + offsetFromRightEdge = Math.abs(rightEdge - widthOfCharsOnSameLineBeforeCursor); indexOnPrevLine = offsetFromRightEdge < offsetFromLeftEdge ? j : (j - 1); @@ -24747,7 +25548,8 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot } this.setCoords(); - this.fire('text:changed'); + this.fire('changed'); + this.canvas && this.canvas.fire('text:changed', { target: this }); }, /** @@ -24771,7 +25573,7 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot this.selectionStart = leftWordBoundary; } else { - var isBeginningOfLine = this.text.slice(this.selectionStart-1, this.selectionStart) === '\n'; + var isBeginningOfLine = this.text.slice(this.selectionStart - 1, this.selectionStart) === '\n'; this.removeStyleObject(isBeginningOfLine); this.selectionStart--; @@ -24905,7 +25707,7 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot return; } - var DOMParser = new require('xmldom').DOMParser, + var DOMParser = require('xmldom').DOMParser, URL = require('url'), HTTP = require('http'), HTTPS = require('https'), @@ -24923,27 +25725,26 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot } // assign request handler based on protocol - var reqHandler = ( oURL.port === 443 ) ? HTTPS : HTTP; - - var req = reqHandler.request({ - hostname: oURL.hostname, - port: oURL.port, - path: oURL.path, - method: 'GET' - }, function(response){ - var body = ""; - if (encoding) { - response.setEncoding(encoding); - } - response.on('end', function () { - callback(body); - }); - response.on('data', function (chunk) { - if (response.statusCode === 200) { - body += chunk; - } - }); - }); + var reqHandler = ( oURL.port === 443 ) ? HTTPS : HTTP, + req = reqHandler.request({ + hostname: oURL.hostname, + port: oURL.port, + path: oURL.path, + method: 'GET' + }, function(response) { + var body = ''; + if (encoding) { + response.setEncoding(encoding); + } + response.on('end', function () { + callback(body); + }); + response.on('data', function (chunk) { + if (response.statusCode === 200) { + body += chunk; + } + }); + }); req.on('error', function(err) { if (err.errno === process.ECONNREFUSED) { @@ -24958,32 +25759,33 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot } /** @private */ - function request_fs(path, callback){ + function requestFs(path, callback){ var fs = require('fs'); fs.readFile(path, function (err, data) { if (err) { fabric.log(err); throw err; - } else { + } + else { callback(data); } }); } fabric.util.loadImage = function(url, callback, context) { - var createImageAndCallBack = function(data){ + function createImageAndCallBack(data) { img.src = new Buffer(data, 'binary'); // preserving original url, which seems to be lost in node-canvas img._src = url; callback && callback.call(context, img); - }; + } var img = new Image(); if (url && (url instanceof Buffer || url.indexOf('data') === 0)) { img.src = img._src = url; callback && callback.call(context, img); } else if (url && url.indexOf('http') !== 0) { - request_fs(url, createImageAndCallBack); + requestFs(url, createImageAndCallBack); } else if (url) { request(url, 'binary', createImageAndCallBack); @@ -24996,8 +25798,8 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot fabric.loadSVGFromURL = function(url, callback, reviver) { url = url.replace(/^\n\s*/, '').replace(/\?.*$/, '').trim(); if (url.indexOf('http') !== 0) { - request_fs(url, function(body) { - fabric.loadSVGFromString(body, callback, reviver); + requestFs(url, function(body) { + fabric.loadSVGFromString(body.toString(), callback, reviver); }); } else { @@ -25037,12 +25839,15 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot * Only available when running fabric on node.js * @param width Canvas width * @param height Canvas height + * @param {Object} options to pass to FabricCanvas. + * @param {Object} options to pass to NodeCanvas. * @return {Object} wrapped canvas instance */ - fabric.createCanvasForNode = function(width, height) { + fabric.createCanvasForNode = function(width, height, options, nodeCanvasOptions) { + nodeCanvasOptions = nodeCanvasOptions || options; var canvasEl = fabric.document.createElement('canvas'), - nodeCanvas = new Canvas(width || 600, height || 600); + nodeCanvas = new Canvas(width || 600, height || 600, nodeCanvasOptions); // jsdom doesn't create style on canvas element, so here be temp. workaround canvasEl.style = { }; @@ -25050,8 +25855,9 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot canvasEl.width = nodeCanvas.width; canvasEl.height = nodeCanvas.height; - var FabricCanvas = fabric.Canvas || fabric.StaticCanvas; - var fabricCanvas = new FabricCanvas(canvasEl); + var FabricCanvas = fabric.Canvas || fabric.StaticCanvas, + fabricCanvas = new FabricCanvas(canvasEl, options); + fabricCanvas.contextContainer = nodeCanvas.getContext('2d'); fabricCanvas.nodeCanvas = nodeCanvas; fabricCanvas.Font = Canvas.Font; @@ -25090,3 +25896,16 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot })(); + +/* Footer for requirejs AMD support */ + +window.fabric = fabric; + +// make sure exports.fabric is always defined when used as 'global' later scopes +var exports = exports || {}; +exports.fabric = fabric; + +if (typeof define === 'function' && define.amd) { + define([], function() { return fabric }); +} + diff --git a/lib/aligning_guidelines.js b/lib/aligning_guidelines.js index 289240b9..26282d5e 100644 --- a/lib/aligning_guidelines.js +++ b/lib/aligning_guidelines.js @@ -1,5 +1,5 @@ /** - * Should objects by aligned by a bounding box? + * Should objects be aligned by a bounding box? * [Bug] Scaled objects sometimes can not be aligned by edges * */ diff --git a/lib/event.js b/lib/event.js index e7790348..84b0f130 100644 --- a/lib/event.js +++ b/lib/event.js @@ -27,10 +27,10 @@ if (typeof(eventjs) === "undefined") var eventjs = Event; (function(root) { "use strict"; // Add custom *EventListener commands to HTMLElements (set false to prevent funkiness). -root.modifyEventListener = true; +root.modifyEventListener = false; // Add bulk *EventListener commands on NodeLists from querySelectorAll and others (set false to prevent funkiness). -root.modifySelectors = true; +root.modifySelectors = false; // Event maintenance. root.add = function(target, type, listener, configure) { diff --git a/lib/excanvas-diff.patch b/lib/excanvas-diff.patch index 2b7d64cc..37ad3c8b 100644 --- a/lib/excanvas-diff.patch +++ b/lib/excanvas-diff.patch @@ -6,7 +6,7 @@ Index: excanvas.js o2.arcScaleX_ = o1.arcScaleX_; o2.arcScaleY_ = o1.arcScaleY_; o2.lineScale_ = o1.lineScale_; -+ o2.rotation_ = o1.rotation_; // used for images ++ o2.rotation_ = o1.rotation_; // used for images } var colorData = { @@ -14,7 +14,7 @@ Index: excanvas.js this.arcScaleX_ = 1; this.arcScaleY_ = 1; this.lineScale_ = 1; -+ this.rotation_ = 0; ++ this.rotation_ = 0; } var contextPrototype = CanvasRenderingContext2D_.prototype; @@ -26,10 +26,10 @@ Index: excanvas.js + contextPrototype.drawImage = function(image) { var dx, dy, dw, dh, sx, sy, sw, sh; - -+ ++ + // to fix new Image() we check the existance of runtimeStyle + var rts = image.runtimeStyle.width; -+ ++ // to find the original width we overide the width and height - var oldRuntimeWidth = image.runtimeStyle.width; - var oldRuntimeHeight = image.runtimeStyle.height; @@ -38,25 +38,25 @@ Index: excanvas.js + if(rts) { + var oldRuntimeWidth = image.runtimeStyle.width; + var oldRuntimeHeight = image.runtimeStyle.height; -+ ++ + image.runtimeStyle.width = 'auto'; -+ image.runtimeStyle.height = 'auto'; ++ image.runtimeStyle.height = 'auto'; + } // get the original size var w = image.width; var h = image.height; - -+ ++ // and remove overides - image.runtimeStyle.width = oldRuntimeWidth; - image.runtimeStyle.height = oldRuntimeHeight; - + if(rts) { + image.runtimeStyle.width = oldRuntimeWidth; -+ image.runtimeStyle.height = oldRuntimeHeight; ++ image.runtimeStyle.height = oldRuntimeHeight; + } -+ ++ if (arguments.length == 3) { dx = arguments[1]; dy = arguments[2]; @@ -64,9 +64,9 @@ Index: excanvas.js var W = 10; var H = 10; -+ ++ + var scaleX = scaleY = 1; -+ ++ + // FIX: divs give better quality then vml image and also fixes transparent PNG's + vmlStr.push('
'); + } -+ -+ ++ ++ + // Apply scales to width and height + vmlStr.push('
'); -+ -+ // Close the crop div if necessary ++ ++ // Close the crop div if necessary + if (sx || sy) vmlStr.push('
'); -+ ++ + vmlStr.push('
'); -+ ++ + this.element_.insertAdjacentHTML('beforeEnd', vmlStr.join('')); }; @@ -176,8 +176,8 @@ Index: excanvas.js var c = mc(aRot); var s = ms(aRot); -+ this.rotation_ += aRot; -+ ++ this.rotation_ += aRot; ++ var m1 = [ [c, s, 0], [-s, c, 0], @@ -188,7 +188,7 @@ Index: excanvas.js - this.textMeasureEl_.style.font = this.font; + // FIX: Apply current font style to textMeasureEl to get correct size + var fontStyle = getComputedStyle(processFontStyle(this.font), this.element_), -+ fontStyleString = buildStyle(fontStyle); ++ fontStyleString = buildStyle(fontStyle); + this.textMeasureEl_.style.font = fontStyleString; + // Don't use innerHTML or innerText because they allow markup/whitespace. diff --git a/lib/excanvas.js b/lib/excanvas.js index a6dceecb..b95043cf 100644 --- a/lib/excanvas.js +++ b/lib/excanvas.js @@ -253,7 +253,7 @@ if (!document.createElement('canvas').getContext) { o2.arcScaleX_ = o1.arcScaleX_; o2.arcScaleY_ = o1.arcScaleY_; o2.lineScale_ = o1.lineScale_; - o2.rotation_ = o1.rotation_; // used for images + o2.rotation_ = o1.rotation_; // used for images } var colorData = { @@ -604,7 +604,7 @@ if (!document.createElement('canvas').getContext) { this.arcScaleX_ = 1; this.arcScaleY_ = 1; this.lineScale_ = 1; - this.rotation_ = 0; + this.rotation_ = 0; } var contextPrototype = CanvasRenderingContext2D_.prototype; @@ -771,29 +771,29 @@ if (!document.createElement('canvas').getContext) { contextPrototype.drawImage = function(image) { var dx, dy, dw, dh, sx, sy, sw, sh; - + // to fix new Image() we check the existance of runtimeStyle var rts = image.runtimeStyle.width; - + // to find the original width we overide the width and height if(rts) { var oldRuntimeWidth = image.runtimeStyle.width; var oldRuntimeHeight = image.runtimeStyle.height; - + image.runtimeStyle.width = 'auto'; - image.runtimeStyle.height = 'auto'; + image.runtimeStyle.height = 'auto'; } // get the original size var w = image.width; var h = image.height; - + // and remove overides if(rts) { image.runtimeStyle.width = oldRuntimeWidth; - image.runtimeStyle.height = oldRuntimeHeight; + image.runtimeStyle.height = oldRuntimeHeight; } - + if (arguments.length == 3) { dx = arguments[1]; dy = arguments[2]; @@ -830,9 +830,9 @@ if (!document.createElement('canvas').getContext) { var W = 10; var H = 10; - + var scaleX = scaleY = 1; - + // FIX: divs give better quality then vml image and also fixes transparent PNG's vmlStr.push('
'); } - - + + // Apply scales to width and height vmlStr.push('
'); - - // Close the crop div if necessary + + // Close the crop div if necessary if (sx || sy) vmlStr.push('
'); - + vmlStr.push('
'); - + this.element_.insertAdjacentHTML('beforeEnd', vmlStr.join('')); }; @@ -1198,8 +1198,8 @@ if (!document.createElement('canvas').getContext) { var c = mc(aRot); var s = ms(aRot); - this.rotation_ += aRot; - + this.rotation_ += aRot; + var m1 = [ [c, s, 0], [-s, c, 0], @@ -1355,7 +1355,7 @@ if (!document.createElement('canvas').getContext) { this.textMeasureEl_.innerHTML = ''; // FIX: Apply current font style to textMeasureEl to get correct size var fontStyle = getComputedStyle(processFontStyle(this.font), this.element_), - fontStyleString = buildStyle(fontStyle); + fontStyleString = buildStyle(fontStyle); this.textMeasureEl_.style.font = fontStyleString; // Don't use innerHTML or innerText because they allow markup/whitespace. diff --git a/lib/json2.js b/lib/json2.js index a43520a6..deb88ec9 100644 --- a/lib/json2.js +++ b/lib/json2.js @@ -1,6 +1,6 @@ /* json2.js - 2011-10-19 + 2014-02-04 Public Domain. @@ -159,8 +159,7 @@ // 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) { +if (typeof JSON !== 'object') { JSON = {}; } @@ -174,8 +173,7 @@ if (!JSON) { if (typeof Date.prototype.toJSON !== 'function') { - /** @ignore */ - Date.prototype.toJSON = function (key) { + Date.prototype.toJSON = function () { return isFinite(this.valueOf()) ? this.getUTCFullYear() + '-' + @@ -189,25 +187,16 @@ if (!JSON) { String.prototype.toJSON = Number.prototype.toJSON = - /** @ignore */ - Boolean.prototype.toJSON = function (key) { + Boolean.prototype.toJSON = function () { 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, + var cx, + escapable, gap, indent, - meta = { // table of character substitutions - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '"' : '\\"', - '\\': '\\\\' - }, + meta, rep; @@ -359,7 +348,16 @@ if (!JSON) { // If the JSON object does not yet have a stringify method, give it one. if (typeof JSON.stringify !== 'function') { - /** @ignore */ + escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }; JSON.stringify = function (value, replacer, space) { // The stringify method takes a value and an optional replacer, and an optional @@ -407,7 +405,7 @@ if (!JSON) { // If the JSON object does not yet have a parse method, give it one. if (typeof JSON.parse !== 'function') { - /** @ignore */ + cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; JSON.parse = function (text, reviver) { // The parse method takes a text and an optional reviver function, and returns @@ -488,4 +486,4 @@ if (!JSON) { throw new SyntaxError('JSON.parse'); }; } -}()); \ No newline at end of file +}()); diff --git a/lib/screenshot.png b/lib/screenshot.png index 27c2b3b7..1135d4cd 100644 Binary files a/lib/screenshot.png and b/lib/screenshot.png differ diff --git a/package.json b/package.json index efb8d684..ab88eb81 100644 --- a/package.json +++ b/package.json @@ -1,30 +1,46 @@ { "name": "fabric", "description": "Object model for HTML5 canvas, and SVG-to-canvas parser. Backed by jsdom and node-canvas.", - "version": "1.4.0", + "homepage": "http://fabricjs.com/", + "version": "1.4.6", "author": "Juriy Zaytsev ", - "keywords": ["canvas", "graphic", "graphics", "SVG", "node-canvas", "parser", "HTML5", "object model"], - "repository": "git://github.com/kangax/fabric.js", - "licenses": [{ - "type": "MIT", - "url": "http://github.com/kangax/fabric.js/raw/master/LICENSE" - }], + "keywords": [ + "canvas", + "graphic", + "graphics", + "SVG", + "node-canvas", + "parser", + "HTML5", + "object model" + ], + "repository": { + "type": "git", + "url": "https://github.com/kangax/fabric.js" + }, + "bugs": { + "url": "https://github.com/kangax/fabric.js/issues" + }, + "license": "MIT", "scripts": { "build": "node build.js modules=ALL exclude=json,cufon,gestures", "test": "node test.js && jshint src" }, "dependencies": { "canvas": "1.0.x", - "jsdom": "0.7.x", + "jsdom": "0.10.x", "xmldom": "0.1.x" }, "devDependencies": { - "qunit": "0.5.x", - "jshint": "2.3.x", - "uglify-js": "2.4.x", "execSync": "0.0.x", - "plato": "0.6.x" + "uglify-js": "2.4.x", + "jscs": "1.4.x", + "jshint": "2.5.x", + "qunit": "0.6.x", + "istanbul": "0.2.6" }, - "engines": { "node": ">=0.4.0 && <1.0.0" }, - "main": "./dist/all.js" + "engines": { + "node": ">=0.4.0 && <1.0.0" + }, + "main": "./dist/fabric.js" } diff --git a/src/amd/requirejs.js b/src/amd/requirejs.js index 26a2696d..56259b84 100644 --- a/src/amd/requirejs.js +++ b/src/amd/requirejs.js @@ -6,6 +6,6 @@ window.fabric = fabric; var exports = exports || {}; exports.fabric = fabric; -if (typeof define === "function" && define.amd) { +if (typeof define === 'function' && define.amd) { define([], function() { return fabric }); } diff --git a/src/brushes/circle_brush.class.js b/src/brushes/circle_brush.class.js index 2302bb55..af24e237 100644 --- a/src/brushes/circle_brush.class.js +++ b/src/brushes/circle_brush.class.js @@ -25,8 +25,8 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric * @param {Object} pointer */ drawDot: function(pointer) { - var point = this.addPoint(pointer); - var ctx = this.canvas.contextTop; + var point = this.addPoint(pointer), + ctx = this.canvas.contextTop; var v = this.canvas.viewportTransform; ctx.save(); @@ -69,15 +69,15 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric var circles = [ ]; for (var i = 0, len = this.points.length; i < len; i++) { - var point = this.points[i]; - var circle = new fabric.Circle({ - radius: this.points[i].radius, - left: point.x, - top: point.y, - originX: 'center', - originY: 'center', - fill: this.points[i].fill - }); + var point = this.points[i], + circle = new fabric.Circle({ + radius: point.radius, + left: point.x, + top: point.y, + originX: 'center', + originY: 'center', + fill: point.fill + }); this.shadow && circle.setShadow(this.shadow); @@ -100,12 +100,12 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric * @return {fabric.Point} Just added pointer point */ addPoint: function(pointer) { - var pointerPoint = new fabric.Point(pointer.x, pointer.y); + var pointerPoint = new fabric.Point(pointer.x, pointer.y), - var circleRadius = fabric.util.getRandomInt( - Math.max(0, this.width - 20), this.width + 20) / 2; + circleRadius = fabric.util.getRandomInt( + Math.max(0, this.width - 20), this.width + 20) / 2, - var circleColor = new fabric.Color(this.color) + circleColor = new fabric.Color(this.color) .setAlpha(fabric.util.getRandomInt(0, 100) / 100) .toRgba(); diff --git a/src/brushes/pencil_brush.class.js b/src/brushes/pencil_brush.class.js index d6a2c947..5d6b00ec 100644 --- a/src/brushes/pencil_brush.class.js +++ b/src/brushes/pencil_brush.class.js @@ -109,27 +109,27 @@ ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); ctx.beginPath(); - var p1 = this._points[0]; - var p2 = this._points[1]; + var p1 = this._points[0], + p2 = this._points[1]; //if we only have 2 points in the path and they are the same //it means that the user only clicked the canvas without moving the mouse //then we should be drawing a dot. A path isn't drawn between two identical dots //that's why we set them apart a bit if (this._points.length === 2 && p1.x === p2.x && p1.y === p2.y) { - p1.x -= 0.5; - p2.x += 0.5; + p1.x -= 0.5; + p2.x += 0.5; } ctx.moveTo(p1.x, p1.y); for (var i = 1, len = this._points.length; i < len; i++) { - // we pick the point between pi+1 & pi+2 as the + // we pick the point between pi + 1 & pi + 2 as the // end point and p1 as our control point. var midPoint = p1.midPointFrom(p2); ctx.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y); p1 = this._points[i]; - p2 = this._points[i+1]; + p2 = this._points[i + 1]; } // Draw last line as a straight line while // we wait for the next point to be able to calculate @@ -155,7 +155,7 @@ * @param {Array} points * @return {Object} object with minx, miny, maxx, maxy */ - getPathBoundingBox: function(points) { + getPathBoundingBox: function(points) { var xBounds = [], yBounds = [], p1 = points[0], @@ -171,19 +171,19 @@ yBounds.push(midPoint.y); p1 = points[i]; - p2 = points[i+1]; + p2 = points[i + 1]; startPoint = midPoint; - } // end for + } - xBounds.push(p1.x); - yBounds.push(p1.y); + xBounds.push(p1.x); + yBounds.push(p1.y); - return { - minx: utilMin(xBounds), - miny: utilMin(yBounds), - maxx: utilMax(xBounds), - maxy: utilMax(yBounds) - }; + return { + minx: utilMin(xBounds), + miny: utilMin(yBounds), + maxx: utilMax(xBounds), + maxy: utilMax(yBounds) + }; }, /** @@ -192,9 +192,9 @@ * @return {String} SVG path */ convertPointsToSVGPath: function(points, minX, maxX, minY) { - var path = []; - var p1 = new fabric.Point(points[0].x - minX, points[0].y - minY); - var p2 = new fabric.Point(points[1].x - minX, points[1].y - minY); + var path = [], + p1 = new fabric.Point(points[0].x - minX, points[0].y - minY), + p2 = new fabric.Point(points[1].x - minX, points[1].y - minY); path.push('M ', points[0].x - minX, ' ', points[0].y - minY, ' '); for (var i = 1, len = points.length; i < len; i++) { @@ -204,8 +204,8 @@ // start point is p(i-1) value. path.push('Q ', p1.x, ' ', p1.y, ' ', midPoint.x, ' ', midPoint.y, ' '); p1 = new fabric.Point(points[i].x - minX, points[i].y - minY); - if ((i+1) < points.length) { - p2 = new fabric.Point(points[i+1].x - minX, points[i+1].y - minY); + if ((i + 1) < points.length) { + p2 = new fabric.Point(points[i + 1].x - minX, points[i + 1].y - minY); } } path.push('L ', p1.x, ' ', p1.y, ' '); @@ -244,7 +244,7 @@ ctx.closePath(); var pathData = this._getSVGPathData().join(''); - if (pathData === "M 0 0 Q 0 0 0 0 L 0 0") { + if (pathData === 'M 0 0 Q 0 0 0 0 L 0 0') { // do not create 0 width/height paths, as they are // rendered inconsistently across browsers // Firefox 4, for example, renders a dot, @@ -254,8 +254,8 @@ } // set path origin coordinates based on our bounding box - var originLeft = this.box.minx + (this.box.maxx - this.box.minx) /2; - var originTop = this.box.miny + (this.box.maxy - this.box.miny) /2; + var originLeft = this.box.minx + (this.box.maxx - this.box.minx) / 2, + originTop = this.box.miny + (this.box.maxy - this.box.miny) / 2; this.canvas.contextTop.arc(originLeft, originTop, 3, 0, Math.PI * 2, false); diff --git a/src/brushes/| b/src/brushes/| deleted file mode 100644 index 2302bb55..00000000 --- a/src/brushes/| +++ /dev/null @@ -1,119 +0,0 @@ -/** - * CircleBrush class - * @class fabric.CircleBrush - */ -fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric.CircleBrush.prototype */ { - - /** - * Width of a brush - * @type Number - * @default - */ - width: 10, - - /** - * Constructor - * @param {fabric.Canvas} canvas - * @return {fabric.CircleBrush} Instance of a circle brush - */ - initialize: function(canvas) { - this.canvas = canvas; - this.points = [ ]; - }, - /** - * Invoked inside on mouse down and mouse move - * @param {Object} pointer - */ - drawDot: function(pointer) { - var point = this.addPoint(pointer); - var ctx = this.canvas.contextTop; - - var v = this.canvas.viewportTransform; - ctx.save(); - ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); - - ctx.fillStyle = point.fill; - ctx.beginPath(); - ctx.arc(point.x, point.y, point.radius, 0, Math.PI * 2, false); - ctx.closePath(); - ctx.fill(); - - ctx.restore(); - }, - - /** - * Invoked on mouse down - */ - onMouseDown: function(pointer) { - this.points.length = 0; - this.canvas.clearContext(this.canvas.contextTop); - this._setShadow(); - this.drawDot(pointer); - }, - - /** - * Invoked on mouse move - * @param {Object} pointer - */ - onMouseMove: function(pointer) { - this.drawDot(pointer); - }, - - /** - * Invoked on mouse up - */ - onMouseUp: function() { - var originalRenderOnAddRemove = this.canvas.renderOnAddRemove; - this.canvas.renderOnAddRemove = false; - - var circles = [ ]; - - for (var i = 0, len = this.points.length; i < len; i++) { - var point = this.points[i]; - var circle = new fabric.Circle({ - radius: this.points[i].radius, - left: point.x, - top: point.y, - originX: 'center', - originY: 'center', - fill: this.points[i].fill - }); - - this.shadow && circle.setShadow(this.shadow); - - circles.push(circle); - } - var group = new fabric.Group(circles, { originX: 'center', originY: 'center' }); - group.canvas = this.canvas; - - this.canvas.add(group); - this.canvas.fire('path:created', { path: group }); - - this.canvas.clearContext(this.canvas.contextTop); - this._resetShadow(); - this.canvas.renderOnAddRemove = originalRenderOnAddRemove; - this.canvas.renderAll(); - }, - - /** - * @param {Object} pointer - * @return {fabric.Point} Just added pointer point - */ - addPoint: function(pointer) { - var pointerPoint = new fabric.Point(pointer.x, pointer.y); - - var circleRadius = fabric.util.getRandomInt( - Math.max(0, this.width - 20), this.width + 20) / 2; - - var circleColor = new fabric.Color(this.color) - .setAlpha(fabric.util.getRandomInt(0, 100) / 100) - .toRgba(); - - pointerPoint.radius = circleRadius; - pointerPoint.fill = circleColor; - - this.points.push(pointerPoint); - - return pointerPoint; - } -}); diff --git a/src/canvas.class.js b/src/canvas.class.js index 36cad3f0..bd7326e2 100644 --- a/src/canvas.class.js +++ b/src/canvas.class.js @@ -29,6 +29,8 @@ * @fires mouse:down * @fires mouse:move * @fires mouse:up + * @fires mouse:over + * @fires mouse:out * */ fabric.Canvas = fabric.util.createClass(fabric.StaticCanvas, /** @lends fabric.Canvas.prototype */ { @@ -203,10 +205,10 @@ var t = this._currentTransform; t.target.set({ - 'scaleX': t.original.scaleX, - 'scaleY': t.original.scaleY, - 'left': t.original.left, - 'top': t.original.top + scaleX: t.original.scaleX, + scaleY: t.original.scaleY, + left: t.original.left, + top: t.original.top }); if (this._shouldCenterTransform(e, t.target)) { @@ -253,7 +255,7 @@ // http://www.geog.ubc.ca/courses/klink/gis.notes/ncgia/u32.html // http://idav.ucdavis.edu/~okreylos/TAship/Spring2000/PointInPolygon.html - return (target.containsPoint(xy) || target._findTargetCorner(e, this._offset)); + return (target.containsPoint(xy) || target._findTargetCorner(pointer)); }, /** @@ -263,14 +265,12 @@ var activeGroup = this.getActiveGroup(), x = pointer.x, y = pointer.y, + isObjectInGroup = ( + activeGroup && + object.type !== 'group' && + activeGroup.contains(object)), lt; - var isObjectInGroup = ( - activeGroup && - object.type !== 'group' && - activeGroup.contains(object) - ); - if (isObjectInGroup) { lt = new fabric.Point(activeGroup.left, activeGroup.top); lt = fabric.util.transformPoint(lt, this.viewportTransform, true); @@ -405,11 +405,8 @@ _setupCurrentTransform: function (e, target) { if (!target) return; - var corner = target._findTargetCorner(e, this._offset), - pointer = fabric.util.transformPoint( - getPointer(e, this.upperCanvasEl), - fabric.util.invertTransform(this.viewportTransform) - ), + var pointer = this.getPointer(e), + corner = target._findTargetCorner(this.getPointer(e, true)), action = this._getActionFromCorner(target, corner), origin = this._getOriginFromCorner(target, corner); @@ -471,7 +468,6 @@ */ _scaleObject: function (x, y, by) { var t = this._currentTransform, - offset = this._offset, target = t.target, lockScalingX = target.get('lockScalingX'), lockScalingY = target.get('lockScalingY'); @@ -479,8 +475,8 @@ if (lockScalingX && lockScalingY) return; // Get the constraint point - var constraintPosition = target.translateToOriginPoint(target.getCenterPoint(), t.originX, t.originY); - var localMouse = target.toLocalPoint(new fabric.Point(x - offset.left, y - offset.top), t.originX, t.originY); + var constraintPosition = target.translateToOriginPoint(target.getCenterPoint(), t.originX, t.originY), + localMouse = target.toLocalPoint(new fabric.Point(x, y), t.originX, t.originY); this._setLocalMouse(localMouse, t); @@ -527,9 +523,8 @@ */ _scaleObjectEqually: function(localMouse, target, transform) { - var dist = localMouse.y + localMouse.x; - - var lastDist = (target.height + (target.strokeWidth)) * transform.original.scaleY + + var dist = localMouse.y + localMouse.x, + lastDist = (target.height + (target.strokeWidth)) * transform.original.scaleY + (target.width + (target.strokeWidth)) * transform.original.scaleX; // We use transform.scaleX/Y instead of target.scaleX/Y @@ -626,13 +621,12 @@ */ _rotateObject: function (x, y) { - var t = this._currentTransform, - o = this._offset; + var t = this._currentTransform; if (t.target.get('lockRotation')) return; - var lastAngle = atan2(t.ey - t.top - o.top, t.ex - t.left - o.left), - curAngle = atan2(y - t.top - o.top, x - t.left - o.left), + var lastAngle = atan2(t.ey - t.top, t.ex - t.left), + curAngle = atan2(y - t.top, x - t.left), angle = radiansToDegrees(curAngle - lastAngle + t.theta); // normalize angle to positive value @@ -685,15 +679,15 @@ // selection border if (this.selectionDashArray.length > 1) { - var px = groupSelector.ex + STROKE_OFFSET - ((left > 0) ? 0: aleft); - var py = groupSelector.ey + STROKE_OFFSET - ((top > 0) ? 0: atop); + var px = groupSelector.ex + STROKE_OFFSET - ((left > 0) ? 0: aleft), + py = groupSelector.ey + STROKE_OFFSET - ((top > 0) ? 0: atop); ctx.beginPath(); - fabric.util.drawDashedLine(ctx, px, py, px+aleft, py, this.selectionDashArray); - fabric.util.drawDashedLine(ctx, px, py+atop-1, px+aleft, py+atop-1, this.selectionDashArray); - fabric.util.drawDashedLine(ctx, px, py, px, py+atop, this.selectionDashArray); - fabric.util.drawDashedLine(ctx, px+aleft-1, py, px+aleft-1, py+atop, this.selectionDashArray); + fabric.util.drawDashedLine(ctx, px, py, px + aleft, py, this.selectionDashArray); + fabric.util.drawDashedLine(ctx, px, py + atop - 1, px + aleft, py + atop - 1, this.selectionDashArray); + fabric.util.drawDashedLine(ctx, px, py, px, py + atop, this.selectionDashArray); + fabric.util.drawDashedLine(ctx, px + aleft - 1, py, px + aleft - 1, py + atop, this.selectionDashArray); ctx.closePath(); ctx.stroke(); @@ -717,7 +711,7 @@ this.lastRenderedObjectWithControlsAboveOverlay && this.lastRenderedObjectWithControlsAboveOverlay.visible && this.containsPoint(e, this.lastRenderedObjectWithControlsAboveOverlay) && - this.lastRenderedObjectWithControlsAboveOverlay._findTargetCorner(e, this._offset)); + this.lastRenderedObjectWithControlsAboveOverlay._findTargetCorner(this.getPointer(e, true))); }, /** @@ -735,10 +729,56 @@ // first check current group (if one exists) var activeGroup = this.getActiveGroup(); if (activeGroup && !skipGroup && this.containsPoint(e, activeGroup)) { + console.log('AG', activeGroup); return activeGroup; } - return this._searchPossibleTargets(e); + var target = this._searchPossibleTargets(e); + this._fireOverOutEvents(target); + + return target; + }, + + /** + * @private + */ + _fireOverOutEvents: function(target) { + if (target) { + if (this._hoveredTarget !== target) { + this.fire('mouse:over', { target: target }); + target.fire('mouseover'); + if (this._hoveredTarget) { + this.fire('mouse:out', { target: this._hoveredTarget }); + this._hoveredTarget.fire('mouseout'); + } + this._hoveredTarget = target; + } + } + else if (this._hoveredTarget) { + this.fire('mouse:out', { target: this._hoveredTarget }); + this._hoveredTarget.fire('mouseout'); + this._hoveredTarget = null; + } + }, + + /** + * @private + */ + _checkTarget: function(e, obj, pointer) { + if (obj && + obj.visible && + obj.evented && + this.containsPoint(e, obj)){ + if ((this.perPixelTargetFind || obj.perPixelTargetFind) && !obj.isEditing) { + var isTransparent = this.isTargetTransparent(obj, pointer.x, pointer.y); + if (!isTransparent) { + return true; + } + } + else { + return true; + } + } }, /** @@ -747,33 +787,15 @@ _searchPossibleTargets: function(e) { // Cache all targets where their bounding box contains point. - var possibleTargets = [], - target, + var target, pointer = this.getPointer(e, true); - for (var i = this._objects.length; i--; ) { - if (this._objects[i] && - this._objects[i].visible && - this._objects[i].evented && - this.containsPoint(e, this._objects[i])) { + var i = this._objects.length; - if (this.perPixelTargetFind || this._objects[i].perPixelTargetFind) { - possibleTargets[possibleTargets.length] = this._objects[i]; - } - else { - target = this._objects[i]; - this.relatedTarget = target; - break; - } - } - } - - for (var j = 0, len = possibleTargets.length; j < len; j++) { - pointer = this.getPointer(e, true); - var isTransparent = this.isTargetTransparent(possibleTargets[j], pointer.x, pointer.y); - if (!isTransparent) { - target = possibleTargets[j]; - this.relatedTarget = target; + while (i--) { + if (this._checkTarget(e, this._objects[i], pointer)){ + this.relatedTarget = this._objects[i]; + target = this._objects[i]; break; } } @@ -790,7 +812,12 @@ if (!upperCanvasEl) { upperCanvasEl = this.upperCanvasEl; } - var pointer = getPointer(e, upperCanvasEl); + var pointer = getPointer(e, upperCanvasEl), + bounds = upperCanvasEl.getBoundingClientRect(), + cssScale; + + pointer.x = pointer.x - this._offset.left; + pointer.y = pointer.y - this._offset.top; if (!ignoreZoom) { pointer = fabric.util.transformPoint( pointer, @@ -798,9 +825,19 @@ ); } + if (bounds.width === 0 || bounds.height === 0) { + // If bounds are not available (i.e. not visible), do not apply scale. + cssScale = { width: 1, height: 1 }; + } + else { + cssScale = { + width: upperCanvasEl.width / bounds.width, + height: upperCanvasEl.height / bounds.height + }; + } return { - x: pointer.x - this._offset.left, - y: pointer.y - this._offset.top + x: pointer.x * cssScale.width, + y: pointer.y * cssScale.height }; }, diff --git a/src/color.class.js b/src/color.class.js index 5d7490db..35393c4d 100644 --- a/src/color.class.js +++ b/src/color.class.js @@ -1,6 +1,6 @@ (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }); @@ -43,6 +43,11 @@ color = Color.colorNameMap[color]; } + if (color === 'transparent') { + this.setSource([255,255,255,0]); + return; + } + source = Color.sourceFromHex(color); if (!source) { @@ -161,15 +166,15 @@ * @return {String} ex: FF5555 */ toHex: function() { - var source = this.getSource(); + var source = this.getSource(), r, g, b; - var r = source[0].toString(16); + r = source[0].toString(16); r = (r.length === 1) ? ('0' + r) : r; - var g = source[1].toString(16); + g = source[1].toString(16); g = (g.length === 1) ? ('0' + g) : g; - var b = source[2].toString(16); + b = source[2].toString(16); b = (b.length === 1) ? ('0' + b) : b; return r.toUpperCase() + g.toUpperCase() + b.toUpperCase(); @@ -256,7 +261,7 @@ * @field * @memberOf fabric.Color */ - fabric.Color.reRGBa = /^rgba?\(\s*(\d{1,3}\%?)\s*,\s*(\d{1,3}\%?)\s*,\s*(\d{1,3}\%?)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/; + fabric.Color.reRGBa = /^rgba?\(\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/; /** * Regex matching color in HSL or HSLA formats (ex: hsl(200, 80%, 10%), hsla(300, 50%, 80%, 0.5), hsla( 300 , 50% , 80% , 0.5 )) @@ -282,23 +287,23 @@ * @see: http://www.w3.org/TR/CSS2/syndata.html#color-units */ fabric.Color.colorNameMap = { - 'aqua': '#00FFFF', - 'black': '#000000', - 'blue': '#0000FF', - 'fuchsia': '#FF00FF', - 'gray': '#808080', - 'green': '#008000', - 'lime': '#00FF00', - 'maroon': '#800000', - 'navy': '#000080', - 'olive': '#808000', - 'orange': '#FFA500', - 'purple': '#800080', - 'red': '#FF0000', - 'silver': '#C0C0C0', - 'teal': '#008080', - 'white': '#FFFFFF', - 'yellow': '#FFFF00' + aqua: '#00FFFF', + black: '#000000', + blue: '#0000FF', + fuchsia: '#FF00FF', + gray: '#808080', + green: '#008000', + lime: '#00FF00', + maroon: '#800000', + navy: '#000080', + olive: '#808000', + orange: '#FFA500', + purple: '#800080', + red: '#FF0000', + silver: '#C0C0C0', + teal: '#008080', + white: '#FFFFFF', + yellow: '#FFFF00' }; /** @@ -309,11 +314,21 @@ * @return {Number} */ function hue2rgb(p, q, t){ - if (t < 0) t += 1; - if (t > 1) t -= 1; - if (t < 1/6) return p + (q - p) * 6 * t; - if (t < 1/2) return q; - if (t < 2/3) return p + (q - p) * (2/3 - t) * 6; + if (t < 0) { + t += 1; + } + if (t > 1) { + t -= 1; + } + if (t < 1/6) { + return p + (q - p) * 6 * t; + } + if (t < 1/2) { + return q; + } + if (t < 2/3) { + return p + (q - p) * (2/3 - t) * 6; + } return p; } @@ -390,8 +405,8 @@ r = g = b = l; } else { - var q = l <= 0.5 ? l * (s + 1) : l + s - l * s; - var p = l * 2 - q; + var q = l <= 0.5 ? l * (s + 1) : l + s - l * s, + p = l * 2 - q; r = hue2rgb(p, q, h + 1/3); g = hue2rgb(p, q, h); diff --git a/src/elements_parser.js b/src/elements_parser.js index caf548ea..46f67580 100644 --- a/src/elements_parser.js +++ b/src/elements_parser.js @@ -1,67 +1,69 @@ -fabric.ElementsParser = { +fabric.ElementsParser = function(elements, callback, options, reviver) { + this.elements = elements; + this.callback = callback; + this.options = options; + this.reviver = reviver; +}; - parse: function(elements, callback, options, reviver) { +fabric.ElementsParser.prototype.parse = function() { + this.instances = new Array(this.elements.length); + this.numElements = this.elements.length; - this.elements = elements; - this.callback = callback; - this.options = options; - this.reviver = reviver; + this.createObjects(); +}; - 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); - } +fabric.ElementsParser.prototype.createObjects = function() { + for (var i = 0, len = this.elements.length; i < len; i++) { + (function(_this, i) { + setTimeout(function() { + _this.createObject(_this.elements[i], i); + }, 0); + })(this, i); + } +}; + +fabric.ElementsParser.prototype.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(); + } +}; + +fabric.ElementsParser.prototype._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(); + } +}; + +fabric.ElementsParser.prototype.createCallback = function(index, el) { + var _this = this; + return function(obj) { + _this.reviver && _this.reviver(el, obj); + _this.instances.splice(index, 0, obj); + _this.checkIfDone(); + }; +}; + +fabric.ElementsParser.prototype.checkIfDone = function() { + if (--this.numElements === 0) { + this.instances = this.instances.filter(function(el) { + return el != null; + }); + fabric.resolveGradients(this.instances); + this.callback(this.instances); } }; diff --git a/src/filters/base_filter.class.js b/src/filters/base_filter.class.js index 7ada290d..b5e1340c 100644 --- a/src/filters/base_filter.class.js +++ b/src/filters/base_filter.class.js @@ -6,7 +6,6 @@ */ fabric.Image.filters = fabric.Image.filters || { }; - /** * Root filter class from which all filter classes inherit from * @class fabric.Image.filters.BaseFilter diff --git a/src/filters/brightness_filter.class.js b/src/filters/brightness_filter.class.js index 4fe03d5d..93e2dcea 100644 --- a/src/filters/brightness_filter.class.js +++ b/src/filters/brightness_filter.class.js @@ -1,6 +1,6 @@ (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend; @@ -32,11 +32,11 @@ * Constructor * @memberOf fabric.Image.filters.Brightness.prototype * @param {Object} [options] Options object - * @param {Number} [options.brightness=100] Value to brighten the image up (0..255) + * @param {Number} [options.brightness=0] Value to brighten the image up (0..255) */ initialize: function(options) { options = options || { }; - this.brightness = options.brightness || 100; + this.brightness = options.brightness || 0; }, /** diff --git a/src/filters/convolute_filter.class.js b/src/filters/convolute_filter.class.js index a63e7175..3aef4ab3 100644 --- a/src/filters/convolute_filter.class.js +++ b/src/filters/convolute_filter.class.js @@ -1,6 +1,6 @@ (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend; @@ -66,9 +66,11 @@ options = options || { }; this.opaque = options.opaque; - this.matrix = options.matrix || [ 0, 0, 0, + this.matrix = options.matrix || [ + 0, 0, 0, 0, 1, 0, - 0, 0, 0 ]; + 0, 0, 0 + ]; var canvasEl = fabric.util.createCanvasElement(); this.tmpCtx = canvasEl.getContext('2d'); @@ -95,49 +97,49 @@ halfSide = Math.floor(side/2), src = pixels.data, sw = pixels.width, - sh = pixels.height; + sh = pixels.height, - // pad output by the convolution matrix - var w = sw; - var h = sh; - var output = this._createImageData(w, h); + // pad output by the convolution matrix + w = sw, + h = sh, + output = this._createImageData(w, h), - var dst = output.data; + dst = output.data, - // go through the destination image pixels - var alphaFac = this.opaque ? 1 : 0; + // go through the destination image pixels + alphaFac = this.opaque ? 1 : 0; - for (var y=0; y sh || scx < 0 || scx > sw) continue; - var srcOff = (scy*sw+scx)*4; - var wt = weights[cy*side+cx]; + var srcOff = (scy * sw + scx) * 4, + wt = weights[cy * side + cx]; r += src[srcOff] * wt; - g += src[srcOff+1] * wt; - b += src[srcOff+2] * wt; - a += src[srcOff+3] * wt; + g += src[srcOff + 1] * wt; + b += src[srcOff + 2] * wt; + a += src[srcOff + 3] * wt; } } dst[dstOff] = r; - dst[dstOff+1] = g; - dst[dstOff+2] = b; - dst[dstOff+3] = a + alphaFac*(255-a); + dst[dstOff + 1] = g; + dst[dstOff + 2] = b; + dst[dstOff + 3] = a + alphaFac * (255 - a); } } @@ -163,7 +165,7 @@ * @return {fabric.Image.filters.Convolute} Instance of fabric.Image.filters.Convolute */ fabric.Image.filters.Convolute.fromObject = function(object) { - return new fabric.Image.filters.Convolute(object); + return new fabric.Image.filters.Convolute(object); }; })(typeof exports !== 'undefined' ? exports : this); diff --git a/src/filters/gradienttransparency_filter.class.js b/src/filters/gradienttransparency_filter.class.js index 9f7ea01f..fc83e322 100644 --- a/src/filters/gradienttransparency_filter.class.js +++ b/src/filters/gradienttransparency_filter.class.js @@ -1,6 +1,6 @@ (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend; diff --git a/src/filters/grayscale_filter.class.js b/src/filters/grayscale_filter.class.js index 34e61e76..1057da89 100644 --- a/src/filters/grayscale_filter.class.js +++ b/src/filters/grayscale_filter.class.js @@ -1,6 +1,6 @@ (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }); diff --git a/src/filters/invert_filter.class.js b/src/filters/invert_filter.class.js index 1478863b..6b472ef1 100644 --- a/src/filters/invert_filter.class.js +++ b/src/filters/invert_filter.class.js @@ -1,6 +1,6 @@ (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }); diff --git a/src/filters/mask_filter.class.js b/src/filters/mask_filter.class.js index f432cbed..7b7c457c 100644 --- a/src/filters/mask_filter.class.js +++ b/src/filters/mask_filter.class.js @@ -1,6 +1,6 @@ (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend; diff --git a/src/filters/multiply_filter.class.js b/src/filters/multiply_filter.class.js new file mode 100644 index 00000000..3361ff29 --- /dev/null +++ b/src/filters/multiply_filter.class.js @@ -0,0 +1,92 @@ +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend; + + /** + * Multiply filter class + * Adapted from http://www.laurenscorijn.com/articles/colormath-basics + * @class fabric.Image.filters.Multiply + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @example Multiply filter with hex color + * var filter = new fabric.Image.filters.Multiply({ + * color: '#F0F' + * }); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + * @example Multiply filter with rgb color + * var filter = new fabric.Image.filters.Multiply({ + * color: 'rgb(53, 21, 176)' + * }); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + */ + fabric.Image.filters.Multiply = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Multiply.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Multiply', + + /** + * Constructor + * @memberOf fabric.Image.filters.Multiply.prototype + * @param {Object} [options] Options object + * @param {String} [options.color=#000000] Color to multiply the image pixels with + */ + initialize: function(options) { + options = options || { }; + + this.color = options.color || '#000000'; + }, + + /** + * Applies filter to canvas element + * @param {Object} canvasEl Canvas element to apply filter to + */ + applyTo: function(canvasEl) { + var context = canvasEl.getContext('2d'), + imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), + data = imageData.data, + iLen = data.length, i, + source; + + source = new fabric.Color(this.color).getSource(); + + for (i = 0; i < iLen; i+=4) { + data[i] *= source[0]/255; + data[i + 1] *= source[1]/255; + data[i + 2] *= source[2]/255; + + } + + context.putImageData(imageData, 0, 0); + }, + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + return extend(this.callSuper('toObject'), { + color: this.color + }); + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @return {fabric.Image.filters.Multiply} Instance of fabric.Image.filters.Multiply + */ + fabric.Image.filters.Multiply.fromObject = function(object) { + return new fabric.Image.filters.Multiply(object); + }; + +})(typeof exports !== 'undefined' ? exports : this); diff --git a/src/filters/noise_filter.class.js b/src/filters/noise_filter.class.js index b6900554..3fd6e9b6 100644 --- a/src/filters/noise_filter.class.js +++ b/src/filters/noise_filter.class.js @@ -1,6 +1,6 @@ (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend; @@ -32,11 +32,11 @@ * Constructor * @memberOf fabric.Image.filters.Noise.prototype * @param {Object} [options] Options object - * @param {Number} [options.noise=100] Noise value + * @param {Number} [options.noise=0] Noise value */ initialize: function(options) { options = options || { }; - this.noise = options.noise || 100; + this.noise = options.noise || 0; }, /** diff --git a/src/filters/pixelate_filter.class.js b/src/filters/pixelate_filter.class.js index 1c31420b..df328902 100644 --- a/src/filters/pixelate_filter.class.js +++ b/src/filters/pixelate_filter.class.js @@ -1,6 +1,6 @@ (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend; @@ -57,9 +57,9 @@ index = (i * 4) * jLen + (j * 4); r = data[index]; - g = data[index+1]; - b = data[index+2]; - a = data[index+3]; + g = data[index + 1]; + b = data[index + 2]; + a = data[index + 3]; /* blocksize: 4 diff --git a/src/filters/removewhite_filter.class.js b/src/filters/removewhite_filter.class.js index 1d33d994..44cd212c 100644 --- a/src/filters/removewhite_filter.class.js +++ b/src/filters/removewhite_filter.class.js @@ -1,6 +1,6 @@ (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend; @@ -58,17 +58,17 @@ for (var i = 0, len = data.length; i < len; i += 4) { r = data[i]; - g = data[i+1]; - b = data[i+2]; + g = data[i + 1]; + b = data[i + 2]; if (r > limit && g > limit && b > limit && - abs(r-g) < distance && - abs(r-b) < distance && - abs(g-b) < distance + abs(r - g) < distance && + abs(r - b) < distance && + abs(g - b) < distance ) { - data[i+3] = 1; + data[i + 3] = 1; } } diff --git a/src/filters/sepia2_filter.class.js b/src/filters/sepia2_filter.class.js index c39ea090..5e639aba 100644 --- a/src/filters/sepia2_filter.class.js +++ b/src/filters/sepia2_filter.class.js @@ -1,6 +1,6 @@ (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }); diff --git a/src/filters/sepia_filter.class.js b/src/filters/sepia_filter.class.js index 663d5b3c..3751fab1 100644 --- a/src/filters/sepia_filter.class.js +++ b/src/filters/sepia_filter.class.js @@ -1,6 +1,6 @@ (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }); diff --git a/src/filters/tint_filter.class.js b/src/filters/tint_filter.class.js index 6a1c1093..ab9d7e94 100644 --- a/src/filters/tint_filter.class.js +++ b/src/filters/tint_filter.class.js @@ -1,6 +1,6 @@ (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend; diff --git a/src/gradient.class.js b/src/gradient.class.js index 0f0210f3..6e58c16e 100644 --- a/src/gradient.class.js +++ b/src/gradient.class.js @@ -12,7 +12,7 @@ if (style) { var keyValuePairs = style.split(/\s*;\s*/); - if (keyValuePairs[keyValuePairs.length-1] === '') { + if (keyValuePairs[keyValuePairs.length - 1] === '') { keyValuePairs.pop(); } @@ -115,7 +115,11 @@ addColorStop: function(colorStop) { for (var position in colorStop) { var color = new fabric.Color(colorStop[position]); - this.colorStops.push({offset: position, color: color.toRgb(), opacity: color.getAlpha()}); + this.colorStops.push({ + offset: position, + color: color.toRgb(), + opacity: color.getAlpha() + }); } return this; }, diff --git a/src/intersection.class.js b/src/intersection.class.js index f76e5fb2..935fa659 100644 --- a/src/intersection.class.js +++ b/src/intersection.class.js @@ -1,161 +1,160 @@ -(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, + uaT = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x), + ubT = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x), + uB = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y); + if (uB !== 0) { + var ua = uaT / uB, + ub = ubT / uB; + 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 (uaT === 0 || ubT === 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); diff --git a/src/mixins/animation.mixin.js b/src/mixins/animation.mixin.js index 456fa0b7..b937d49e 100644 --- a/src/mixins/animation.mixin.js +++ b/src/mixins/animation.mixin.js @@ -142,7 +142,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot for (prop in arguments[0]) { propsToAnimate.push(prop); } - for (var i = 0, len = propsToAnimate.length; i 1) { + this.setWidth(scaledWidth).setHeight(scaledHeight); + } ctx.scale(multiplier, multiplier); if (cropping.left) { @@ -134,9 +135,15 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati if (cropping.width) { cropping.width *= multiplier; } + else if (multiplier < 1) { + cropping.width = scaledWidth; + } if (cropping.height) { cropping.height *= multiplier; } + else if (multiplier < 1) { + cropping.height = scaledHeight; + } if (activeGroup) { // not removing group due to complications with restoring it with correct state afterwords diff --git a/src/mixins/canvas_events.mixin.js b/src/mixins/canvas_events.mixin.js index b97842d2..0d008997 100644 --- a/src/mixins/canvas_events.mixin.js +++ b/src/mixins/canvas_events.mixin.js @@ -1,31 +1,35 @@ (function(){ - var cursorMap = [ - 'n-resize', - 'ne-resize', - 'e-resize', - 'se-resize', - 's-resize', - 'sw-resize', - 'w-resize', - 'nw-resize' - ], - cursorOffset = { - 'mt': 0, // n - 'tr': 1, // ne - 'mr': 2, // e - 'br': 3, // se - 'mb': 4, // s - 'bl': 5, // sw - 'ml': 6, // w - 'tl': 7 // nw + var cursorOffset = { + mt: 0, // n + tr: 1, // ne + mr: 2, // e + br: 3, // se + mb: 4, // s + bl: 5, // sw + ml: 6, // w + tl: 7 // nw }, addListener = fabric.util.addListener, - removeListener = fabric.util.removeListener, - getPointer = fabric.util.getPointer; + removeListener = fabric.util.removeListener; fabric.util.object.extend(fabric.Canvas.prototype, /** @lends fabric.Canvas.prototype */ { + /** + * Map of cursor style values for each of the object controls + * @private + */ + cursorMap: [ + 'n-resize', + 'ne-resize', + 'e-resize', + 'se-resize', + 's-resize', + 'sw-resize', + 'w-resize', + 'nw-resize' + ], + /** * Adds mouse listeners to canvas * @private @@ -141,14 +145,20 @@ _onMouseDown: function (e) { this.__onMouseDown(e); - addListener(fabric.document, 'mouseup', this._onMouseUp); addListener(fabric.document, 'touchend', this._onMouseUp); - - addListener(fabric.document, 'mousemove', this._onMouseMove); addListener(fabric.document, 'touchmove', this._onMouseMove); removeListener(this.upperCanvasEl, 'mousemove', this._onMouseMove); removeListener(this.upperCanvasEl, 'touchmove', this._onMouseMove); + + if (e.type === 'touchstart') { + // Unbind mousedown to prevent double triggers from touch devices + removeListener(this.upperCanvasEl, 'mousedown', this._onMouseDown); + } + else { + addListener(fabric.document, 'mouseup', this._onMouseUp); + addListener(fabric.document, 'mousemove', this._onMouseMove); + } }, /** @@ -166,6 +176,15 @@ addListener(this.upperCanvasEl, 'mousemove', this._onMouseMove); addListener(this.upperCanvasEl, 'touchmove', this._onMouseMove); + + if (e.type === 'touchend') { + // Wait 400ms before rebinding mousedown to prevent double triggers + // from touch devices + var _this = this; + setTimeout(function() { + addListener(_this.upperCanvasEl, 'mousedown', _this._onMouseDown); + }, 400); + } }, /** @@ -264,8 +283,8 @@ */ _finalizeCurrentTransform: function() { - var transform = this._currentTransform; - var target = transform.target; + var transform = this._currentTransform, + target = transform.target; if (target._scaling) { target._scaling = false; @@ -407,7 +426,7 @@ this.stateful && target.saveState(); // determine if it's a drag or rotate case - if ((corner = target._findTargetCorner(e, this._offset))) { + if ((corner = target._findTargetCorner(this.getPointer(e)))) { this.onBeforeScaleRotate(target); } @@ -529,11 +548,11 @@ * @param {Event} e Event fired on mousemove */ _transformObject: function(e) { - var pointer = fabric.util.transformPoint( - getPointer(e, this.upperCanvasEl), + fabric.util.getPointer(e, this.upperCanvasEl), fabric.util.invertTransform(this.viewportTransform) ), + pointer = this.getPointer(e), transform = this._currentTransform; transform.reset = false, @@ -581,7 +600,7 @@ * @private */ _fire: function(eventName, target, e) { - this.fire('object:' + eventName, { target: target, e: e}); + this.fire('object:' + eventName, { target: target, e: e }); target.fire(eventName, { e: e }); }, @@ -638,11 +657,11 @@ return false; } else { - var activeGroup = this.getActiveGroup(); - // only show proper corner when group selection is not active - var corner = target._findTargetCorner + var activeGroup = this.getActiveGroup(), + // only show proper corner when group selection is not active + corner = target._findTargetCorner && (!activeGroup || !activeGroup.contains(target)) - && target._findTargetCorner(e, this._offset); + && target._findTargetCorner(this.getPointer(e, true)); if (!corner) { style.cursor = target.hoverCursor || this.hoverCursor; @@ -685,7 +704,7 @@ // normalize n to be from 0 to 7 n %= 8; - return cursorMap[n]; + return this.cursorMap[n]; } }); })(); diff --git a/src/mixins/canvas_gestures.mixin.js b/src/mixins/canvas_gestures.mixin.js index f615d293..fdb18853 100644 --- a/src/mixins/canvas_gestures.mixin.js +++ b/src/mixins/canvas_gestures.mixin.js @@ -14,7 +14,7 @@ */ __onTransformGesture: function(e, self) { - if (this.isDrawingMode || e.touches.length !== 2 || 'gesture' !== self.gesture) { + if (this.isDrawingMode || !e.touches || e.touches.length !== 2 || 'gesture' !== self.gesture) { return; } @@ -25,7 +25,7 @@ this._scaleObjectBy(self.scale); } - this.fire('touch:gesture', {target: target, e: e, self: self}); + this.fire('touch:gesture', { target: target, e: e, self: self }); }, /** @@ -35,7 +35,7 @@ * @param self Event proxy object by Event.js */ __onDrag: function(e, self) { - this.fire('touch:drag', {e: e, self: self}); + this.fire('touch:drag', { e: e, self: self }); }, /** @@ -45,7 +45,7 @@ * @param self Event proxy object by Event.js */ __onOrientationChange: function(e, self) { - this.fire('touch:orientation', {e: e, self: self}); + this.fire('touch:orientation', { e: e, self: self }); }, /** @@ -55,7 +55,7 @@ * @param self Event proxy object by Event.js */ __onShake: function(e, self) { - this.fire('touch:shake', {e: e, self: self}); + this.fire('touch:shake', { e: e, self: self }); }, /** @@ -66,10 +66,9 @@ */ _scaleObjectBy: function(s, by) { var t = this._currentTransform, - target = t.target; - - var lockScalingX = target.get('lockScalingX'), - lockScalingY = target.get('lockScalingY'); + target = t.target, + lockScalingX = target.get('lockScalingX'), + lockScalingY = target.get('lockScalingY'); if (lockScalingX && lockScalingY) return; diff --git a/src/mixins/canvas_grouping.mixin.js b/src/mixins/canvas_grouping.mixin.js index 8fb945a2..b277ba94 100644 --- a/src/mixins/canvas_grouping.mixin.js +++ b/src/mixins/canvas_grouping.mixin.js @@ -99,13 +99,11 @@ */ _createGroup: function(target) { - var objects = this.getObjects(); - - var isActiveLower = objects.indexOf(this._activeObject) < objects.indexOf(target); - - var groupObjects = isActiveLower - ? [ this._activeObject, target ] - : [ target, this._activeObject ]; + var objects = this.getObjects(), + isActiveLower = objects.indexOf(this._activeObject) < objects.indexOf(target), + groupObjects = isActiveLower + ? [ this._activeObject, target ] + : [ target, this._activeObject ]; return new fabric.Group(groupObjects, { originX: 'center', diff --git a/src/mixins/canvas_serialization.mixin.js b/src/mixins/canvas_serialization.mixin.js index 86bb3ad8..907c87a5 100644 --- a/src/mixins/canvas_serialization.mixin.js +++ b/src/mixins/canvas_serialization.mixin.js @@ -129,8 +129,9 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati _enlivenObjects: function (objects, callback, reviver) { var _this = this; - if (objects.length === 0) { + if (!objects || objects.length === 0) { callback && callback(); + return; } var renderOnAddRemove = this.renderOnAddRemove; diff --git a/src/mixins/collection.mixin.js b/src/mixins/collection.mixin.js index b387e7f9..bf4c686a 100644 --- a/src/mixins/collection.mixin.js +++ b/src/mixins/collection.mixin.js @@ -6,12 +6,12 @@ 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 + * @param {...fabric.Object} object Zero or more fabric instances * @return {Self} thisArg */ add: function () { this._objects.push.apply(this._objects, arguments); - for (var i = arguments.length; i--; ) { + for (var i = 0, length = arguments.length; i < length; i++) { this._onObjectAdded(arguments[i]); } this.renderOnAddRemove && this.renderAll(); @@ -25,6 +25,7 @@ fabric.Collection = { * @param {Number} index Index to insert object at * @param {Boolean} nonSplicing When `true`, no splicing (shifting) of objects occurs * @return {Self} thisArg + * @chainable */ insertAt: function (object, index, nonSplicing) { var objects = this.getObjects(); @@ -40,22 +41,27 @@ fabric.Collection = { }, /** - * Removes an object from a collection, then renders canvas (if `renderOnAddRemove` is not `false`) - * @param {Object} object Object to remove + * Removes objects from a collection, then renders canvas (if `renderOnAddRemove` is not `false`) + * @param {...fabric.Object} object Zero or more fabric instances * @return {Self} thisArg + * @chainable */ - remove: function(object) { + remove: function() { var objects = this.getObjects(), - index = objects.indexOf(object); + index; - // only call onObjectRemoved if an object was actually removed - if (index !== -1) { - objects.splice(index, 1); - this._onObjectRemoved(object); + for (var i = 0, length = arguments.length; i < length; i++) { + index = objects.indexOf(arguments[i]); + + // only call onObjectRemoved if an object was actually removed + if (index !== -1) { + objects.splice(index, 1); + this._onObjectRemoved(arguments[i]); + } } this.renderOnAddRemove && this.renderAll(); - return object; + return this; }, /** diff --git a/src/mixins/itext_behavior.mixin.js b/src/mixins/itext_behavior.mixin.js index 6efe334f..32de02b5 100644 --- a/src/mixins/itext_behavior.mixin.js +++ b/src/mixins/itext_behavior.mixin.js @@ -8,10 +8,9 @@ * Initializes all the interactive behavior of IText */ initBehavior: function() { - this.initKeyHandlers(); + this.initAddedHandler(); this.initCursorSelectionHandlers(); this.initDoubleClickSimulation(); - this.initHiddenTextarea(); }, /** @@ -24,10 +23,17 @@ setTimeout(function() { _this.selected = true; }, 100); + }); + }, - if (!this._hasCanvasHandlers) { + /** + * Initializes "added" event handler + */ + initAddedHandler: function() { + this.on('added', function() { + if (this.canvas && !this.canvas._hasITextHandlers) { + this.canvas._hasITextHandlers = true; this._initCanvasHandlers(); - this._hasCanvasHandlers = true; } }); }, @@ -36,25 +42,18 @@ * @private */ _initCanvasHandlers: function() { - var _this = this; - - this.canvas.on('selection:cleared', function(options) { - - // do not exit editing if event fired - // when clicking on an object again (in editing mode) - if (options.e && _this.canvas.containsPoint(options.e, _this)) return; - - _this.exitEditing(); + this.canvas.on('selection:cleared', function() { + fabric.IText.prototype.exitEditingOnOthers.call(); }); this.canvas.on('mouse:up', function() { - this.getObjects('i-text').forEach(function(obj) { + fabric.IText.instances.forEach(function(obj) { obj.__isMousedown = false; }); }); - this.canvas.on('object:selected', function() { - fabric.IText.prototype.exitEditingOnOthers.call(this); + this.canvas.on('object:selected', function(options) { + fabric.IText.prototype.exitEditingOnOthers.call(options.target); }); }, @@ -114,15 +113,23 @@ /** * Initializes delayed cursor */ - initDelayedCursor: function() { - var _this = this; + initDelayedCursor: function(restart) { + var _this = this, + delay = restart ? 0 : this.cursorDelay; + + if (restart) { + this._abortCursorAnimation = true; + clearTimeout(this._cursorTimeout1); + this._currentCursorOpacity = 1; + this.canvas && this.canvas.renderAll(); + } if (this._cursorTimeout2) { clearTimeout(this._cursorTimeout2); } this._cursorTimeout2 = setTimeout(function() { _this._abortCursorAnimation = false; _this._tick(); - }, this.cursorDelay); + }, delay); }, /** @@ -149,6 +156,7 @@ selectAll: function() { this.selectionStart = 0; this.selectionEnd = this.text.length; + this.canvas && this.canvas.fire('text:selection:changed', { target: this }); }, /** @@ -240,8 +248,9 @@ * @return {Number} Number of newlines in selected text */ getNumNewLinesInSelectedText: function() { - var selectedText = this.getSelectedText(); - var numNewLines = 0; + var selectedText = this.getSelectedText(), + numNewLines = 0; + for (var i = 0, chars = selectedText.split(''), len = chars.length; i < len; i++) { if (chars[i] === '\n') { numNewLines++; @@ -256,9 +265,9 @@ * @param {Number} direction: 1 or -1 */ searchWordBoundary: function(selectionStart, direction) { - var index = selectionStart; - var _char = this.text.charAt(index); - var reNonWord = /[ \n\.,;!\?\-]/; + var index = this._reSpace.test(this.text.charAt(selectionStart)) ? selectionStart - 1 : selectionStart, + _char = this.text.charAt(index), + reNonWord = /[ \n\.,;!\?\-]/; while (!reNonWord.test(_char) && index > 0 && index < this.text.length) { index += direction; @@ -275,11 +284,12 @@ * @param {Number} selectionStart Index of a character */ selectWord: function(selectionStart) { - var newSelectionStart = this.searchWordBoundary(selectionStart, -1); /* search backwards */ - var newSelectionEnd = this.searchWordBoundary(selectionStart, 1); /* search forward */ + var newSelectionStart = this.searchWordBoundary(selectionStart, -1), /* search backwards */ + newSelectionEnd = this.searchWordBoundary(selectionStart, 1); /* search forward */ this.setSelectionStart(newSelectionStart); this.setSelectionEnd(newSelectionEnd); + this.initDelayedCursor(true); }, /** @@ -287,11 +297,12 @@ * @param {Number} selectionStart Index of a character */ selectLine: function(selectionStart) { - var newSelectionStart = this.findLineBoundaryLeft(selectionStart); - var newSelectionEnd = this.findLineBoundaryRight(selectionStart); + var newSelectionStart = this.findLineBoundaryLeft(selectionStart), + newSelectionEnd = this.findLineBoundaryRight(selectionStart); this.setSelectionStart(newSelectionStart); this.setSelectionEnd(newSelectionEnd); + this.initDelayedCursor(true); }, /** @@ -306,6 +317,7 @@ this.isEditing = true; + this.initHiddenTextarea(); this._updateTextarea(); this._saveEditingProps(); this._setEditingProps(); @@ -314,14 +326,17 @@ this.canvas && this.canvas.renderAll(); this.fire('editing:entered'); + this.canvas && this.canvas.fire('text:editing:entered', { target: this }); return this; }, exitEditingOnOthers: function() { fabric.IText.instances.forEach(function(obj) { - if (obj === this) return; - obj.exitEditing(); + obj.selected = false; + if (obj.isEditing) { + obj.exitEditing(); + } }, this); }, @@ -349,7 +364,6 @@ this.hiddenTextarea.value = this.text; this.hiddenTextarea.selectionStart = this.selectionStart; - this.hiddenTextarea.focus(); }, /** @@ -397,13 +411,15 @@ this.selectable = true; this.selectionEnd = this.selectionStart; - this.hiddenTextarea && this.hiddenTextarea.blur(); + this.hiddenTextarea && this.canvas && this.hiddenTextarea.parentNode.removeChild(this.hiddenTextarea); + this.hiddenTextarea = null; this.abortCursorAnimation(); this._restoreEditingProps(); this._currentCursorOpacity = 0; this.fire('editing:exited'); + this.canvas && this.canvas.fire('text:editing:exited', { target: this }); return this; }, @@ -430,8 +446,9 @@ var prevIndex = this.get2DCursorLocation(i).charIndex; i--; - var index = this.get2DCursorLocation(i).charIndex; - var isNewline = index > prevIndex; + + var index = this.get2DCursorLocation(i).charIndex, + isNewline = index > prevIndex; if (isNewline) { this.removeStyleObject(isNewline, i + 1); @@ -460,10 +477,10 @@ if (this.selectionStart === this.selectionEnd) { this.insertStyleObjects(_chars, isEndOfLine, this.copiedStyles); } - else if (this.selectionEnd - this.selectionStart > 1) { + // else if (this.selectionEnd - this.selectionStart > 1) { // TODO: replace styles properly - console.log('replacing MORE than 1 char'); - } + // console.log('replacing MORE than 1 char'); + // } this.selectionStart += _chars.length; this.selectionEnd = this.selectionStart; @@ -475,7 +492,8 @@ } this.setCoords(); - this.fire('text:changed'); + this.fire('changed'); + this.canvas && this.canvas.fire('text:changed', { target: this }); }, /** @@ -622,7 +640,9 @@ var textLines = this.text.split(this._reNewline), textOnPreviousLine = textLines[lineIndex - 1], - newCharIndexOnPrevLine = textOnPreviousLine.length; + newCharIndexOnPrevLine = textOnPreviousLine + ? textOnPreviousLine.length + : 0; if (!this.styles[lineIndex - 1]) { this.styles[lineIndex - 1] = { }; diff --git a/src/mixins/itext_click_behavior.mixin.js b/src/mixins/itext_click_behavior.mixin.js index 92aa0f67..adf2bec4 100644 --- a/src/mixins/itext_click_behavior.mixin.js +++ b/src/mixins/itext_click_behavior.mixin.js @@ -10,7 +10,7 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot // for triple click this.__lastLastClickTime = +new Date(); - this.lastPointer = { }; + this.__lastPointer = { }; this.on('mousedown', this.onMouseDown.bind(this)); }, @@ -32,12 +32,13 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot this.__lastLastClickTime = this.__lastClickTime; this.__lastClickTime = this.__newClickTime; this.__lastPointer = newPointer; + this.__lastIsEditing = this.isEditing; }, isDoubleClick: function(newPointer) { return this.__newClickTime - this.__lastClickTime < 500 && this.__lastPointer.x === newPointer.x && - this.__lastPointer.y === newPointer.y; + this.__lastPointer.y === newPointer.y && this.__lastIsEditing; }, isTripleClick: function(newPointer) { @@ -94,9 +95,13 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot this.canvas.wrapperEl.appendChild(this.hiddenTextarea); } - if (this.isEditing) { + if (this.selected) { this.setCursorByClick(options.e); + } + + if (this.isEditing) { this.__selectionStartOnMouseDown = this.selectionStart; + this.initDelayedCursor(true); } }); }, @@ -137,12 +142,13 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot initMouseupHandler: function() { this.on('mouseup', function(options) { this.__isMousedown = false; - if (this._isObjectMoved(options.e)) return; if (this.selected) { this.enterEditing(); + this.initDelayedCursor(true); } + this.selected = true; }); }, @@ -204,10 +210,10 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot height += this._getHeightOfLine(this.ctx, i) * this.scaleY; - var widthOfLine = this._getWidthOfLine(this.ctx, i, textLines); - var lineLeftOffset = this._getLineLeftOffset(widthOfLine); + var widthOfLine = this._getWidthOfLine(this.ctx, i, textLines), + lineLeftOffset = this._getLineLeftOffset(widthOfLine); - width = lineLeftOffset; + width = lineLeftOffset * this.scaleX; if (this.flipX) { // when oject is horizontally flipped we reverse chars @@ -230,6 +236,11 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot return this._getNewSelectionStartFromOffset( mouseOffset, prevWidth, width, charIndex + i, jlen); } + + if (mouseOffset.y < height) { + return this._getNewSelectionStartFromOffset( + mouseOffset, prevWidth, width, charIndex + i, jlen, j); + } } // clicked somewhere after all chars, so set at the end @@ -241,7 +252,7 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot /** * @private */ - _getNewSelectionStartFromOffset: function(mouseOffset, prevWidth, width, index, jlen) { + _getNewSelectionStartFromOffset: function(mouseOffset, prevWidth, width, index, jlen, j) { var distanceBtwLastCharAndCursor = mouseOffset.x - prevWidth, distanceBtwNextCharAndCursor = width - mouseOffset.x, @@ -257,6 +268,10 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot newSelectionStart = this.text.length; } + if (j === jlen) { + newSelectionStart--; + } + return newSelectionStart; } }); diff --git a/src/mixins/itext_key_behavior.mixin.js b/src/mixins/itext_key_behavior.mixin.js index 88945438..60b502e5 100644 --- a/src/mixins/itext_key_behavior.mixin.js +++ b/src/mixins/itext_key_behavior.mixin.js @@ -1,13 +1,5 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ { - /** - * Initializes key handlers - */ - initKeyHandlers: function() { - fabric.util.addListener(fabric.document, 'keydown', this.onKeyDown.bind(this)); - fabric.util.addListener(fabric.document, 'keypress', this.onKeyPress.bind(this)); - }, - /** * Initializes hidden textarea (needed to bring up keyboard in iOS) */ @@ -18,6 +10,14 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot this.hiddenTextarea.style.cssText = 'position: absolute; top: 0; left: -9999px'; fabric.document.body.appendChild(this.hiddenTextarea); + + fabric.util.addListener(this.hiddenTextarea, 'keydown', this.onKeyDown.bind(this)); + fabric.util.addListener(this.hiddenTextarea, 'keypress', this.onKeyPress.bind(this)); + + if (!this._clickHandlerInitialized && this.canvas) { + fabric.util.addListener(this.canvas.upperCanvasEl, 'click', this.onClick.bind(this)); + this._clickHandlerInitialized = true; + } }, /** @@ -43,6 +43,11 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot 88: 'cut' }, + onClick: function() { + // No need to trigger click event here, focus is enough to have the keyboard appear on Android + this.hiddenTextarea && this.hiddenTextarea.focus(); + }, + /** * Handles keyup event * @param {Event} e Event object @@ -149,8 +154,8 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot var widthOfSameLineBeforeCursor = this._getWidthOfLine(this.ctx, cursorLocation.lineIndex, textLines); lineLeftOffset = this._getLineLeftOffset(widthOfSameLineBeforeCursor); - var widthOfCharsOnSameLineBeforeCursor = lineLeftOffset; - var lineIndex = cursorLocation.lineIndex; + var widthOfCharsOnSameLineBeforeCursor = lineLeftOffset, + lineIndex = cursorLocation.lineIndex; for (var i = 0, len = textOnSameLineBeforeCursor.length; i < len; i++) { _char = textOnSameLineBeforeCursor[i]; @@ -168,17 +173,17 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot */ _getIndexOnNextLine: function(cursorLocation, textOnNextLine, widthOfCharsOnSameLineBeforeCursor, textLines) { - var lineIndex = cursorLocation.lineIndex + 1; - var widthOfNextLine = this._getWidthOfLine(this.ctx, lineIndex, textLines); - var lineLeftOffset = this._getLineLeftOffset(widthOfNextLine); - var widthOfCharsOnNextLine = lineLeftOffset; - var indexOnNextLine = 0; - var foundMatch; + var lineIndex = cursorLocation.lineIndex + 1, + widthOfNextLine = this._getWidthOfLine(this.ctx, lineIndex, textLines), + lineLeftOffset = this._getLineLeftOffset(widthOfNextLine), + widthOfCharsOnNextLine = lineLeftOffset, + indexOnNextLine = 0, + foundMatch; for (var j = 0, jlen = textOnNextLine.length; j < jlen; j++) { - var _char = textOnNextLine[j]; - var widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j); + var _char = textOnNextLine[j], + widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j); widthOfCharsOnNextLine += widthOfChar; @@ -186,10 +191,10 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot foundMatch = true; - var leftEdge = widthOfCharsOnNextLine - widthOfChar; - var rightEdge = widthOfCharsOnNextLine; - var offsetFromLeftEdge = Math.abs(leftEdge - widthOfCharsOnSameLineBeforeCursor); - var offsetFromRightEdge = Math.abs(rightEdge - widthOfCharsOnSameLineBeforeCursor); + var leftEdge = widthOfCharsOnNextLine - widthOfChar, + rightEdge = widthOfCharsOnNextLine, + offsetFromLeftEdge = Math.abs(leftEdge - widthOfCharsOnSameLineBeforeCursor), + offsetFromRightEdge = Math.abs(rightEdge - widthOfCharsOnSameLineBeforeCursor); indexOnNextLine = offsetFromRightEdge < offsetFromLeftEdge ? j + 1 : j; @@ -277,13 +282,10 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot textOnPreviousLine = (textBeforeCursor.match(/\n?(.*)\n.*$/) || {})[1] || '', textLines = this.text.split(this._reNewline), _char, - lineLeftOffset; - - var widthOfSameLineBeforeCursor = this._getWidthOfLine(this.ctx, cursorLocation.lineIndex, textLines); - lineLeftOffset = this._getLineLeftOffset(widthOfSameLineBeforeCursor); - - var widthOfCharsOnSameLineBeforeCursor = lineLeftOffset; - var lineIndex = cursorLocation.lineIndex; + widthOfSameLineBeforeCursor = this._getWidthOfLine(this.ctx, cursorLocation.lineIndex, textLines), + lineLeftOffset = this._getLineLeftOffset(widthOfSameLineBeforeCursor), + widthOfCharsOnSameLineBeforeCursor = lineLeftOffset, + lineIndex = cursorLocation.lineIndex; for (var i = 0, len = textOnSameLineBeforeCursor.length; i < len; i++) { _char = textOnSameLineBeforeCursor[i]; @@ -301,17 +303,17 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot */ _getIndexOnPrevLine: function(cursorLocation, textOnPreviousLine, widthOfCharsOnSameLineBeforeCursor, textLines) { - var lineIndex = cursorLocation.lineIndex - 1; - var widthOfPreviousLine = this._getWidthOfLine(this.ctx, lineIndex, textLines); - var lineLeftOffset = this._getLineLeftOffset(widthOfPreviousLine); - var widthOfCharsOnPreviousLine = lineLeftOffset; - var indexOnPrevLine = 0; - var foundMatch; + var lineIndex = cursorLocation.lineIndex - 1, + widthOfPreviousLine = this._getWidthOfLine(this.ctx, lineIndex, textLines), + lineLeftOffset = this._getLineLeftOffset(widthOfPreviousLine), + widthOfCharsOnPreviousLine = lineLeftOffset, + indexOnPrevLine = 0, + foundMatch; for (var j = 0, jlen = textOnPreviousLine.length; j < jlen; j++) { - var _char = textOnPreviousLine[j]; - var widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j); + var _char = textOnPreviousLine[j], + widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j); widthOfCharsOnPreviousLine += widthOfChar; @@ -319,10 +321,10 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot foundMatch = true; - var leftEdge = widthOfCharsOnPreviousLine - widthOfChar; - var rightEdge = widthOfCharsOnPreviousLine; - var offsetFromLeftEdge = Math.abs(leftEdge - widthOfCharsOnSameLineBeforeCursor); - var offsetFromRightEdge = Math.abs(rightEdge - widthOfCharsOnSameLineBeforeCursor); + var leftEdge = widthOfCharsOnPreviousLine - widthOfChar, + rightEdge = widthOfCharsOnPreviousLine, + offsetFromLeftEdge = Math.abs(leftEdge - widthOfCharsOnSameLineBeforeCursor), + offsetFromRightEdge = Math.abs(rightEdge - widthOfCharsOnSameLineBeforeCursor); indexOnPrevLine = offsetFromRightEdge < offsetFromLeftEdge ? j : (j - 1); @@ -572,7 +574,8 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot } this.setCoords(); - this.fire('text:changed'); + this.fire('changed'); + this.canvas && this.canvas.fire('text:changed', { target: this }); }, /** @@ -596,7 +599,7 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot this.selectionStart = leftWordBoundary; } else { - var isBeginningOfLine = this.text.slice(this.selectionStart-1, this.selectionStart) === '\n'; + var isBeginningOfLine = this.text.slice(this.selectionStart - 1, this.selectionStart) === '\n'; this.removeStyleObject(isBeginningOfLine); this.selectionStart--; diff --git a/src/mixins/object.svg_export.js b/src/mixins/object.svg_export.js index 39cc07f2..43717121 100644 --- a/src/mixins/object.svg_export.js +++ b/src/mixins/object.svg_export.js @@ -8,32 +8,32 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot getSvgStyles: function() { var fill = this.fill - ? (this.fill.toLive ? 'url(#SVGID_' + this.fill.id + ')' : this.fill) - : 'none'; + ? (this.fill.toLive ? 'url(#SVGID_' + this.fill.id + ')' : this.fill) + : 'none', - var stroke = this.stroke - ? (this.stroke.toLive ? 'url(#SVGID_' + this.stroke.id + ')' : this.stroke) - : 'none'; + stroke = this.stroke + ? (this.stroke.toLive ? 'url(#SVGID_' + this.stroke.id + ')' : this.stroke) + : 'none', - var strokeWidth = this.strokeWidth ? this.strokeWidth : '0'; - var strokeDashArray = this.strokeDashArray ? this.strokeDashArray.join(' ') : ''; - var strokeLineCap = this.strokeLineCap ? this.strokeLineCap : 'butt'; - var strokeLineJoin = this.strokeLineJoin ? this.strokeLineJoin : 'miter'; - var strokeMiterLimit = this.strokeMiterLimit ? this.strokeMiterLimit : '4'; - var opacity = typeof this.opacity !== 'undefined' ? this.opacity : '1'; + strokeWidth = this.strokeWidth ? this.strokeWidth : '0', + strokeDashArray = this.strokeDashArray ? this.strokeDashArray.join(' ') : '', + strokeLineCap = this.strokeLineCap ? this.strokeLineCap : 'butt', + strokeLineJoin = this.strokeLineJoin ? this.strokeLineJoin : 'miter', + strokeMiterLimit = this.strokeMiterLimit ? this.strokeMiterLimit : '4', + opacity = typeof this.opacity !== 'undefined' ? this.opacity : '1', - var visibility = this.visible ? '' : " visibility: hidden;"; - var filter = this.shadow && this.type !== 'text' ? 'filter: url(#SVGID_' + this.shadow.id + ');' : ''; + visibility = this.visible ? '' : ' visibility: hidden;', + filter = this.shadow && this.type !== 'text' ? 'filter: url(#SVGID_' + this.shadow.id + ');' : ''; return [ - "stroke: ", stroke, "; ", - "stroke-width: ", strokeWidth, "; ", - "stroke-dasharray: ", strokeDashArray, "; ", - "stroke-linecap: ", strokeLineCap, "; ", - "stroke-linejoin: ", strokeLineJoin, "; ", - "stroke-miterlimit: ", strokeMiterLimit, "; ", - "fill: ", fill, "; ", - "opacity: ", opacity, ";", + 'stroke: ', stroke, '; ', + 'stroke-width: ', strokeWidth, '; ', + 'stroke-dasharray: ', strokeDashArray, '; ', + 'stroke-linecap: ', strokeLineCap, '; ', + 'stroke-linejoin: ', strokeLineJoin, '; ', + 'stroke-miterlimit: ', strokeMiterLimit, '; ', + 'fill: ', fill, '; ', + 'opacity: ', opacity, ';', filter, visibility ].join(''); @@ -44,34 +44,37 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot * @return {String} */ getSvgTransform: function() { - var toFixed = fabric.util.toFixed; - var angle = this.getAngle(); - var center = this.getCenterPoint(); + var toFixed = fabric.util.toFixed, + angle = this.getAngle(), + center = this.getCenterPoint(), - var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS; + NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, - var translatePart = "translate(" + + translatePart = 'translate(' + toFixed(center.x, NUM_FRACTION_DIGITS) + - " " + + ' ' + toFixed(center.y, NUM_FRACTION_DIGITS) + - ")"; + ')', - var anglePart = angle !== 0 - ? (" rotate(" + toFixed(angle, NUM_FRACTION_DIGITS) + ")") - : ''; + anglePart = angle !== 0 + ? (' rotate(' + toFixed(angle, NUM_FRACTION_DIGITS) + ')') + : '', - var scalePart = (this.scaleX === 1 && this.scaleY === 1) - ? '' : - (" scale(" + - toFixed(this.scaleX, NUM_FRACTION_DIGITS) + - " " + - toFixed(this.scaleY, NUM_FRACTION_DIGITS) + - ")"); + scalePart = (this.scaleX === 1 && this.scaleY === 1) + ? '' : + (' scale(' + + toFixed(this.scaleX, NUM_FRACTION_DIGITS) + + ' ' + + toFixed(this.scaleY, NUM_FRACTION_DIGITS) + + ')'), - var flipXPart = this.flipX ? "matrix(-1 0 0 1 0 0) " : ""; - var flipYPart = this.flipY ? "matrix(1 0 0 -1 0 0)" : ""; + flipXPart = this.flipX ? 'matrix(-1 0 0 1 0 0) ' : '', - return [ translatePart, anglePart, scalePart, flipXPart, flipYPart ].join(''); + flipYPart = this.flipY ? 'matrix(1 0 0 -1 0 0)' : ''; + + return [ + translatePart, anglePart, scalePart, flipXPart, flipYPart + ].join(''); }, /** diff --git a/src/mixins/object_geometry.mixin.js b/src/mixins/object_geometry.mixin.js index 03460f5f..d393b29c 100644 --- a/src/mixins/object_geometry.mixin.js +++ b/src/mixins/object_geometry.mixin.js @@ -22,13 +22,12 @@ tl = new fabric.Point(oCoords.tl.x, oCoords.tl.y), tr = new fabric.Point(oCoords.tr.x, oCoords.tr.y), bl = new fabric.Point(oCoords.bl.x, oCoords.bl.y), - br = new fabric.Point(oCoords.br.x, oCoords.br.y); - - var intersection = fabric.Intersection.intersectPolygonRectangle( - [tl, tr, br, bl], - pointTL, - pointBR - ); + br = new fabric.Point(oCoords.br.x, oCoords.br.y), + intersection = fabric.Intersection.intersectPolygonRectangle( + [tl, tr, br, bl], + pointTL, + pointBR + ); return intersection.status === 'Intersection'; }, @@ -48,12 +47,11 @@ }; } var thisCoords = getCoords(this.oCoords), - otherCoords = getCoords(other.oCoords); - - var intersection = fabric.Intersection.intersectPolygonPolygon( - [thisCoords.tl, thisCoords.tr, thisCoords.br, thisCoords.bl], - [otherCoords.tl, otherCoords.tr, otherCoords.br, otherCoords.bl] - ); + otherCoords = getCoords(other.oCoords), + intersection = fabric.Intersection.intersectPolygonPolygon( + [thisCoords.tl, thisCoords.tr, thisCoords.br, thisCoords.bl], + [otherCoords.tl, otherCoords.tr, otherCoords.br, otherCoords.bl] + ); return intersection.status === 'Intersection'; }, @@ -81,10 +79,10 @@ var boundingRect = this.getBoundingRect(); return ( - boundingRect.left > pointTL.x && - boundingRect.left + boundingRect.width < pointBR.x && - boundingRect.top > pointTL.y && - boundingRect.top + boundingRect.height < pointBR.y + boundingRect.left >= pointTL.x && + boundingRect.left + boundingRect.width <= pointBR.x && + boundingRect.top >= pointTL.y && + boundingRect.top + boundingRect.height <= pointBR.y ); }, @@ -158,7 +156,7 @@ else { b1 = 0; b2 = (iLine.d.y - iLine.o.y) / (iLine.d.x - iLine.o.x); - a1 = point.y- b1 * point.x; + a1 = point.y - b1 * point.x; a2 = iLine.o.y - b2 * iLine.o.x; xi = - (a1 - a2) / (b1 - b2); @@ -201,15 +199,15 @@ getBoundingRect: function() { this.oCoords || this.setCoords(); - var xCoords = [this.oCoords.tl.x, this.oCoords.tr.x, this.oCoords.br.x, this.oCoords.bl.x]; - var minX = fabric.util.array.min(xCoords); - var maxX = fabric.util.array.max(xCoords); - var width = Math.abs(minX - maxX); + var xCoords = [this.oCoords.tl.x, this.oCoords.tr.x, this.oCoords.br.x, this.oCoords.bl.x], + minX = fabric.util.array.min(xCoords), + maxX = fabric.util.array.max(xCoords), + width = Math.abs(minX - maxX), - var yCoords = [this.oCoords.tl.y, this.oCoords.tr.y, this.oCoords.br.y, this.oCoords.bl.y]; - var minY = fabric.util.array.min(yCoords); - var maxY = fabric.util.array.max(yCoords); - var height = Math.abs(minY - maxY); + yCoords = [this.oCoords.tl.y, this.oCoords.tr.y, this.oCoords.br.y, this.oCoords.bl.y], + minY = fabric.util.array.min(yCoords), + maxY = fabric.util.array.max(yCoords), + height = Math.abs(minY - maxY); return { left: minX, @@ -243,12 +241,13 @@ */ _constrainScale: function(value) { if (Math.abs(value) < this.minScaleLimit) { - if (value < 0) + if (value < 0) { return -this.minScaleLimit; - else + } + else { return this.minScaleLimit; + } } - return value; }, @@ -307,9 +306,7 @@ var strokeWidth = this.strokeWidth > 1 ? this.strokeWidth : 0, padding = this.padding, theta = degreesToRadians(this.angle), - vpt; - // TODO: ideally we should never setCoords an object which lacks a canvas - vpt = this.canvas ? this.canvas.viewportTransform : [1, 0, 0, 1, 0, 0]; + vpt = this.getViewportTransform(); var f = function (p) { return fabric.util.transformPoint(p, vpt); @@ -324,13 +321,13 @@ } var _hypotenuse = Math.sqrt( - Math.pow(this.currentWidth / 2, 2) + - Math.pow(this.currentHeight / 2, 2)); + Math.pow(this.currentWidth / 2, 2) + + Math.pow(this.currentHeight / 2, 2)), - var _angle = Math.atan(isFinite(this.currentHeight / this.currentWidth) ? this.currentHeight / this.currentWidth : 0); + _angle = Math.atan(isFinite(this.currentHeight / this.currentWidth) ? this.currentHeight / this.currentWidth : 0), - // offset added for rotate and scale actions - var offsetX = Math.cos(_angle + theta) * _hypotenuse, + // offset added for rotate and scale actions + offsetX = Math.cos(_angle + theta) * _hypotenuse, offsetY = Math.sin(_angle + theta) * _hypotenuse, sinTh = Math.sin(theta), cosTh = Math.cos(theta), diff --git a/src/mixins/object_interactivity.mixin.js b/src/mixins/object_interactivity.mixin.js index 9ac2b341..de15029d 100644 --- a/src/mixins/object_interactivity.mixin.js +++ b/src/mixins/object_interactivity.mixin.js @@ -1,7 +1,6 @@ (function(){ - var getPointer = fabric.util.getPointer, - degreesToRadians = fabric.util.degreesToRadians, + var degreesToRadians = fabric.util.degreesToRadians, isVML = typeof G_vmlCanvasManager !== 'undefined'; fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { @@ -15,15 +14,13 @@ /** * Determines which corner has been clicked * @private - * @param {Event} e Event object - * @param {Object} offset Canvas offset + * @param {Object} pointer The pointer indicating the mouse position * @return {String|Boolean} corner code (tl, tr, bl, br, etc.), or false if nothing is found */ - _findTargetCorner: function(e, offset) { + _findTargetCorner: function(pointer) { if (!this.hasControls || !this.active) return false; - var pointer = this.canvas.getPointer(e, true), - ex = pointer.x, + var ex = pointer.x, ey = pointer.y, xPoints, lines; @@ -38,7 +35,8 @@ continue; } - if (this.get('lockUniScaling') && (i === 'mt' || i === 'mr' || i === 'mb' || i === 'ml')) { + if (this.get('lockUniScaling') && + (i === 'mt' || i === 'mr' || i === 'mb' || i === 'ml')) { continue; } @@ -58,7 +56,7 @@ // canvas.contextTop.fillRect(lines.rightline.d.x, lines.rightline.d.y, 2, 2); // canvas.contextTop.fillRect(lines.rightline.o.x, lines.rightline.o.y, 2, 2); - xPoints = this._findCrossPoints({x: ex, y: ey}, lines); + xPoints = this._findCrossPoints({ x: ex, y: ey }, lines); if (xPoints !== 0 && xPoints % 2 === 1) { this.__corner = i; return i; @@ -278,7 +276,7 @@ ctx.lineWidth = 1 / this.borderScaleFactor; - var vpt = this.canvas.viewportTransform, + var vpt = this.getViewportTransform(), wh = fabric.util.transformPoint(new fabric.Point(this.getWidth(), this.getHeight()), vpt, true), sxy = fabric.util.transformPoint(new fabric.Point(scaleX, scaleY), vpt, true), w = wh.x, @@ -330,7 +328,7 @@ var size = this.cornerSize, size2 = size / 2, strokeWidth2 = ~~(this.strokeWidth / 2), // half strokeWidth rounded down - wh = fabric.util.transformPoint(new fabric.Point(this.getWidth(), this.getHeight()), this.canvas.viewportTransform, true), + wh = fabric.util.transformPoint(new fabric.Point(this.getWidth(), this.getHeight()), this.getViewportTransform(), true), width = wh.x, height = wh.y, left = -(width / 2), @@ -360,7 +358,7 @@ top - scaleOffset - strokeWidth2 - padding); // bottom-left - this._drawControl('tr', ctx, methodName, + this._drawControl('bl', ctx, methodName, left - scaleOffset - strokeWidth2 - padding, top + height + scaleOffsetSize + strokeWidth2 + padding); @@ -382,7 +380,7 @@ top + height + scaleOffsetSize + strokeWidth2 + padding); // middle-right - this._drawControl('mb', ctx, methodName, + this._drawControl('mr', ctx, methodName, left + width + scaleOffsetSize + strokeWidth2 + padding, top + height/2 - scaleOffset); @@ -471,15 +469,15 @@ _getControlsVisibility: function() { if (!this._controlsVisibility) { this._controlsVisibility = { - tl: true, - tr: true, - br: true, - bl: true, - ml: true, - mt: true, - mr: true, - mb: true, - mtr: true + tl: true, + tr: true, + br: true, + bl: true, + ml: true, + mt: true, + mr: true, + mb: true, + mtr: true }; } return this._controlsVisibility; diff --git a/src/mixins/object_origin.mixin.js b/src/mixins/object_origin.mixin.js index 102b170a..fbcce105 100644 --- a/src/mixins/object_origin.mixin.js +++ b/src/mixins/object_origin.mixin.js @@ -16,17 +16,17 @@ cy = point.y, strokeWidth = this.stroke ? this.strokeWidth : 0; - if (originX === "left") { + if (originX === 'left') { cx = point.x + (this.getWidth() + strokeWidth * this.scaleX) / 2; } - else if (originX === "right") { + else if (originX === 'right') { cx = point.x - (this.getWidth() + strokeWidth * this.scaleX) / 2; } - if (originY === "top") { + if (originY === 'top') { cy = point.y + (this.getHeight() + strokeWidth * this.scaleY) / 2; } - else if (originY === "bottom") { + else if (originY === 'bottom') { cy = point.y - (this.getHeight() + strokeWidth * this.scaleY) / 2; } @@ -47,16 +47,16 @@ strokeWidth = this.stroke ? this.strokeWidth : 0; // Get the point coordinates - if (originX === "left") { + if (originX === 'left') { x = center.x - (this.getWidth() + strokeWidth * this.scaleX) / 2; } - else if (originX === "right") { + else if (originX === 'right') { x = center.x + (this.getWidth() + strokeWidth * this.scaleX) / 2; } - if (originY === "top") { + if (originY === 'top') { y = center.y - (this.getHeight() + strokeWidth * this.scaleY) / 2; } - else if (originY === "bottom") { + else if (originY === 'bottom') { y = center.y + (this.getHeight() + strokeWidth * this.scaleY) / 2; } @@ -106,20 +106,20 @@ x, y; if (originX && originY) { - if (originX === "left") { + if (originX === 'left') { x = center.x - (this.getWidth() + strokeWidth * this.scaleX) / 2; } - else if (originX === "right") { + else if (originX === 'right') { x = center.x + (this.getWidth() + strokeWidth * this.scaleX) / 2; } else { x = center.x; } - if (originY === "top") { + if (originY === 'top') { y = center.y - (this.getHeight() + strokeWidth * this.scaleY) / 2; } - else if (originY === "bottom") { + else if (originY === 'bottom') { y = center.y + (this.getHeight() + strokeWidth * this.scaleY) / 2; } else { @@ -152,8 +152,8 @@ * @return {void} */ setPositionByOrigin: function(pos, originX, originY) { - var center = this.translateToCenterPoint(pos, originX, originY); - var position = this.translateToOriginPoint(center, this.originX, this.originY); + var center = this.translateToCenterPoint(pos, originX, originY), + position = this.translateToOriginPoint(center, this.originX, this.originY); this.set('left', position.x); this.set('top', position.y); @@ -163,13 +163,13 @@ * @param {String} to One of 'left', 'center', 'right' */ adjustPosition: function(to) { - var angle = degreesToRadians(this.angle); - var hypotHalf = this.getWidth() / 2; - var xHalf = Math.cos(angle) * hypotHalf; - var yHalf = Math.sin(angle) * hypotHalf; - var hypotFull = this.getWidth(); - var xFull = Math.cos(angle) * hypotFull; - var yFull = Math.sin(angle) * hypotFull; + var angle = degreesToRadians(this.angle), + hypotHalf = this.getWidth() / 2, + xHalf = Math.cos(angle) * hypotHalf, + yHalf = Math.sin(angle) * hypotHalf, + hypotFull = this.getWidth(), + xFull = Math.cos(angle) * hypotFull, + yFull = Math.sin(angle) * hypotFull; if (this.originX === 'center' && to === 'left' || this.originX === 'right' && to === 'center') { @@ -198,6 +198,45 @@ this.originX = to; }, + /** + * @private + * Sets the origin/position of the object to it's center point + * @return {void} + */ + _setOriginToCenter: function() { + this._originalOriginX = this.originX; + this._originalOriginY = this.originY; + + var center = this.getCenterPoint(); + + this.originX = 'center'; + this.originY = 'center'; + + this.left = center.x; + this.top = center.y; + }, + + /** + * @private + * Resets the origin/position of the object to it's original origin + * @return {void} + */ + _resetOrigin: function() { + var originPoint = this.translateToOriginPoint( + this.getCenterPoint(), + this._originalOriginX, + this._originalOriginY); + + this.originX = this._originalOriginX; + this.originY = this._originalOriginY; + + this.left = originPoint.x; + this.top = originPoint.y; + + this._originalOriginX = null; + this._originalOriginY = null; + }, + /** * @private */ diff --git a/src/mixins/object_straightening.mixin.js b/src/mixins/object_straightening.mixin.js index 1ec7ce23..0c3e30aa 100644 --- a/src/mixins/object_straightening.mixin.js +++ b/src/mixins/object_straightening.mixin.js @@ -7,9 +7,9 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot _getAngleValueForStraighten: function() { var angle = this.getAngle() % 360; if (angle > 0) { - return Math.round((angle-1)/90) * 90; + return Math.round((angle - 1) / 90) * 90; } - return Math.round(angle/90) * 90; + return Math.round(angle / 90) * 90; }, /** diff --git a/src/node.js b/src/node.js index 4bb44fab..5f344400 100644 --- a/src/node.js +++ b/src/node.js @@ -4,7 +4,7 @@ return; } - var DOMParser = new require('xmldom').DOMParser, + var DOMParser = require('xmldom').DOMParser, URL = require('url'), HTTP = require('http'), HTTPS = require('https'), @@ -22,27 +22,26 @@ } // assign request handler based on protocol - var reqHandler = ( oURL.port === 443 ) ? HTTPS : HTTP; - - var req = reqHandler.request({ - hostname: oURL.hostname, - port: oURL.port, - path: oURL.path, - method: 'GET' - }, function(response){ - var body = ""; - if (encoding) { - response.setEncoding(encoding); - } - response.on('end', function () { - callback(body); - }); - response.on('data', function (chunk) { - if (response.statusCode === 200) { - body += chunk; - } - }); - }); + var reqHandler = ( oURL.port === 443 ) ? HTTPS : HTTP, + req = reqHandler.request({ + hostname: oURL.hostname, + port: oURL.port, + path: oURL.path, + method: 'GET' + }, function(response) { + var body = ''; + if (encoding) { + response.setEncoding(encoding); + } + response.on('end', function () { + callback(body); + }); + response.on('data', function (chunk) { + if (response.statusCode === 200) { + body += chunk; + } + }); + }); req.on('error', function(err) { if (err.errno === process.ECONNREFUSED) { @@ -57,32 +56,33 @@ } /** @private */ - function request_fs(path, callback){ + function requestFs(path, callback){ var fs = require('fs'); fs.readFile(path, function (err, data) { if (err) { fabric.log(err); throw err; - } else { + } + else { callback(data); } }); } fabric.util.loadImage = function(url, callback, context) { - var createImageAndCallBack = function(data){ + function createImageAndCallBack(data) { img.src = new Buffer(data, 'binary'); // preserving original url, which seems to be lost in node-canvas img._src = url; callback && callback.call(context, img); - }; + } var img = new Image(); if (url && (url instanceof Buffer || url.indexOf('data') === 0)) { img.src = img._src = url; callback && callback.call(context, img); } else if (url && url.indexOf('http') !== 0) { - request_fs(url, createImageAndCallBack); + requestFs(url, createImageAndCallBack); } else if (url) { request(url, 'binary', createImageAndCallBack); @@ -95,8 +95,8 @@ fabric.loadSVGFromURL = function(url, callback, reviver) { url = url.replace(/^\n\s*/, '').replace(/\?.*$/, '').trim(); if (url.indexOf('http') !== 0) { - request_fs(url, function(body) { - fabric.loadSVGFromString(body, callback, reviver); + requestFs(url, function(body) { + fabric.loadSVGFromString(body.toString(), callback, reviver); }); } else { @@ -136,12 +136,15 @@ * Only available when running fabric on node.js * @param width Canvas width * @param height Canvas height + * @param {Object} options to pass to FabricCanvas. + * @param {Object} options to pass to NodeCanvas. * @return {Object} wrapped canvas instance */ - fabric.createCanvasForNode = function(width, height) { + fabric.createCanvasForNode = function(width, height, options, nodeCanvasOptions) { + nodeCanvasOptions = nodeCanvasOptions || options; var canvasEl = fabric.document.createElement('canvas'), - nodeCanvas = new Canvas(width || 600, height || 600); + nodeCanvas = new Canvas(width || 600, height || 600, nodeCanvasOptions); // jsdom doesn't create style on canvas element, so here be temp. workaround canvasEl.style = { }; @@ -149,8 +152,9 @@ canvasEl.width = nodeCanvas.width; canvasEl.height = nodeCanvas.height; - var FabricCanvas = fabric.Canvas || fabric.StaticCanvas; - var fabricCanvas = new FabricCanvas(canvasEl); + var FabricCanvas = fabric.Canvas || fabric.StaticCanvas, + fabricCanvas = new FabricCanvas(canvasEl, options); + fabricCanvas.contextContainer = nodeCanvas.getContext('2d'); fabricCanvas.nodeCanvas = nodeCanvas; fabricCanvas.Font = Canvas.Font; diff --git a/src/parser.js b/src/parser.js index 7b5172b3..ff9ba93f 100644 --- a/src/parser.js +++ b/src/parser.js @@ -1,6 +1,6 @@ (function(global) { - "use strict"; + 'use strict'; /** * @name fabric @@ -12,34 +12,37 @@ capitalize = fabric.util.string.capitalize, clone = fabric.util.object.clone, toFixed = fabric.util.toFixed, - multiplyTransformMatrices = fabric.util.multiplyTransformMatrices; + multiplyTransformMatrices = fabric.util.multiplyTransformMatrices, - var attributesMap = { - 'fill-opacity': 'fillOpacity', - 'fill-rule': 'fillRule', - 'font-family': 'fontFamily', - 'font-size': 'fontSize', - 'font-style': 'fontStyle', - 'font-weight': 'fontWeight', - 'cx': 'left', - 'x': 'left', - 'r': 'radius', - 'stroke-dasharray': 'strokeDashArray', - 'stroke-linecap': 'strokeLineCap', - 'stroke-linejoin': 'strokeLineJoin', - 'stroke-miterlimit':'strokeMiterLimit', - 'stroke-opacity': 'strokeOpacity', - 'stroke-width': 'strokeWidth', - 'text-decoration': 'textDecoration', - 'cy': 'top', - 'y': 'top', - 'transform': 'transformMatrix' - }; + attributesMap = { + cx: 'left', + x: 'left', + r: 'radius', + cy: 'top', + y: 'top', + display: 'visible', + visibility: 'visible', + transform: 'transformMatrix', + 'fill-opacity': 'fillOpacity', + 'fill-rule': 'fillRule', + 'font-family': 'fontFamily', + 'font-size': 'fontSize', + 'font-style': 'fontStyle', + 'font-weight': 'fontWeight', + 'stroke-dasharray': 'strokeDashArray', + 'stroke-linecap': 'strokeLineCap', + 'stroke-linejoin': 'strokeLineJoin', + 'stroke-miterlimit': 'strokeMiterLimit', + 'stroke-opacity': 'strokeOpacity', + 'stroke-width': 'strokeWidth', + 'text-decoration': 'textDecoration', + 'text-anchor': 'originX' + }, - var colorAttributes = { - 'stroke': 'strokeOpacity', - 'fill': 'fillOpacity' - }; + colorAttributes = { + stroke: 'strokeOpacity', + fill: 'fillOpacity' + }; function normalizeAttr(attr) { // transform attribute names @@ -70,6 +73,16 @@ value = fabric.parseTransformAttribute(value); } } + else if (attr === 'visible') { + value = (value === 'none' || value === 'hidden') ? false : true; + // display=none on parent element always takes precedence over child element + if (parentAttributes.visible === false) { + value = false; + } + } + else if (attr === 'originX' /* text-anchor */) { + value = value === 'start' ? 'left' : value === 'end' ? 'right' : 'center'; + } isArray = Object.prototype.toString.call(value) === '[object Array]'; @@ -150,30 +163,30 @@ ], // == begin transform regexp - number = '(?:[-+]?\\d+(?:\\.\\d+)?(?:e[-+]?\\d+)?)', + number = '(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)', - comma_wsp = '(?:\\s+,?\\s*|,\\s*)', + commaWsp = '(?:\\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*\\))', + commaWsp + '(' + number + ')' + + commaWsp + '(' + number + '))?\\s*\\))', scale = '(?:(scale)\\s*\\(\\s*(' + number + ')(?:' + - comma_wsp + '(' + number + '))?\\s*\\))', + commaWsp + '(' + number + '))?\\s*\\))', translate = '(?:(translate)\\s*\\(\\s*(' + number + ')(?:' + - comma_wsp + '(' + number + '))?\\s*\\))', + commaWsp + '(' + number + '))?\\s*\\))', matrix = '(?:(matrix)\\s*\\(\\s*' + - '(' + number + ')' + comma_wsp + - '(' + number + ')' + comma_wsp + - '(' + number + ')' + comma_wsp + - '(' + number + ')' + comma_wsp + - '(' + number + ')' + comma_wsp + + '(' + number + ')' + commaWsp + + '(' + number + ')' + commaWsp + + '(' + number + ')' + commaWsp + + '(' + number + ')' + commaWsp + + '(' + number + ')' + commaWsp + '(' + number + ')' + '\\s*\\))', @@ -186,12 +199,12 @@ skewY + ')', - transforms = '(?:' + transform + '(?:' + comma_wsp + transform + ')*' + ')', + transforms = '(?:' + transform + '(?:' + commaWsp + transform + ')*' + ')', - transform_list = '^\\s*(?:' + transforms + '?)\\s*$', + transformList = '^\\s*(?:' + transforms + '?)\\s*$', // http://www.w3.org/TR/SVG/coords.html#TransformAttribute - reTransformList = new RegExp(transform_list), + reTransformList = new RegExp(transformList), // == end transform regexp reTransform = new RegExp(transform, 'g'); @@ -199,8 +212,8 @@ return function(attributeValue) { // start with identity matrix - var matrix = iMatrix.concat(); - var matrices = [ ]; + var matrix = iMatrix.concat(), + matrices = [ ]; // return if no argument was given or // an argument does not match transform attribute regexp @@ -216,11 +229,12 @@ operation = m[1], args = m.slice(2).map(parseFloat); - switch(operation) { + switch (operation) { case 'translate': translateMatrix(matrix, args); break; case 'rotate': + args[0] = fabric.util.degreesToRadians(args[0]); rotateMatrix(matrix, args); break; case 'scale': @@ -259,19 +273,19 @@ if (!match) return; - var fontStyle = match[1]; - // Font variant is not used - // var fontVariant = match[2]; - var fontWeight = match[3]; - var fontSize = match[4]; - var lineHeight = match[5]; - var fontFamily = match[6]; + var fontStyle = match[1], + // font variant is not used + // fontVariant = match[2], + fontWeight = match[3], + fontSize = match[4], + lineHeight = match[5], + fontFamily = match[6]; if (fontStyle) { oStyle.fontStyle = fontStyle; } if (fontWeight) { - oStyle.fontSize = isNaN(parseFloat(fontWeight)) ? fontWeight : parseFloat(fontWeight); + oStyle.fontWeight = isNaN(parseFloat(fontWeight)) ? fontWeight : parseFloat(fontWeight); } if (fontSize) { oStyle.fontSize = parseFloat(fontSize); @@ -359,22 +373,22 @@ */ fabric.parseSVGDocument = (function() { - var reAllowedSVGTagNames = /^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/; + var reAllowedSVGTagNames = /^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/, - // http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute - // \d doesn't quite cut it (as we need to match an actual float number) + // http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute + // \d doesn't quite cut it (as we need to match an actual float number) - // matches, e.g.: +14.56e-12, etc. - var reNum = '(?:[-+]?\\d+(?:\\.\\d+)?(?:e[-+]?\\d+)?)'; + // matches, e.g.: +14.56e-12, etc. + reNum = '(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)', - var reViewBoxAttrValue = new RegExp( - '^' + - '\\s*(' + reNum + '+)\\s*,?' + - '\\s*(' + reNum + '+)\\s*,?' + - '\\s*(' + reNum + '+)\\s*,?' + - '\\s*(' + reNum + '+)\\s*' + - '$' - ); + reViewBoxAttrValue = new RegExp( + '^' + + '\\s*(' + reNum + '+)\\s*,?' + + '\\s*(' + reNum + '+)\\s*,?' + + '\\s*(' + reNum + '+)\\s*,?' + + '\\s*(' + reNum + '+)\\s*' + + '$' + ); function hasAncestorWithNodeName(element, nodeName) { while (element && (element = element.parentNode)) { @@ -391,10 +405,10 @@ var startTime = new Date(), descendants = fabric.util.toArray(doc.getElementsByTagName('*')); - if (descendants.length === 0) { + if (descendants.length === 0 && fabric.isLikelyNode) { // we're likely in node, where "o3-xml" library fails to gEBTN("*") // https://github.com/ajaxorg/node-o3-xml/issues/21 - descendants = doc.selectNodes("//*[name(.)!='svg']"); + descendants = doc.selectNodes('//*[name(.)!="svg"]'); var arr = [ ]; for (var i = 0, len = descendants.length; i < len; i++) { arr[i] = descendants[i]; @@ -407,30 +421,43 @@ !hasAncestorWithNodeName(el, /^(?:pattern|defs)$/); // http://www.w3.org/TR/SVG/struct.html#DefsElement }); - if (!elements || (elements && !elements.length)) return; + if (!elements || (elements && !elements.length)) { + callback && callback([], {}); + return; + } var viewBoxAttr = doc.getAttribute('viewBox'), - widthAttr = doc.getAttribute('width'), - heightAttr = doc.getAttribute('height'), + widthAttr = parseFloat(doc.getAttribute('width')), + heightAttr = parseFloat(doc.getAttribute('height')), width = null, height = null, + viewBoxWidth, + viewBoxHeight, minX, minY; if (viewBoxAttr && (viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))) { - minX = parseInt(viewBoxAttr[1], 10); - minY = parseInt(viewBoxAttr[2], 10); - width = parseInt(viewBoxAttr[3], 10); - height = parseInt(viewBoxAttr[4], 10); + minX = parseFloat(viewBoxAttr[1]); + minY = parseFloat(viewBoxAttr[2]); + viewBoxWidth = parseFloat(viewBoxAttr[3]); + viewBoxHeight = parseFloat(viewBoxAttr[4]); } - // values of width/height attributes overwrite those extracted from viewbox attribute - width = widthAttr ? parseFloat(widthAttr) : width; - height = heightAttr ? parseFloat(heightAttr) : height; + if (viewBoxWidth && widthAttr && viewBoxWidth !== widthAttr) { + width = viewBoxWidth; + height = viewBoxHeight; + } + else { + // values of width/height attributes overwrite those extracted from viewbox attribute + width = widthAttr ? widthAttr : viewBoxWidth; + height = heightAttr ? heightAttr : viewBoxHeight; + } var options = { width: width, - height: height + height: height, + widthAttr: widthAttr, + heightAttr: heightAttr }; fabric.gradientDefs = fabric.getGradientDefs(doc); @@ -614,7 +641,7 @@ * @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); + new fabric.ElementsParser(elements, callback, options, reviver).parse(); }, /** @@ -628,7 +655,9 @@ var oStyle = { }, style = element.getAttribute('style'); - if (!style) return oStyle; + if (!style) { + return oStyle; + } if (typeof style === 'string') { parseStyleString(style, oStyle); @@ -664,21 +693,27 @@ len = points.length; for (; i < len; i++) { var pair = points[i].split(','); - parsedPoints.push({ x: parseFloat(pair[0]), y: parseFloat(pair[1]) }); + 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]) }); + parsedPoints.push({ + x: parseFloat(points[i]), + y: parseFloat(points[i + 1]) + }); } } // odd number of points is an error - if (parsedPoints.length % 2 !== 0) { + // if (parsedPoints.length % 2 !== 0) { // return null; - } + // } return parsedPoints; }, @@ -758,13 +793,13 @@ function onComplete(r) { var xml = r.responseXML; - if (!xml.documentElement && fabric.window.ActiveXObject && r.responseText) { + if (xml && !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; + if (!xml || !xml.documentElement) return; fabric.parseSVGDocument(xml.documentElement, function (results, options) { svgCache.set(url, { diff --git a/src/pattern.class.js b/src/pattern.class.js index c89d371a..7d1cb04c 100644 --- a/src/pattern.class.js +++ b/src/pattern.class.js @@ -101,10 +101,10 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ * @return {String} SVG representation of a pattern */ toSVG: function(object) { - var patternSource = typeof this.source === 'function' ? this.source() : this.source; - var patternWidth = patternSource.width / object.getWidth(); - var patternHeight = patternSource.height / object.getHeight(); - var patternImgSrc = ''; + var patternSource = typeof this.source === 'function' ? this.source() : this.source, + patternWidth = patternSource.width / object.getWidth(), + patternHeight = patternSource.height / object.getHeight(), + patternImgSrc = ''; if (patternSource.src) { patternImgSrc = patternSource.src; @@ -133,11 +133,23 @@ fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ * @return {CanvasPattern} */ toLive: function(ctx) { - var source = typeof this.source === 'function' ? this.source() : this.source; + var source = typeof this.source === 'function' + ? this.source() + : this.source; + + // if the image failed to load, return, and allow rest to continue loading + if (!source) { + return ''; + } + // if an image if (typeof source.src !== 'undefined') { - if (!source.complete) return ''; - if (source.naturalWidth === 0 || source.naturalHeight === 0) return ''; + if (!source.complete) { + return ''; + } + if (source.naturalWidth === 0 || source.naturalHeight === 0) { + return ''; + } } return ctx.createPattern(source, this.repeat); } diff --git a/src/point.class.js b/src/point.class.js index 7ae1f776..727be608 100644 --- a/src/point.class.js +++ b/src/point.class.js @@ -1,6 +1,6 @@ (function(global) { - "use strict"; + 'use strict'; /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */ @@ -250,7 +250,7 @@ * @return {String} */ toString: function () { - return this.x + "," + this.y; + return this.x + ',' + this.y; }, /** diff --git a/src/shadow.class.js b/src/shadow.class.js index 46b797b5..47ac4d5e 100644 --- a/src/shadow.class.js +++ b/src/shadow.class.js @@ -1,6 +1,6 @@ (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }); @@ -81,9 +81,8 @@ * @return {Object} Shadow object with color, offsetX, offsetY and blur */ _parseShadow: function(shadow) { - var shadowStr = shadow.trim(); - - var offsetsAndBlur = fabric.Shadow.reOffsetsAndBlur.exec(shadowStr) || [ ], + var shadowStr = shadow.trim(), + offsetsAndBlur = fabric.Shadow.reOffsetsAndBlur.exec(shadowStr) || [ ], color = shadowStr.replace(fabric.Shadow.reOffsetsAndBlur, '') || 'rgb(0,0,0)'; return { diff --git a/src/shapes/circle.class.js b/src/shapes/circle.class.js index b6390865..7abea87a 100644 --- a/src/shapes/circle.class.js +++ b/src/shapes/circle.class.js @@ -1,6 +1,6 @@ (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), piBy2 = Math.PI * 2, @@ -26,6 +26,13 @@ */ type: 'circle', + /** + * Radius of this circle + * @type Number + * @default + */ + radius: 0, + /** * Constructor * @param {Object} [options] Options object @@ -99,7 +106,7 @@ ctx.closePath(); this._renderFill(ctx); - this._renderStroke(ctx); + this.stroke && this._renderStroke(ctx); }, /** diff --git a/src/shapes/ellipse.class.js b/src/shapes/ellipse.class.js index fd58efac..59c1c41f 100644 --- a/src/shapes/ellipse.class.js +++ b/src/shapes/ellipse.class.js @@ -1,6 +1,6 @@ (function(global){ - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), piBy2 = Math.PI * 2, @@ -116,10 +116,10 @@ } ctx.transform(1, 0, 0, this.ry/this.rx, 0, 0); ctx.arc(noTransform ? this.left : 0, noTransform ? this.top : 0, this.rx, 0, piBy2, false); + ctx.restore(); this._renderFill(ctx); this._renderStroke(ctx); - ctx.restore(); }, /** @@ -151,9 +151,9 @@ fabric.Ellipse.fromElement = function(element, options) { options || (options = { }); - var parsedAttributes = fabric.parseAttributes(element, fabric.Ellipse.ATTRIBUTE_NAMES); - var cx = parsedAttributes.left; - var cy = parsedAttributes.top; + var parsedAttributes = fabric.parseAttributes(element, fabric.Ellipse.ATTRIBUTE_NAMES), + cx = parsedAttributes.left, + cy = parsedAttributes.top; if ('left' in parsedAttributes) { parsedAttributes.left -= (options.width / 2) || 0; diff --git a/src/shapes/group.class.js b/src/shapes/group.class.js index aa2e5786..6449911d 100644 --- a/src/shapes/group.class.js +++ b/src/shapes/group.class.js @@ -1,6 +1,6 @@ (function(global){ - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend, @@ -232,7 +232,7 @@ * @private */ _renderObject: function(object, ctx) { - var v = this.canvas.viewportTransform, + var v = this.getViewportTransform(), sxy = fabric.util.transformPoint( new fabric.Point(this.scaleX, this.scaleY), v, @@ -409,11 +409,10 @@ */ _setOpacityIfSame: function() { var objects = this.getObjects(), - firstValue = objects[0] ? objects[0].get('opacity') : 1; - - var isSameOpacity = objects.every(function(o) { - return o.get('opacity') === firstValue; - }); + firstValue = objects[0] ? objects[0].get('opacity') : 1, + isSameOpacity = objects.every(function(o) { + return o.get('opacity') === firstValue; + }); if (isSameOpacity) { this.opacity = firstValue; @@ -423,7 +422,7 @@ /** * @private */ - _calcBounds: function() { + _calcBounds: function(onlyWidthHeight) { var aX = [], aY = [], o; @@ -437,26 +436,29 @@ } } - this.set(this._getBounds(aX, aY)); + this.set(this._getBounds(aX, aY, onlyWidthHeight)); }, /** * @private */ - _getBounds: function(aX, aY) { + _getBounds: function(aX, aY, onlyWidthHeight) { var ivt; if (this.canvas) { - ivt = fabric.util.invertTransform(this.canvas.viewportTransform); + ivt = fabric.util.invertTransform(this.getViewportTransform()); } var minXY = fabric.util.transformPoint(new fabric.Point(min(aX), min(aY)), ivt), - maxXY = fabric.util.transformPoint(new fabric.Point(max(aX), max(aY)), ivt); + maxXY = fabric.util.transformPoint(new fabric.Point(max(aX), max(aY)), ivt), + obj = { + width: (maxXY.x - minXY.x) || 0, + height: (maxXY.y - minXY.y) || 0 + }; - return { - width: (maxXY.x - minXY.x) || 0, - height: (maxXY.y - minXY.y) || 0, - left: (minXY.x + maxXY.x) / 2 || 0, - top: (minXY.y + maxXY.y) / 2 || 0, - }; + if (!onlyWidthHeight) { + obj.left = (minXY.x + maxXY.x) / 2 || 0; + obj.top = (minXY.y + maxXY.y) / 2 || 0; + } + return obj; }, /* _TO_SVG_START_ */ diff --git a/src/shapes/image.class.js b/src/shapes/image.class.js index 65c41f1c..c485042a 100644 --- a/src/shapes/image.class.js +++ b/src/shapes/image.class.js @@ -1,6 +1,6 @@ (function(global) { - "use strict"; + 'use strict'; var extend = fabric.util.object.extend; @@ -122,17 +122,15 @@ if (!this.visible) return; ctx.save(); - var m = this.transformMatrix; - var v; - v = this.canvas.viewportTransform; - - var isInPathGroup = this.group && this.group.type === 'path-group'; + var m = this.transformMatrix, + v = this.getViewportTransform(), + isInPathGroup = this.group && this.group.type === 'path-group'; ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); // this._resetWidthHeight(); if (isInPathGroup) { - ctx.translate(-this.group.width/2 + this.width/2, -this.group.height/2 + this.height/2); + ctx.translate(-this.group.width/2, -this.group.height/2); } if (m) { ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); @@ -140,6 +138,9 @@ if (!noTransform) { this.transform(ctx); } + if (isInPathGroup) { + ctx.translate(this.width/2, this.height/2); + } ctx.save(); this._setShadow(ctx); @@ -183,10 +184,10 @@ this._setStrokeStyles(ctx); ctx.beginPath(); - fabric.util.drawDashedLine(ctx, x, y, x+w, y, this.strokeDashArray); - fabric.util.drawDashedLine(ctx, x+w, y, x+w, y+h, this.strokeDashArray); - fabric.util.drawDashedLine(ctx, x+w, y+h, x, y+h, this.strokeDashArray); - fabric.util.drawDashedLine(ctx, x, y+h, x, y, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, x, y, x + w, y, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, x + w, y, x + w, y + h, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, x + w, y + h, x, y + h, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, x, y + h, x, y, this.strokeDashArray); ctx.closePath(); ctx.restore(); }, @@ -253,7 +254,9 @@ * @return {String} Source of an image */ getSrc: function() { - return this.getElement().src || this.getElement()._src; + if (this.getElement()) { + return this.getElement().src || this.getElement()._src; + } }, /** @@ -282,6 +285,10 @@ */ applyFilters: function(callback) { + if (!this._originalElement) { + return; + } + if (this.filters.length === 0) { this._element = this._originalElement; callback && callback(); @@ -331,13 +338,13 @@ * @param {CanvasRenderingContext2D} ctx Context to render on */ _render: function(ctx) { - ctx.drawImage( - this._element, - -this.width / 2, - -this.height / 2, - this.width, - this.height - ); + this._element && ctx.drawImage( + this._element, + -this.width / 2, + -this.height / 2, + this.width, + this.height + ); }, /** @@ -369,7 +376,9 @@ options || (options = { }); this.setOptions(options); this._setWidthHeight(options); - this._element.crossOrigin = this.crossOrigin; + if (this._element && this.crossOrigin) { + this._element.crossOrigin = this.crossOrigin; + } }, /** @@ -395,11 +404,15 @@ _setWidthHeight: function(options) { this.width = 'width' in options ? options.width - : (this.getElement().width || 0); + : (this.getElement() + ? this.getElement().width || 0 + : 0); this.height = 'height' in options ? options.height - : (this.getElement().height || 0); + : (this.getElement() + ? this.getElement().height || 0 + : 0); }, /** @@ -417,7 +430,7 @@ * @type String * @default */ - fabric.Image.CSS_CANVAS = "canvas-img"; + fabric.Image.CSS_CANVAS = 'canvas-img'; /** * Alias for getSrc diff --git a/src/shapes/itext.class.js b/src/shapes/itext.class.js index 4e0ca63a..ca66b3ec 100644 --- a/src/shapes/itext.class.js +++ b/src/shapes/itext.class.js @@ -8,9 +8,9 @@ * @extends fabric.Text * @mixes fabric.Observable * - * @fires text:changed - * @fires editing:entered - * @fires editing:exited + * @fires changed ("text:changed" when observing canvas) + * @fires editing:entered ("text:editing:entered" when observing canvas) + * @fires editing:exited ("text:editing:exited" when observing canvas) * * @return {fabric.IText} thisArg * @see {@link fabric.IText#initialize} for constructor definition @@ -33,6 +33,7 @@ * Copy text: ctrl/cmd + c * Paste text: ctrl/cmd + v * Cut text: ctrl/cmd + x + * Select entire text: ctrl/cmd + a * * *

Supported mouse/touch combination

@@ -217,6 +218,9 @@ * @param {Number} index Index to set selection start to */ setSelectionStart: function(index) { + if (this.selectionStart !== index) { + this.canvas && this.canvas.fire('text:selection:changed', { target: this }); + } this.selectionStart = index; this.hiddenTextarea && (this.hiddenTextarea.selectionStart = index); }, @@ -226,6 +230,9 @@ * @param {Number} index Index to set selection end to */ setSelectionEnd: function(index) { + if (this.selectionEnd !== index) { + this.canvas && this.canvas.fire('text:selection:changed', { target: this }); + } this.selectionEnd = index; this.hiddenTextarea && (this.hiddenTextarea.selectionEnd = index); }, @@ -289,9 +296,9 @@ }, /** - * @private - * @param {CanvasRenderingContext2D} ctx Context to render on - */ + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ _render: function(ctx) { this.callSuper('_render', ctx); this.ctx = ctx; @@ -325,8 +332,8 @@ if (typeof selectionStart === 'undefined') { selectionStart = this.selectionStart; } - var textBeforeCursor = this.text.slice(0, selectionStart); - var linesBeforeCursor = textBeforeCursor.split(this._reNewline); + var textBeforeCursor = this.text.slice(0, selectionStart), + linesBeforeCursor = textBeforeCursor.split(this._reNewline); return { lineIndex: linesBeforeCursor.length - 1, @@ -334,6 +341,26 @@ }; }, + /** + * Returns complete style of char at the current cursor + * @param {Number} lineIndex Line index + * @param {Number} charIndex Char index + * @return {Object} Character style + */ + getCurrentCharStyle: function(lineIndex, charIndex) { + var style = this.styles[lineIndex] && this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)]; + + return { + fontSize: style && style.fontSize || this.fontSize, + fill: style && style.fill || this.fill, + textBackgroundColor: style && style.textBackgroundColor || this.textBackgroundColor, + textDecoration: style && style.textDecoration || this.textDecoration, + fontFamily: style && style.fontFamily || this.fontFamily, + stroke: style && style.stroke || this.stroke, + strokeWidth: style && style.strokeWidth || this.strokeWidth + }; + }, + /** * Returns fontSize of char at the current cursor * @param {Number} lineIndex Line index @@ -480,13 +507,16 @@ var cursorLocation = this.get2DCursorLocation(), lineIndex = cursorLocation.lineIndex, charIndex = cursorLocation.charIndex, - charHeight = this.getCurrentCharFontSize(lineIndex, charIndex); + charHeight = this.getCurrentCharFontSize(lineIndex, charIndex), + leftOffset = (lineIndex === 0 && charIndex === 0) + ? this._getCachedLineOffset(lineIndex, this.text.split(this._reNewline)) + : boundaries.leftOffset; ctx.fillStyle = this.getCurrentCharColor(lineIndex, charIndex); - ctx.globalAlpha = this._currentCursorOpacity; + ctx.globalAlpha = this.__isMousedown ? 1 : this._currentCursorOpacity; ctx.fillRect( - boundaries.left + boundaries.leftOffset, + boundaries.left + leftOffset, boundaries.top + boundaries.topOffset, this.cursorWidth / this.scaleX, charHeight); @@ -506,39 +536,43 @@ ctx.fillStyle = this.selectionColor; - var cursorLocation = this.get2DCursorLocation(), - lineIndex = cursorLocation.lineIndex, - charIndex = cursorLocation.charIndex, - textLines = this.text.split(this._reNewline), - origLineIndex = lineIndex; + var start = this.get2DCursorLocation(this.selectionStart), + end = this.get2DCursorLocation(this.selectionEnd), + startLine = start.lineIndex, + endLine = end.lineIndex, + textLines = this.text.split(this._reNewline); - for (var i = this.selectionStart; i < this.selectionEnd; i++) { + for (var i = startLine; i <= endLine; i++) { + var lineOffset = this._getCachedLineOffset(i, textLines) || 0, + lineHeight = this._getCachedLineHeight(i), + boxWidth = 0; - if (chars[i] === '\n') { - boundaries.leftOffset = 0; - boundaries.topOffset += this._getHeightOfLine(ctx, lineIndex); - lineIndex++; - charIndex = 0; - } - else if (i !== this.text.length) { - - var charWidth = this._getWidthOfChar(ctx, chars[i], lineIndex, charIndex), - lineOffset = this._getLineLeftOffset(this._getWidthOfLine(ctx, lineIndex, textLines)) || 0; - - if (lineIndex === origLineIndex) { - // only offset the line if we're rendering selection of 2nd, 3rd, etc. line - lineOffset = 0; + if (i === startLine) { + for (var j = 0, len = textLines[i].length; j < len; j++) { + if (j >= start.charIndex && (i !== endLine || j < end.charIndex)) { + boxWidth += this._getWidthOfChar(ctx, textLines[i][j], i, j); + } + if (j < start.charIndex) { + lineOffset += this._getWidthOfChar(ctx, textLines[i][j], i, j); + } } - - ctx.fillRect( - boundaries.left + boundaries.leftOffset + lineOffset, - boundaries.top + boundaries.topOffset, - charWidth, - this._getHeightOfLine(ctx, lineIndex)); - - boundaries.leftOffset += charWidth; - charIndex++; } + else if (i > startLine && i < endLine) { + boxWidth += this._getCachedLineWidth(i, textLines) || 5; + } + else if (i === endLine) { + for (var j2 = 0, j2len = end.charIndex; j2 < j2len; j2++) { + boxWidth += this._getWidthOfChar(ctx, textLines[i][j2], i, j2); + } + } + + ctx.fillRect( + boundaries.left + lineOffset, + boundaries.top + boundaries.topOffset, + boxWidth, + lineHeight); + + boundaries.topOffset += lineHeight; } ctx.restore(); }, @@ -568,14 +602,26 @@ lineWidth = this._getWidthOfLine(ctx, lineIndex, textLines), lineHeight = this._getHeightOfLine(ctx, lineIndex, textLines), lineLeftOffset = this._getLineLeftOffset(lineWidth), - chars = line.split(''); + chars = line.split(''), + prevStyle, + charsToRender = ''; left += lineLeftOffset || 0; ctx.save(); - for (var i = 0, len = chars.length; i < len; i++) { - this._renderChar(method, ctx, lineIndex, i, chars[i], left, top, lineHeight); + + for (var i = 0, len = chars.length; i <= len; i++) { + prevStyle = prevStyle || this.getCurrentCharStyle(lineIndex, i); + var thisStyle = this.getCurrentCharStyle(lineIndex, i + 1); + + if (this._hasStyleChanged(prevStyle, thisStyle) || i === len) { + this._renderChar(method, ctx, lineIndex, i - 1, charsToRender, left, top, lineHeight); + charsToRender = ''; + prevStyle = thisStyle; + } + charsToRender += chars[i]; } + ctx.restore(); }, @@ -638,6 +684,22 @@ } }, + /** + * @private + * @param {Object} prevStyle + * @param {Object} thisStyle + */ + _hasStyleChanged: function(prevStyle, thisStyle) { + return (prevStyle.fill !== thisStyle.fill || + prevStyle.fontSize !== thisStyle.fontSize || + prevStyle.textBackgroundColor !== thisStyle.textBackgroundColor || + prevStyle.textDecoration !== thisStyle.textDecoration || + prevStyle.fontFamily !== thisStyle.fontFamily || + prevStyle.stroke !== thisStyle.stroke || + prevStyle.strokeWidth !== thisStyle.strokeWidth + ); + }, + /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on @@ -645,10 +707,10 @@ _renderCharDecoration: function(ctx, styleDeclaration, left, top, charWidth, lineHeight, charHeight) { var textDecoration = styleDeclaration - ? (styleDeclaration.textDecoration || this.textDecoration) - : this.textDecoration; + ? (styleDeclaration.textDecoration || this.textDecoration) + : this.textDecoration, - var fontSize = (styleDeclaration ? styleDeclaration.fontSize : null) || this.fontSize; + fontSize = (styleDeclaration ? styleDeclaration.fontSize : null) || this.fontSize; if (!textDecoration) return; @@ -868,15 +930,35 @@ } }, + /** + * @private + * @param {Number} lineIndex + * @param {Number} charIndex + */ + _getStyleDeclaration: function(lineIndex, charIndex) { + return (this.styles[lineIndex] && this.styles[lineIndex][charIndex]) + ? clone(this.styles[lineIndex][charIndex]) + : { }; + }, + /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _getWidthOfChar: function(ctx, _char, lineIndex, charIndex) { - ctx.save(); - var width = this._applyCharStylesGetWidth(ctx, _char, lineIndex, charIndex); - ctx.restore(); - return width; + var styleDeclaration = this._getStyleDeclaration(lineIndex, charIndex); + this._applyFontStyles(styleDeclaration); + var cacheProp = this._getCacheProp(_char, styleDeclaration); + + if (this._charWidthsCache[cacheProp] && this.caching) { + return this._charWidthsCache[cacheProp]; + } + else if (ctx) { + ctx.save(); + var width = this._applyCharStylesGetWidth(ctx, _char, lineIndex, charIndex); + ctx.restore(); + return width; + } }, /** @@ -962,10 +1044,9 @@ textLines = textLines || this.text.split(this._reNewline); - var maxHeight = this._getHeightOfChar(ctx, textLines[lineIndex][0], lineIndex, 0); - - var line = textLines[lineIndex]; - var chars = line.split(''); + var maxHeight = this._getHeightOfChar(ctx, textLines[lineIndex][0], lineIndex, 0), + line = textLines[lineIndex], + chars = line.split(''); for (var i = 1, len = chars.length; i < len; i++) { var currentCharHeight = this._getHeightOfChar(ctx, chars[i], lineIndex, i); @@ -998,6 +1079,26 @@ return topOffset - (this.fontSize / this._fontSizeFraction); }, + /** + * @private + * This method is overwritten to account for different top offset + */ + _renderTextBoxBackground: function(ctx) { + if (!this.backgroundColor) return; + + ctx.save(); + ctx.fillStyle = this.backgroundColor; + + ctx.fillRect( + this._getLeftOffset(), + this._getTopOffset() + (this.fontSize / this._fontSizeFraction), + this.width, + this.height + ); + + ctx.restore(); + }, + /** * Returns object representation of an instance * @methd toObject @@ -1022,6 +1123,12 @@ return new fabric.IText(object.text, clone(object)); }; + /** + * Contains all fabric.IText objects that have been created + * @static + * @memberof fabric.IText + * @type Array + */ fabric.IText.instances = [ ]; })(); diff --git a/src/shapes/line.class.js b/src/shapes/line.class.js index 321ec458..52309ed9 100644 --- a/src/shapes/line.class.js +++ b/src/shapes/line.class.js @@ -1,10 +1,10 @@ (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend, - coordProps = { 'x1': 1, 'x2': 1, 'y1': 1, 'y2': 1 }, + coordProps = { x1: 1, x2: 1, y1: 1, y2: 1 }, supportsLineDash = fabric.StaticCanvas.supports('setLineDash'); if (fabric.Line) { @@ -27,6 +27,34 @@ */ type: 'line', + /** + * x value or first line edge + * @type Number + * @default + */ + x1: 0, + + /** + * y value or first line edge + * @type Number + * @default + */ + y1: 0, + + /** + * x value or second line edge + * @type Number + * @default + */ + x2: 0, + + /** + * y value or second line edge + * @type Number + * @default + */ + y2: 0, + /** * Constructor * @param {Array} [points] Array of points @@ -57,11 +85,16 @@ _setWidthHeight: function(options) { options || (options = { }); - this.set('width', Math.abs(this.x2 - this.x1) || 1); - this.set('height', Math.abs(this.y2 - this.y1) || 1); + this.width = Math.abs(this.x2 - this.x1) || 1; + this.height = Math.abs(this.y2 - this.y1) || 1; - this.set('left', 'left' in options ? options.left : (Math.min(this.x1, this.x2) + this.width / 2)); - this.set('top', 'top' in options ? options.top : (Math.min(this.y1, this.y2) + this.height / 2)); + this.left = 'left' in options + ? options.left + : this._getLeftToOriginX(); + + this.top = 'top' in options + ? options.top + : this._getTopToOriginY(); }, /** @@ -71,12 +104,48 @@ */ _set: function(key, value) { this[key] = value; - if (key in coordProps) { + if (typeof coordProps[key] !== 'undefined') { this._setWidthHeight(); } return this; }, + /** + * @private + * @return {Number} leftToOriginX Distance from left edge of canvas to originX of Line. + */ + _getLeftToOriginX: makeEdgeToOriginGetter( + { // property names + origin: 'originX', + axis1: 'x1', + axis2: 'x2', + dimension: 'width' + }, + { // possible values of origin + nearest: 'left', + center: 'center', + farthest: 'right' + } + ), + + /** + * @private + * @return {Number} topToOriginY Distance from top edge of canvas to originY of Line. + */ + _getTopToOriginY: makeEdgeToOriginGetter( + { // property names + origin: 'originY', + axis1: 'y1', + axis2: 'y2', + dimension: 'height' + }, + { // possible values of origin + nearest: 'top', + center: 'center', + farthest: 'bottom' + } + ), + /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on @@ -86,15 +155,23 @@ var isInPathGroup = this.group && this.group.type === 'path-group'; if (isInPathGroup && !this.transformMatrix) { - ctx.translate(-this.group.width/2 + this.left, -this.group.height / 2 + this.top); + // Line coords are distances from left-top of canvas to origin of line. + // + // To render line in a path-group, we need to translate them to + // distances from center of path-group to center of line. + var cp = this.getCenterPoint(); + ctx.translate( + -this.group.width/2 + cp.x, + -this.group.height / 2 + cp.y + ); } if (!this.strokeDashArray || this.strokeDashArray && supportsLineDash) { // move from center (of virtual box) to its left/top corner // we can't assume x1, y1 is top left and x2, y2 is bottom right - var xMult = this.x1 <= this.x2 ? -1 : 1; - var yMult = this.y1 <= this.y2 ? -1 : 1; + var xMult = this.x1 <= this.x2 ? -1 : 1, + yMult = this.y1 <= this.y2 ? -1 : 1; ctx.moveTo( this.width === 1 ? 0 : (xMult * this.width / 2), @@ -112,7 +189,7 @@ // (by copying fillStyle to strokeStyle, since line is stroked, not filled) var origStrokeStyle = ctx.strokeStyle; ctx.strokeStyle = this.stroke || ctx.fillStyle; - this._renderStroke(ctx); + this.stroke && this._renderStroke(ctx); ctx.strokeStyle = origStrokeStyle; }, @@ -197,13 +274,13 @@ * @return {fabric.Line} instance of fabric.Line */ fabric.Line.fromElement = function(element, options) { - var parsedAttributes = fabric.parseAttributes(element, fabric.Line.ATTRIBUTE_NAMES); - var points = [ - parsedAttributes.x1 || 0, - parsedAttributes.y1 || 0, - parsedAttributes.x2 || 0, - parsedAttributes.y2 || 0 - ]; + var parsedAttributes = fabric.parseAttributes(element, fabric.Line.ATTRIBUTE_NAMES), + points = [ + parsedAttributes.x1 || 0, + parsedAttributes.y1 || 0, + parsedAttributes.x2 || 0, + parsedAttributes.y2 || 0 + ]; return new fabric.Line(points, extend(parsedAttributes, options)); }; /* _FROM_SVG_END_ */ @@ -220,4 +297,29 @@ return new fabric.Line(points, object); }; + /** + * Produces a function that calculates distance from canvas edge to Line origin. + */ + function makeEdgeToOriginGetter(propertyNames, originValues) { + var origin = propertyNames.origin, + axis1 = propertyNames.axis1, + axis2 = propertyNames.axis2, + dimension = propertyNames.dimension, + nearest = originValues.nearest, + center = originValues.center, + farthest = originValues.farthest; + + return function() { + switch (this.get(origin)) { + case nearest: + return Math.min(this.get(axis1), this.get(axis2)); + case center: + return Math.min(this.get(axis1), this.get(axis2)) + (0.5 * this.get(dimension)); + case farthest: + return Math.max(this.get(axis1), this.get(axis2)); + } + }; + + } + })(typeof exports !== 'undefined' ? exports : this); diff --git a/src/shapes/object.class.js b/src/shapes/object.class.js index 989f5e7c..407c4975 100644 --- a/src/shapes/object.class.js +++ b/src/shapes/object.class.js @@ -1,6 +1,6 @@ (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend, @@ -611,6 +611,7 @@ /** * Function that determines clipping of an object (context is passed as a first argument) + * Note that context origin is at the object's center point (not left/top corner) * @type Function */ clipTo: null, @@ -752,34 +753,34 @@ */ toObject: function(propertiesToInclude) { - var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS; + var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, - var object = { - type: this.type, - originX: this.originX, - originY: this.originY, - left: toFixed(this.left, NUM_FRACTION_DIGITS), - top: toFixed(this.top, NUM_FRACTION_DIGITS), - width: toFixed(this.width, NUM_FRACTION_DIGITS), - height: toFixed(this.height, NUM_FRACTION_DIGITS), - fill: (this.fill && this.fill.toObject) ? this.fill.toObject() : this.fill, - stroke: (this.stroke && this.stroke.toObject) ? this.stroke.toObject() : this.stroke, - strokeWidth: toFixed(this.strokeWidth, NUM_FRACTION_DIGITS), - strokeDashArray: this.strokeDashArray, - strokeLineCap: this.strokeLineCap, - strokeLineJoin: this.strokeLineJoin, - strokeMiterLimit: toFixed(this.strokeMiterLimit, NUM_FRACTION_DIGITS), - scaleX: toFixed(this.scaleX, NUM_FRACTION_DIGITS), - scaleY: toFixed(this.scaleY, NUM_FRACTION_DIGITS), - angle: toFixed(this.getAngle(), NUM_FRACTION_DIGITS), - flipX: this.flipX, - flipY: this.flipY, - opacity: toFixed(this.opacity, NUM_FRACTION_DIGITS), - shadow: (this.shadow && this.shadow.toObject) ? this.shadow.toObject() : this.shadow, - visible: this.visible, - clipTo: this.clipTo && String(this.clipTo), - backgroundColor: this.backgroundColor - }; + object = { + type: this.type, + originX: this.originX, + originY: this.originY, + left: toFixed(this.left, NUM_FRACTION_DIGITS), + top: toFixed(this.top, NUM_FRACTION_DIGITS), + width: toFixed(this.width, NUM_FRACTION_DIGITS), + height: toFixed(this.height, NUM_FRACTION_DIGITS), + fill: (this.fill && this.fill.toObject) ? this.fill.toObject() : this.fill, + stroke: (this.stroke && this.stroke.toObject) ? this.stroke.toObject() : this.stroke, + strokeWidth: toFixed(this.strokeWidth, NUM_FRACTION_DIGITS), + strokeDashArray: this.strokeDashArray, + strokeLineCap: this.strokeLineCap, + strokeLineJoin: this.strokeLineJoin, + strokeMiterLimit: toFixed(this.strokeMiterLimit, NUM_FRACTION_DIGITS), + scaleX: toFixed(this.scaleX, NUM_FRACTION_DIGITS), + scaleY: toFixed(this.scaleY, NUM_FRACTION_DIGITS), + angle: toFixed(this.getAngle(), NUM_FRACTION_DIGITS), + flipX: this.flipX, + flipY: this.flipY, + opacity: toFixed(this.opacity, NUM_FRACTION_DIGITS), + shadow: (this.shadow && this.shadow.toObject) ? this.shadow.toObject() : this.shadow, + visible: this.visible, + clipTo: this.clipTo && String(this.clipTo), + backgroundColor: this.backgroundColor + }; if (!this.includeDefaultValues) { object = this._removeDefaultValues(object); @@ -805,8 +806,8 @@ * @param {Object} object */ _removeDefaultValues: function(object) { - var prototype = fabric.util.getKlass(object.type).prototype; - var stateProperties = prototype.stateProperties; + var prototype = fabric.util.getKlass(object.type).prototype, + stateProperties = prototype.stateProperties; stateProperties.forEach(function(prop) { if (object[prop] === prototype[prop]) { @@ -822,7 +823,7 @@ * @return {String} */ toString: function() { - return "#"; + return '#'; }, /** @@ -834,6 +835,15 @@ return this[property]; }, + /** + * @private + */ + _setObject: function(obj) { + for (var prop in obj) { + this._set(prop, obj[prop]); + } + }, + /** * Sets property to a given value. When changing position/dimension -related properties (left, top, scale, angle, etc.) `set` does not update position of object's borders/controls. If you need to update those, call `setCoords()`. * @param {String|Object} key Property name or object (if object, iterate over the object properties) @@ -843,9 +853,7 @@ */ set: function(key, value) { if (typeof key === 'object') { - for (var prop in key) { - this._set(prop, key[prop]); - } + this._setObject(key); } else { if (typeof value === 'function' && key !== 'clipTo') { @@ -915,6 +923,18 @@ return this; }, + /** + * Retrieves viewportTransform from Object's canvas if possible + * @method getViewportTransform + * @memberOf fabric.Object.prototype + * @return {Boolean} flipY value // TODO + */ + getViewportTransform: function() { + if (this.canvas && this.canvas.viewportTransform) + return this.canvas.viewportTransform; + return [1, 0, 0, 1, 0, 0]; + }, + /** * Renders an object on a specified context * @param {CanvasRenderingContext2D} ctx Context to render on @@ -926,6 +946,9 @@ ctx.save(); + //setup fill rule for current object + this._setupFillRule(ctx); + this._transform(ctx, noTransform); this._setStrokeStyles(ctx); this._setFillStyles(ctx); @@ -941,6 +964,8 @@ this._render(ctx, noTransform); this.clipTo && ctx.restore(); this._removeShadow(ctx); + this._restoreFillRule(ctx); + ctx.restore(); this._renderControls(ctx, noTransform); @@ -948,7 +973,7 @@ _transform: function(ctx, noTransform) { var m = this.transformMatrix; - var v = this.canvas.viewportTransform; + var v = this.getViewportTransform(); ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); @@ -986,7 +1011,7 @@ * @param {Boolean} [noTransform] When true, context is not transformed */ _renderControls: function(ctx, noTransform) { - var v = this.canvas.viewportTransform; + var v = this.getViewportTransform(); ctx.save(); if (this.active && !noTransform) { @@ -1027,6 +1052,8 @@ * @param {CanvasRenderingContext2D} ctx Context to render on */ _removeShadow: function(ctx) { + if (!this.shadow) return; + ctx.shadowColor = ''; ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0; }, @@ -1044,7 +1071,12 @@ -this.width / 2 + this.fill.offsetX || 0, -this.height / 2 + this.fill.offsetY || 0); } - ctx.fill(); + if (this.fillRule === 'destination-over') { + ctx.fill('evenodd'); + } + else { + ctx.fill(); + } if (this.fill.toLive) { ctx.restore(); } @@ -1243,7 +1275,7 @@ setGradient: function(property, options) { options || (options = { }); - var gradient = {colorStops: []}; + var gradient = { colorStops: [] }; gradient.type = options.type || (options.r1 || options.r2 ? 'radial' : 'linear'); gradient.coords = { @@ -1260,7 +1292,11 @@ for (var position in options.colorStops) { var color = new fabric.Color(options.colorStops[position]); - gradient.colorStops.push({offset: position, color: color.toRgb(), opacity: color.getAlpha()}); + gradient.colorStops.push({ + offset: position, + color: color.toRgb(), + opacity: color.getAlpha() + }); } return this.set(property, fabric.Gradient.forObject(this, gradient)); @@ -1318,7 +1354,7 @@ /** * Sets "color" of an instance (alias of `set('fill', …)`) * @param {String} color Color value - * @return {fabric.Text} thisArg + * @return {fabric.Object} thisArg * @chainable */ setColor: function(color) { @@ -1326,6 +1362,28 @@ return this; }, + /** + * Sets "angle" of an instance + * @param {Number} angle Angle value + * @return {fabric.Object} thisArg + * @chainable + */ + setAngle: function(angle) { + var shouldCenterOrigin = (this.originX !== 'center' || this.originY !== 'center') && this.centeredRotation; + + if (shouldCenterOrigin) { + this._setOriginToCenter(); + } + + this.set('angle', angle); + + if (shouldCenterOrigin) { + this._resetOrigin(); + } + + return this; + }, + /** * Centers object horizontally on canvas to which it was added last. * You might need to call `setCoords` on an object after centering, to update controls area. @@ -1365,7 +1423,8 @@ * @chainable */ remove: function() { - return this.canvas.remove(this); + this.canvas.remove(this); + return this; }, /** @@ -1381,6 +1440,28 @@ x: pointer.x - objectLeftTop.x, y: pointer.y - objectLeftTop.y }; + }, + + /** + * Sets canvas globalCompositeOperation for specific object + * custom composition operation for the particular object can be specifed using fillRule property + * @param {CanvasRenderingContext2D} ctx Rendering canvas context + */ + _setupFillRule: function (ctx) { + if (this.fillRule) { + this._prevFillRule = ctx.globalCompositeOperation; + ctx.globalCompositeOperation = this.fillRule; + } + }, + + /** + * Restores previously saved canvas globalCompositeOperation after obeject rendering + * @param {CanvasRenderingContext2D} ctx Rendering canvas context + */ + _restoreFillRule: function (ctx) { + if (this.fillRule && this._prevFillRule) { + ctx.globalCompositeOperation = this._prevFillRule; + } } }); diff --git a/src/shapes/path.class.js b/src/shapes/path.class.js index fc9b6207..1d6db96e 100644 --- a/src/shapes/path.class.js +++ b/src/shapes/path.class.js @@ -1,25 +1,24 @@ (function(global) { - var commandLengths = { - m: 2, - l: 2, - h: 1, - v: 1, - c: 6, - s: 4, - q: 4, - t: 2, - a: 7 - }; - - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), min = fabric.util.array.min, max = fabric.util.array.max, extend = fabric.util.object.extend, _toString = Object.prototype.toString, - drawArc = fabric.util.drawArc; + drawArc = fabric.util.drawArc, + commandLengths = { + m: 2, + l: 2, + h: 1, + v: 1, + c: 6, + s: 4, + q: 4, + t: 2, + a: 7 + }; if (fabric.Path) { fabric.warn('fabric.Path is already defined'); @@ -62,6 +61,13 @@ */ type: 'path', + /** + * Array of path points + * @type Array + * @default + */ + path: null, + /** * Constructor * @param {Array|String} path Path data (sequence of coordinates and corresponding "command" tokens) @@ -125,7 +131,9 @@ this.left = this.width / 2; } } - this.pathOffset = this.pathOffset || this._calculatePathOffset(origLeft, origTop); //Save top-left coords as offset + this.pathOffset = this.pathOffset || + // Save top-left coords as offset + this._calculatePathOffset(origLeft, origTop); }, /** @@ -281,8 +289,8 @@ tempX = current[3]; tempY = current[4]; // calculate reflection of previous control points - controlX = 2*x - controlX; - controlY = 2*y - controlY; + controlX = 2 * x - controlX; + controlY = 2 * y - controlY; ctx.bezierCurveTo( controlX + l, controlY + t, @@ -343,7 +351,6 @@ tempX = x + current[1]; tempY = y + current[2]; - if (previous[0].match(/[QqTt]/) === null) { // If there is no previous command or if the previous command was not a Q, q, T or t, // assume the control point is coincident with the current point @@ -444,7 +451,7 @@ ctx.save(); var m = this.transformMatrix; - var v = this.canvas.viewportTransform; + var v = this.getViewportTransform(); ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); if (m) { @@ -485,7 +492,7 @@ */ toObject: function(propertiesToInclude) { var o = extend(this.callSuper('toObject', propertiesToInclude), { - path: this.path, + path: this.path.map(function(item) { return item.slice() }), pathOffset: this.pathOffset }); if (this.sourcePath) { @@ -557,7 +564,7 @@ coords = [ ], currentPath, parsed, - re = /(-?\.\d+)|(-?\d+(\.\d+)?)/g, + re = /([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/ig, match, coordsStr; @@ -613,14 +620,14 @@ maxX = max(aX), maxY = max(aY), deltaX = maxX - minX, - deltaY = maxY - minY; + deltaY = maxY - minY, - var o = { - left: this.left + (minX + deltaX / 2), - top: this.top + (minY + deltaY / 2), - width: deltaX, - height: deltaY - }; + o = { + left: this.left + (minX + deltaX / 2), + top: this.top + (minY + deltaY / 2), + width: deltaX, + height: deltaY + }; return o; }, @@ -641,13 +648,18 @@ isLowerCase = true; } - var xy = this._getXY(item, isLowerCase, previous); + var xy = this._getXY(item, isLowerCase, previous), + val; - var val = parseInt(xy.x, 10); - if (!isNaN(val)) aX.push(val); + val = parseInt(xy.x, 10); + if (!isNaN(val)) { + aX.push(val); + } val = parseInt(xy.y, 10); - if (!isNaN(val)) aY.push(val); + if (!isNaN(val)) { + aY.push(val); + } }, _getXY: function(item, isLowerCase, previous) { @@ -659,13 +671,13 @@ ? previous.x + getX(item) : item[0] === 'V' ? previous.x - : getX(item); + : getX(item), - var y = isLowerCase - ? previous.y + getY(item) - : item[0] === 'H' - ? previous.y - : getY(item); + y = isLowerCase + ? previous.y + getY(item) + : item[0] === 'H' + ? previous.y + : getY(item); return { x: x, y: y }; } @@ -681,9 +693,9 @@ fabric.Path.fromObject = function(object, callback) { if (typeof object.path === 'string') { fabric.loadSVGFromURL(object.path, function (elements) { - var path = elements[0]; + var path = elements[0], + pathUrl = object.path; - var pathUrl = object.path; delete object.path; fabric.util.object.extend(path, object); diff --git a/src/shapes/path_group.class.js b/src/shapes/path_group.class.js index a7ea505f..7c1acbab 100644 --- a/src/shapes/path_group.class.js +++ b/src/shapes/path_group.class.js @@ -1,6 +1,6 @@ (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend, @@ -52,6 +52,15 @@ this.setOptions(options); + if (options.widthAttr) { + this.scaleX = options.widthAttr / options.width; + } + if (options.heightAttr) { + this.scaleY = options.heightAttr / options.height; + } + + this.setCoords(); + if (options.sourcePath) { this.setSourcePath(options.sourcePath); } @@ -69,7 +78,7 @@ var m = this.transformMatrix; - var v = this.canvas.viewportTransform; + var v = this.getViewportTransform(); ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); if (m) { @@ -143,13 +152,13 @@ * @return {String} svg representation of an instance */ toSVG: function(reviver) { - var objects = this.getObjects(); - var markup = [ - '' - ]; + var objects = this.getObjects(), + markup = [ + '' + ]; for (var i = 0, len = objects.length; i < len; i++) { markup.push(objects[i].toSVG(reviver)); @@ -174,9 +183,9 @@ * @return {Boolean} true if all paths are of the same color (`fill`) */ isSameColor: function() { - var firstPathFill = this.getObjects()[0].get('fill'); + var firstPathFill = (this.getObjects()[0].get('fill') || '').toLowerCase(); return this.getObjects().every(function(path) { - return path.get('fill') === firstPathFill; + return (path.get('fill') || '').toLowerCase() === firstPathFill; }); }, diff --git a/src/shapes/polygon.class.js b/src/shapes/polygon.class.js index c111d7bd..cf386e55 100644 --- a/src/shapes/polygon.class.js +++ b/src/shapes/polygon.class.js @@ -1,6 +1,6 @@ (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend, @@ -28,6 +28,13 @@ */ type: 'polygon', + /** + * Points array + * @type Array + * @default + */ + points: null, + /** * Constructor * @param {Array} points Array of points @@ -138,7 +145,7 @@ ctx.beginPath(); for (var i = 0, len = this.points.length; i < len; i++) { p1 = this.points[i]; - p2 = this.points[i+1] || this.points[0]; + p2 = this.points[i + 1] || this.points[0]; fabric.util.drawDashedLine(ctx, p1.x, p1.y, p2.x, p2.y, this.strokeDashArray); } ctx.closePath(); diff --git a/src/shapes/polyline.class.js b/src/shapes/polyline.class.js index b6f8f32a..c5a750d0 100644 --- a/src/shapes/polyline.class.js +++ b/src/shapes/polyline.class.js @@ -1,6 +1,6 @@ (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), toFixed = fabric.util.toFixed; @@ -25,6 +25,13 @@ */ type: 'polyline', + /** + * Points array + * @type Array + * @default + */ + points: null, + /** * Constructor * @param {Array} points Array of points (where each point is an object with x and y) @@ -122,7 +129,7 @@ ctx.beginPath(); for (var i = 0, len = this.points.length; i < len; i++) { p1 = this.points[i]; - p2 = this.points[i+1] || p1; + p2 = this.points[i + 1] || p1; fabric.util.drawDashedLine(ctx, p1.x, p1.y, p2.x, p2.y, this.strokeDashArray); } }, diff --git a/src/shapes/rect.class.js b/src/shapes/rect.class.js index d882674b..184b90e0 100644 --- a/src/shapes/rect.class.js +++ b/src/shapes/rect.class.js @@ -1,6 +1,6 @@ (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend; @@ -101,13 +101,22 @@ * @param ctx {CanvasRenderingContext2D} context to render on */ _render: function(ctx) { - var rx = this.rx || 0, - ry = this.ry || 0, - x = -this.width / 2, - y = -this.height / 2, + + // optimize 1x1 case (used in spray brush) + if (this.width === 1 && this.height === 1) { + ctx.fillRect(0, 0, 1, 1); + return; + } + + var rx = this.rx ? Math.min(this.rx, this.width / 2) : 0, + ry = this.ry ? Math.min(this.ry, this.height / 2) : 0, w = this.width, h = this.height, - isInPathGroup = this.group && this.group.type === 'path-group'; + x = -w / 2, + y = -h / 2, + isInPathGroup = this.group && this.group.type === 'path-group', + isRounded = rx !== 0 || ry !== 0, + k = 1 - 0.5522847498 /* "magic number" for bezier approximations of arcs (http://itc.ktu.lt/itc354/Riskus354.pdf) */; ctx.beginPath(); ctx.globalAlpha = isInPathGroup ? (ctx.globalAlpha * this.opacity) : this.opacity; @@ -123,17 +132,20 @@ -this.group.height / 2 + this.height / 2 + this.y); } - var isRounded = rx !== 0 || ry !== 0; + ctx.moveTo(x + rx, y); + + ctx.lineTo(x + w - rx, y); + isRounded && ctx.bezierCurveTo(x + w - k * rx, y, x + w, y + k * ry, x + w, y + ry); + + ctx.lineTo(x + w, y + h - ry); + isRounded && ctx.bezierCurveTo(x + w, y + h - k * ry, x + w - k * rx, y + h, x + w - rx, y + h); + + ctx.lineTo(x + rx, y + h); + isRounded && ctx.bezierCurveTo(x + k * rx, y + h, x, y + h - k * ry, x, y + h - ry); + + ctx.lineTo(x, y + ry); + isRounded && ctx.bezierCurveTo(x, y + k * ry, x + k * rx, y, x + rx, y); - ctx.moveTo(x+rx, y); - ctx.lineTo(x+w-rx, y); - isRounded && ctx.quadraticCurveTo(x+w, y, x+w, y+ry, x+w, y+ry); - ctx.lineTo(x+w, y+h-ry); - isRounded && ctx.quadraticCurveTo(x+w,y+h,x+w-rx,y+h,x+w-rx,y+h); - ctx.lineTo(x+rx,y+h); - isRounded && ctx.quadraticCurveTo(x,y+h,x,y+h-ry,x,y+h-ry); - ctx.lineTo(x,y+ry); - isRounded && ctx.quadraticCurveTo(x,y,x+rx,y,x+rx,y); ctx.closePath(); this._renderFill(ctx); @@ -145,16 +157,16 @@ * @param ctx {CanvasRenderingContext2D} context to render on */ _renderDashedStroke: function(ctx) { - var x = -this.width/2, - y = -this.height/2, - w = this.width, - h = this.height; + var x = -this.width / 2, + y = -this.height / 2, + w = this.width, + h = this.height; ctx.beginPath(); - fabric.util.drawDashedLine(ctx, x, y, x+w, y, this.strokeDashArray); - fabric.util.drawDashedLine(ctx, x+w, y, x+w, y+h, this.strokeDashArray); - fabric.util.drawDashedLine(ctx, x+w, y+h, x, y+h, this.strokeDashArray); - fabric.util.drawDashedLine(ctx, x, y+h, x, y, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, x, y, x + w, y, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, x + w, y, x + w, y + h, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, x + w, y + h, x, y + h, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, x, y + h, x, y, this.strokeDashArray); ctx.closePath(); }, @@ -208,8 +220,7 @@ '" width="', this.width, '" height="', this.height, '" style="', this.getSvgStyles(), '" transform="', this.getSvgTransform(), - '"/>' - ); + '"/>'); return reviver ? reviver(markup.join('')) : markup.join(''); }, diff --git a/src/shapes/text.class.js b/src/shapes/text.class.js index 7f193d3e..a942ad00 100644 --- a/src/shapes/text.class.js +++ b/src/shapes/text.class.js @@ -1,6 +1,6 @@ (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend, @@ -427,8 +427,8 @@ for (var i = 0, len = textLines.length; i < len; i++) { - var lineWidth = this._getLineWidth(ctx, textLines[i]); - var lineLeftOffset = this._getLineLeftOffset(lineWidth); + var lineWidth = this._getLineWidth(ctx, textLines[i]), + lineLeftOffset = this._getLineLeftOffset(lineWidth); this._boundaries.push({ height: this.fontSize * this.lineHeight, @@ -511,18 +511,18 @@ return; } - var lineWidth = ctx.measureText(line).width; - var totalWidth = this.width; + var lineWidth = ctx.measureText(line).width, + totalWidth = this.width; if (totalWidth > lineWidth) { // stretch the line - var words = line.split(/\s+/); - var wordsWidth = ctx.measureText(line.replace(/\s+/g, '')).width; - var widthDiff = totalWidth - wordsWidth; - var numSpaces = words.length - 1; - var spaceWidth = widthDiff / numSpaces; + var words = line.split(/\s+/), + wordsWidth = ctx.measureText(line.replace(/\s+/g, '')).width, + widthDiff = totalWidth - wordsWidth, + numSpaces = words.length - 1, + spaceWidth = widthDiff / numSpaces, + leftOffset = 0; - var leftOffset = 0; for (var i = 0, len = words.length; i < len; i++) { this._renderChars(method, ctx, words[i], left + leftOffset, top, lineIndex); leftOffset += ctx.measureText(words[i]).width + spaceWidth; @@ -664,8 +664,8 @@ if (textLines[i] !== '') { - var lineWidth = this._getLineWidth(ctx, textLines[i]); - var lineLeftOffset = this._getLineLeftOffset(lineWidth); + var lineWidth = this._getLineWidth(ctx, textLines[i]), + lineLeftOffset = this._getLineLeftOffset(lineWidth); ctx.fillRect( this._getLeftOffset() + lineLeftOffset, @@ -714,15 +714,15 @@ if (!this.textDecoration) return; // var halfOfVerticalBox = this.originY === 'top' ? 0 : this._getTextHeight(ctx, textLines) / 2; - var halfOfVerticalBox = this._getTextHeight(ctx, textLines) / 2; - var _this = this; + var halfOfVerticalBox = this._getTextHeight(ctx, textLines) / 2, + _this = this; /** @ignore */ function renderLinesAtOffset(offset) { for (var i = 0, len = textLines.length; i < len; i++) { - var lineWidth = _this._getLineWidth(ctx, textLines[i]); - var lineLeftOffset = _this._getLineLeftOffset(lineWidth); + var lineWidth = _this._getLineWidth(ctx, textLines[i]), + lineLeftOffset = _this._getLineLeftOffset(lineWidth); ctx.fillRect( _this._getLeftOffset() + lineLeftOffset, @@ -766,8 +766,13 @@ if (!this.visible) return; ctx.save(); - var v = this.canvas.viewportTransform; + var v = this.getViewportTransform(); ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + + var m = this.transformMatrix; + if (m && (!this.group || this.group.type === 'path-group')) { + ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + } this._render(ctx); ctx.restore(); @@ -953,7 +958,7 @@ (i === 0 || this.useNative ? 'y' : 'dy'), '="', toFixed(this.useNative ? ((lineHeight * i) - this.height / 2) - : (lineHeight * lineTopOffsetMultiplier), 2) , '" ', + : (lineHeight * lineTopOffsetMultiplier), 2), '" ', // doing this on elements since setting opacity // on containing one doesn't work in Illustrator this._getFillAttributes(this.fill), '>', @@ -1048,7 +1053,14 @@ * @see: http://www.w3.org/TR/SVG/text.html#TextElement */ fabric.Text.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat( - 'x y font-family font-style font-weight font-size text-decoration'.split(' ')); + 'x y dx dy font-family font-style font-weight font-size text-decoration text-anchor'.split(' ')); + + /** + * Default SVG font size + * @static + * @memberOf fabric.Text + */ + fabric.Text.DEFAULT_SVG_FONT_SIZE = 16; /** * Returns fabric.Text instance from an SVG element (not yet implemented) @@ -1066,6 +1078,20 @@ var parsedAttributes = fabric.parseAttributes(element, fabric.Text.ATTRIBUTE_NAMES); options = fabric.util.object.extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes); + if ('dx' in parsedAttributes) { + options.left += parsedAttributes.dx; + } + if ('dy' in parsedAttributes) { + options.top += parsedAttributes.dy; + } + if (!('fontSize' in options)) { + options.fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE; + } + + if (!options.originX) { + options.originX = 'center'; + } + var text = new fabric.Text(element.textContent, options); /* diff --git a/src/shapes/triangle.class.js b/src/shapes/triangle.class.js index 36ed6636..12eaebb8 100644 --- a/src/shapes/triangle.class.js +++ b/src/shapes/triangle.class.js @@ -1,6 +1,6 @@ (function(global) { - "use strict"; + 'use strict'; var fabric = global.fabric || (global.fabric = { }); @@ -81,13 +81,13 @@ toSVG: function(reviver) { var markup = this._createBaseSVGMarkup(), widthBy2 = this.width / 2, - heightBy2 = this.height / 2; - - var points = [ - -widthBy2 + " " + heightBy2, - "0 " + -heightBy2, - widthBy2 + " " + heightBy2 - ].join(","); + heightBy2 = this.height / 2, + points = [ + -widthBy2 + ' ' + heightBy2, + '0 ' + -heightBy2, + widthBy2 + ' ' + heightBy2 + ] + .join(','); markup.push( ' -1) { + if (property in klass.prototype && + typeof klass.prototype[property] === 'function' && + (source[property] + '').indexOf('callSuper') > -1) { - klass.prototype[property] = (function(property) { - return function() { + 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; + 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; + 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; } - }; - })(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; + } + } } - if (source.valueOf !== Object.prototype.valueOf) { - klass.prototype.valueOf = source.valueOf; - } - } - } - }; + }; function Subclass() { } diff --git a/src/util/lang_function.js b/src/util/lang_function.js index 10a99358..594f10a1 100644 --- a/src/util/lang_function.js +++ b/src/util/lang_function.js @@ -14,16 +14,16 @@ * @return {Function} */ Function.prototype.bind = function(thisArg) { - var fn = this, args = slice.call(arguments, 1), bound; + var _this = 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))); + return apply.call(_this, this instanceof Dummy ? this : thisArg, args.concat(slice.call(arguments))); }; } else { /** @ignore */ bound = function() { - return apply.call(fn, this instanceof Dummy ? this : thisArg, arguments); + return apply.call(_this, this instanceof Dummy ? this : thisArg, arguments); }; } Dummy.prototype = this.prototype; diff --git a/src/util/lang_string.js b/src/util/lang_string.js index cf4d2239..708e9b25 100644 --- a/src/util/lang_string.js +++ b/src/util/lang_string.js @@ -1,66 +1,66 @@ (function() { -/* _ES5_COMPAT_START_ */ -if (!String.prototype.trim) { + /* _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_ */ + /** - * Trims a string (removing whitespace from the beginning and the end) - * @function external:String#trim - * @see String#trim on MDN + * Camelizes a string + * @memberOf fabric.util.string + * @param {String} string String to camelize + * @return {String} Camelized version of a string */ - 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]+$/, ''); + 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 }; -} -/* _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 -}; }()); diff --git a/src/util/misc.js b/src/util/misc.js index b905c0fb..0bd8e2ca 100644 --- a/src/util/misc.js +++ b/src/util/misc.js @@ -162,7 +162,9 @@ * @return {Object} Object for given namespace (default fabric) */ resolveNamespace: function(namespace) { - if (!namespace) return fabric; + if (!namespace) { + return fabric; + } var parts = namespace.split('.'), len = parts.length, @@ -324,7 +326,7 @@ drawDashedLine: function(ctx, x, y, x2, y2, da) { var dx = x2 - x, dy = y2 - y, - len = sqrt(dx*dx + dy*dy), + len = sqrt(dx * dx + dy * dy), rot = atan2(dy, dx), dc = da.length, di = 0, @@ -432,22 +434,23 @@ var a = [ [matrixA[0], matrixA[2], matrixA[4]], [matrixA[1], matrixA[3], matrixA[5]], - [0 , 0 , 1 ] - ]; + [0, 0, 1 ] + ], - var b = [ + b = [ [matrixB[0], matrixB[2], matrixB[4]], [matrixB[1], matrixB[3], matrixB[5]], - [0 , 0 , 1 ] - ]; + [0, 0, 1 ] + ], - var result = []; - for (var r=0; r<3; r++) { + result = []; + + for (var r = 0; r < 3; r++) { result[r] = []; - for (var c=0; c<3; c++) { + for (var c = 0; c < 3; c++) { var sum = 0; - for (var k=0; k<3; k++) { - sum += a[r][k]*b[k][c]; + for (var k = 0; k < 3; k++) { + sum += a[r][k] * b[k][c]; } result[r][c] = sum; @@ -520,9 +523,8 @@ } } - var _isTransparent = true; - var imageData = ctx.getImageData( - x, y, (tolerance * 2) || 1, (tolerance * 2) || 1); + var _isTransparent = true, + imageData = ctx.getImageData(x, y, (tolerance * 2) || 1, (tolerance * 2) || 1); // Split image data - for tolerance > 1, pixelDataSize = 4; for (var i = 3, l = imageData.data.length; i < l; i += 4) { diff --git a/test.js b/test.js index cb9589b9..e221fde6 100644 --- a/test.js +++ b/test.js @@ -4,9 +4,11 @@ testrunner.options.log.summary = true; testrunner.options.log.tests = false; testrunner.options.log.assertions = false; +testrunner.options.coverage = true; + testrunner.run({ deps: "./test/fixtures/test_script.js", - code: "./dist/all.js", + code: "./dist/fabric.js", tests: [ './test/unit/rect.js', './test/unit/ellipse.js', diff --git a/test/lib/event.simulate.js b/test/lib/event.simulate.js index 290f873d..74194a8d 100644 --- a/test/lib/event.simulate.js +++ b/test/lib/event.simulate.js @@ -1,6 +1,6 @@ /** * simulateEvent(@element, eventName[, options]) -> Element - * + * * - @element: element to fire event on * - eventName: name of event to fire (only MouseEvents and HTMLEvents interfaces are supported) * - options: optional object to fine-tune event properties - pointerX, pointerY, ctrlKey, etc. @@ -29,44 +29,44 @@ bubbles: true, cancelable: true }; - + global.simulateEvent = function(element, eventName) { - + var options = extendObject(extendObject({ }, defaultOptions), arguments[2] || { }), - oEvent, + oEvent, eventType; - + element = typeof element == 'string' ? document.getElementById(element) : element; - + for (var name in eventMatchers) { if (eventMatchers[name].test(eventName)) { - eventType = name; - break; + eventType = name; + break; } } - + if (!eventType) { throw new SyntaxError('This event is not supported'); } - + if (document.createEvent) { try { - // Opera doesn't support event types like "KeyboardEvent", + // Opera doesn't support event types like "KeyboardEvent", // but allows to create event of type "HTMLEvents", then fire key event on it oEvent = document.createEvent(eventType); } catch(err) { oEvent = document.createEvent('HTMLEvents'); } - + if (eventType == 'HTMLEvents') { oEvent.initEvent(eventName, options.bubbles, options.cancelable); } else if (eventType === 'KeyboardEvent') { // TODO (kangax): this needs to be tested if (oEvent.initKeyEvent) { - oEvent.initKeyEvent(eventName, options.bubbles, options.cancelable, document.defaultView, - options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.keyCode, + oEvent.initKeyEvent(eventName, options.bubbles, options.cancelable, document.defaultView, + options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.keyCode, options.charCode); } else if (oEvent.initEvent) { @@ -74,7 +74,7 @@ } } else { - oEvent.initMouseEvent(eventName, options.bubbles, options.cancelable, document.defaultView, + oEvent.initMouseEvent(eventName, options.bubbles, options.cancelable, document.defaultView, options.button, options.pointerX, options.pointerY, options.pointerX, options.pointerY, options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, element); } diff --git a/test/unit/canvas.js b/test/unit/canvas.js index 4d370064..0b8c4d74 100644 --- a/test/unit/canvas.js +++ b/test/unit/canvas.js @@ -100,18 +100,25 @@ test('calcOffset', function() { ok(typeof canvas.calcOffset == 'function', 'should respond to `calcOffset`'); - equal(canvas, canvas.calcOffset()); + equal(canvas.calcOffset(), canvas, 'should be chainable'); }); test('add', function() { - var rect = makeRect(); + var rect1 = makeRect(), + rect2 = makeRect(), + rect3 = makeRect(), + rect4 = makeRect(); ok(typeof canvas.add == 'function'); - ok(canvas === canvas.add(rect), 'should be chainable'); - equal(canvas.item(0), rect); + equal(canvas.add(rect1), canvas, 'should be chainable'); + strictEqual(canvas.item(0), rect1); - canvas.add(makeRect(), makeRect(), makeRect()); + canvas.add(rect2, rect3, rect4); equal(canvas.getObjects().length, 4, 'should support multiple arguments'); + + strictEqual(canvas.item(1), rect2); + strictEqual(canvas.item(2), rect3); + strictEqual(canvas.item(3), rect4); }); test('insertAt', function() { @@ -124,10 +131,61 @@ var rect = makeRect(); canvas.insertAt(rect, 1); - equal(canvas.item(1), rect); + strictEqual(canvas.item(1), rect); canvas.insertAt(rect, 2); - equal(canvas.item(2), rect); - equal(canvas, canvas.insertAt(rect, 2), 'should be chainable'); + strictEqual(canvas.item(2), rect); + equal(canvas.insertAt(rect, 2), canvas, 'should be chainable'); + }); + + test('remove', function() { + var rect1 = makeRect(), + rect2 = makeRect(), + rect3 = makeRect(), + rect4 = makeRect(); + + canvas.add(rect1, rect2, rect3, rect4); + + ok(typeof canvas.remove == 'function'); + equal(canvas.remove(rect1), canvas, 'should be chainable'); + strictEqual(canvas.item(0), rect2, 'should be second object'); + + canvas.remove(rect2, rect3); + strictEqual(canvas.item(0), rect4); + + canvas.remove(rect4); + equal(canvas.isEmpty(), true, 'canvas should be empty'); + }); + + test('before:selection:cleared', function() { + var isFired = false; + canvas.on('before:selection:cleared', function() { isFired = true }); + + canvas.add(new fabric.Rect()); + canvas.remove(canvas.item(0)); + + equal(isFired, false, 'removing inactive object shouldnt fire "before:selection:cleared"'); + + canvas.add(new fabric.Rect()); + canvas.setActiveObject(canvas.item(0)); + canvas.remove(canvas.item(0)); + + equal(isFired, true, 'removing active object should fire "before:selection:cleared"'); + }); + + test('selection:cleared', function() { + var isFired = false; + canvas.on('selection:cleared', function() { isFired = true }); + + canvas.add(new fabric.Rect()); + canvas.remove(canvas.item(0)); + + equal(isFired, false, 'removing inactive object shouldnt fire "selection:cleared"'); + + canvas.add(new fabric.Rect()); + canvas.setActiveObject(canvas.item(0)); + canvas.remove(canvas.item(0)); + + equal(isFired, true, 'removing active object should fire "selection:cleared"'); }); test('getContext', function() { @@ -136,13 +194,13 @@ test('clearContext', function() { ok(typeof canvas.clearContext == 'function'); - equal(canvas, canvas.clearContext(canvas.getContext()), 'chainable'); + equal(canvas.clearContext(canvas.getContext()), canvas, 'should be chainable'); }); test('clear', function() { ok(typeof canvas.clear == 'function'); - equal(canvas, canvas.clear()); + equal(canvas.clear(), canvas, 'should be chainable'); equal(canvas.getObjects().length, 0); }); @@ -419,6 +477,25 @@ }); }); + asyncTest('loadFromJSON without "objects" property', function() { + var c1 = new fabric.Canvas('c1', { backgroundColor: 'green', overlayColor: 'yellow' }), + c2 = new fabric.Canvas('c2', { backgroundColor: 'red', overlayColor: 'orange' }); + + var json = c1.toJSON(); + var fired = false; + + delete json.objects; + + c2.loadFromJSON(json, function() { + fired = true; + + ok(fired, 'Callback should be fired even if no "objects" property exists'); + equal(c2.backgroundColor, 'green', 'Color should be set properly'); + equal(c2.overlayColor, 'yellow', 'Color should be set properly'); + start(); + }); + }); + asyncTest('loadFromJSON with empty fabric.Group', function() { var c1 = new fabric.Canvas('c1'), c2 = new fabric.Canvas('c2'), @@ -501,14 +578,6 @@ // }, 1000); // }); - test('remove', function() { - ok(typeof canvas.remove == 'function'); - var rect1 = makeRect(), - rect2 = makeRect(); - canvas.add(rect1, rect2); - equal(canvas.remove(rect1), rect1, 'should return removed object'); - equal(canvas.item(0), rect2, 'only second object should be left'); - }); test('sendToBack', function() { ok(typeof canvas.sendToBack == 'function'); @@ -680,7 +749,7 @@ makeRect({ left: 20, top: 20 }) ]); - equal(canvas.setActiveGroup(group), canvas, 'chainable'); + equal(canvas.setActiveGroup(group), canvas, 'should be chainable'); equal(canvas.getActiveGroup(), group); }); @@ -704,7 +773,7 @@ ok(typeof canvas.discardActiveGroup == 'function'); var group = new fabric.Group([makeRect(), makeRect()]); canvas.setActiveGroup(group); - equal(canvas.discardActiveGroup(), canvas, 'chainable'); + equal(canvas.discardActiveGroup(), canvas, 'should be chainable'); equal(canvas.getActiveGroup(), null, 'removing active group sets it to null'); }); @@ -867,14 +936,14 @@ ok(typeof canvas.getWidth == 'function'); equal(canvas.getWidth(), 600); - equal(canvas.setWidth(444), canvas, 'chainable'); + equal(canvas.setWidth(444), canvas, 'should be chainable'); equal(canvas.getWidth(), 444); }); test('getSetHeight', function() { ok(typeof canvas.getHeight == 'function'); equal(canvas.getHeight(), 600); - equal(canvas.setHeight(765), canvas, 'chainable'); + equal(canvas.setHeight(765), canvas, 'should be chainable'); equal(canvas.getHeight(), 765); }); @@ -906,25 +975,6 @@ ok(canvas.containsPoint(eventStub, rect), 'on rect at (200, 200) should be within area (175, 175, 225, 225)'); }); - // asyncTest('resizeImageToFit', function() { - // ok(typeof canvas._resizeImageToFit == 'function'); - - // var imgEl = fabric.util.makeElement('img', { src: '../fixtures/very_large_image.jpg' }), - // ORIGINAL_WIDTH = 3888, - // ORIGINAL_HEIGHT = 2592; - - // setTimeout(function() { - // equal(imgEl.width, ORIGINAL_WIDTH); - // equal(imgEl.height, ORIGINAL_HEIGHT); - - // canvas._resizeImageToFit(imgEl); - - // ok(imgEl.width < ORIGINAL_WIDTH); - - // start(); - // }, 2000); - // }); - asyncTest('fxRemove', function() { ok(typeof canvas.fxRemove == 'function'); @@ -973,22 +1023,6 @@ // }, 1000); // }); - test('selection:cleared', function() { - var isFired = false; - canvas.on('selection:cleared', function() { isFired = true }); - - canvas.add(new fabric.Rect()); - canvas.remove(canvas.item(0)); - - equal(isFired, false, 'removing inactive object shouldnt fire "selection:cleared"'); - - canvas.add(new fabric.Rect()); - canvas.setActiveObject(canvas.item(0)); - canvas.remove(canvas.item(0)); - - equal(isFired, true, 'removing active object should fire "selection:cleared"'); - }); - test('clipTo', function() { canvas.clipTo = function(ctx) { ctx.arc(0, 0, 10, 0, Math.PI * 2, false); diff --git a/test/unit/canvas_static.js b/test/unit/canvas_static.js index 9ed22581..e8aa7aae 100644 --- a/test/unit/canvas_static.js +++ b/test/unit/canvas_static.js @@ -190,18 +190,25 @@ test('calcOffset', function() { ok(typeof canvas.calcOffset == 'function', 'should respond to `calcOffset`'); - equal(canvas, canvas.calcOffset()); + equal(canvas.calcOffset(), canvas, 'should be chainable'); }); test('add', function() { - var rect = makeRect(); + var rect1 = makeRect(), + rect2 = makeRect(), + rect3 = makeRect(), + rect4 = makeRect(); ok(typeof canvas.add == 'function'); - ok(canvas === canvas.add(rect), 'should be chainable'); - equal(canvas.item(0), rect); + equal(canvas.add(rect1), canvas, 'should be chainable'); + strictEqual(canvas.item(0), rect1); - canvas.add(makeRect(), makeRect(), makeRect()); + canvas.add(rect2, rect3, rect4); equal(canvas.getObjects().length, 4, 'should support multiple arguments'); + + strictEqual(canvas.item(1), rect2); + strictEqual(canvas.item(2), rect3); + strictEqual(canvas.item(3), rect4); }); test('add renderOnAddRemove disabled', function() { @@ -218,7 +225,7 @@ canvas.on('after:render', countRenderAll); - ok(canvas === canvas.add(rect), 'should be chainable'); + equal(canvas.add(rect), canvas, 'should be chainable'); equal(renderAllCount, 0); equal(canvas.item(0), rect); @@ -234,6 +241,32 @@ canvas.renderOnAddRemove = originalRenderOnAddition; }); + test('object:added', function() { + var objectsAdded = []; + + canvas.on('object:added', function(e) { + objectsAdded.push(e.target); + }); + + var rect = new fabric.Rect({ width: 10, height: 20 }); + canvas.add(rect); + + deepEqual(objectsAdded[0], rect); + + var circle1 = new fabric.Circle(), + circle2 = new fabric.Circle(); + + canvas.add(circle1, circle2); + + strictEqual(objectsAdded[1], circle1); + strictEqual(objectsAdded[2], circle2); + + var circle3 = new fabric.Circle(); + canvas.insertAt(circle3, 2); + + strictEqual(objectsAdded[3], circle3); + }); + test('insertAt', function() { var rect1 = makeRect(), rect2 = makeRect(); @@ -244,10 +277,10 @@ var rect = makeRect(); canvas.insertAt(rect, 1); - equal(canvas.item(1), rect); + strictEqual(canvas.item(1), rect); canvas.insertAt(rect, 2); - equal(canvas.item(2), rect); - equal(canvas, canvas.insertAt(rect, 2), 'should be chainable'); + strictEqual(canvas.item(2), rect); + equal(canvas.insertAt(rect, 2), canvas, 'should be chainable'); }); test('insertAt renderOnAddRemove disabled', function() { @@ -273,7 +306,7 @@ canvas.insertAt(rect, 1); equal(renderAllCount, 0); - equal(canvas.item(1), rect); + strictEqual(canvas.item(1), rect); canvas.insertAt(rect, 2); equal(renderAllCount, 0); @@ -284,15 +317,90 @@ canvas.renderOnAddRemove = originalRenderOnAddition; }); + test('remove', function() { + var rect1 = makeRect(), + rect2 = makeRect(), + rect3 = makeRect(), + rect4 = makeRect(); + + canvas.add(rect1, rect2, rect3, rect4); + + ok(typeof canvas.remove == 'function'); + equal(canvas.remove(rect1), canvas, 'should be chainable'); + strictEqual(canvas.item(0), rect2, 'should be second object'); + + canvas.remove(rect2, rect3); + strictEqual(canvas.item(0), rect4); + + canvas.remove(rect4); + equal(canvas.isEmpty(), true, 'canvas should be empty'); + }); + + test('remove renderOnAddRemove disabled', function() { + var rect1 = makeRect(), + rect2 = makeRect(), + originalRenderOnAddition, + renderAllCount = 0; + + function countRenderAll() { + renderAllCount++; + } + + originalRenderOnAddition = canvas.renderOnAddRemove; + canvas.renderOnAddRemove = false; + + canvas.on('after:render', countRenderAll); + + canvas.add(rect1, rect2); + equal(renderAllCount, 0); + + equal(canvas.remove(rect1), canvas, 'should be chainable'); + equal(renderAllCount, 0); + strictEqual(canvas.item(0), rect2, 'only second object should be left'); + + canvas.renderAll(); + equal(renderAllCount, 1); + + canvas.off('after:render', countRenderAll); + canvas.renderOnAddRemove = originalRenderOnAddition; + }); + + test('object:removed', function() { + var objectsRemoved = []; + + canvas.on('object:removed', function(e) { + objectsRemoved.push(e.target); + }); + + var rect = new fabric.Rect({ width: 10, height: 20 }), + circle1 = new fabric.Circle(), + circle2 = new fabric.Circle(); + + canvas.add(rect, circle1, circle2); + + strictEqual(canvas.item(0), rect); + strictEqual(canvas.item(1), circle1); + strictEqual(canvas.item(2), circle2); + + canvas.remove(rect); + strictEqual(objectsRemoved[0], rect); + + canvas.remove(circle1, circle2); + strictEqual(objectsRemoved[1], circle1); + strictEqual(objectsRemoved[2], circle2); + + equal(canvas.isEmpty(), true, 'canvas should be empty'); + }); + test('clearContext', function() { ok(typeof canvas.clearContext == 'function'); - equal(canvas, canvas.clearContext(canvas.contextContainer), 'chainable'); + equal(canvas.clearContext(canvas.contextContainer), canvas, 'should be chainable'); }); test('clear', function() { ok(typeof canvas.clear == 'function'); - equal(canvas, canvas.clear()); + equal(canvas.clear(), canvas, 'should be chainable'); equal(canvas.getObjects().length, 0); }); @@ -665,44 +773,6 @@ }); }); - test('remove', function() { - ok(typeof canvas.remove == 'function'); - var rect1 = makeRect(), - rect2 = makeRect(); - canvas.add(rect1, rect2); - equal(canvas.remove(rect1), rect1, 'should return removed object'); - equal(canvas.item(0), rect2, 'only second object should be left'); - }); - - test('remove renderOnAddRemove disabled', function() { - var rect1 = makeRect(), - rect2 = makeRect(), - originalRenderOnAddition, - renderAllCount = 0; - - function countRenderAll() { - renderAllCount++; - } - - originalRenderOnAddition = canvas.renderOnAddRemove; - canvas.renderOnAddRemove = false; - - canvas.on('after:render', countRenderAll); - - canvas.add(rect1, rect2); - equal(renderAllCount, 0); - - equal(canvas.remove(rect1), rect1, 'should return removed object'); - equal(renderAllCount, 0); - equal(canvas.item(0), rect2, 'only second object should be left'); - - canvas.renderAll(); - equal(renderAllCount, 1); - - canvas.off('after:render', countRenderAll); - canvas.renderOnAddRemove = originalRenderOnAddition; - }); - test('sendToBack', function() { ok(typeof canvas.sendToBack == 'function'); @@ -940,36 +1010,17 @@ test('getSetWidth', function() { ok(typeof canvas.getWidth == 'function'); equal(canvas.getWidth(), 600); - equal(canvas.setWidth(444), canvas, 'chainable'); + equal(canvas.setWidth(444), canvas, 'should be chainable'); equal(canvas.getWidth(), 444); }); test('getSetHeight', function() { ok(typeof canvas.getHeight == 'function'); equal(canvas.getHeight(), 600); - equal(canvas.setHeight(765), canvas, 'chainable'); + equal(canvas.setHeight(765), canvas, 'should be chainable'); equal(canvas.getHeight(), 765); }); - // asyncTest('resizeImageToFit', function() { - // ok(typeof canvas._resizeImageToFit == 'function'); - - // var imgEl = fabric.util.makeElement('img', { src: '../fixtures/very_large_image.jpg' }), - // ORIGINAL_WIDTH = 3888, - // ORIGINAL_HEIGHT = 2592; - - // setTimeout(function() { - // equal(imgEl.width, ORIGINAL_WIDTH); - // equal(imgEl.height, ORIGINAL_HEIGHT); - - // canvas._resizeImageToFit(imgEl); - - // ok(imgEl.width < ORIGINAL_WIDTH); - - // start(); - // }, 2000); - // }); - asyncTest('fxRemove', function() { ok(typeof canvas.fxRemove == 'function'); @@ -982,7 +1033,7 @@ } ok(canvas.item(0) === rect); - ok(canvas.fxRemove(rect, { onComplete: onComplete }) === canvas, 'should be chainable'); + equal(canvas.fxRemove(rect, { onComplete: onComplete }), canvas, 'should be chainable'); setTimeout(function() { equal(canvas.item(0), undefined); @@ -1017,30 +1068,4 @@ // }, 1000); // }); - test('object:added', function() { - - var objectsAdded = []; - canvas.on('object:added', function(e) { - objectsAdded.push(e.target); - }); - - var rect = new fabric.Rect({ width: 10, height: 20 }); - canvas.add(rect); - - deepEqual(objectsAdded[0], rect); - - var circle1 = new fabric.Circle(), - circle2 = new fabric.Circle(); - - canvas.add(circle1, circle2); - - deepEqual(objectsAdded[1], circle1); - deepEqual(objectsAdded[2], circle2); - - var circle3 = new fabric.Circle(); - canvas.insertAt(circle3, 2); - - deepEqual(objectsAdded[3], circle3); - }); - })(); diff --git a/test/unit/color.js b/test/unit/color.js index 9b25ba07..e669a94f 100644 --- a/test/unit/color.js +++ b/test/unit/color.js @@ -204,6 +204,17 @@ equal(oColor.getAlpha(), 0.5, 'alpha should be set properly'); }); + test('fromRgba (percentage values with decimals)', function() { + var originalRgba = 'rgba( 100.00%, 100.00%, 100.00% , 0.5 )'; + oColor = fabric.Color.fromRgba(originalRgba); + ok(oColor); + ok(oColor instanceof fabric.Color); + equal(oColor.toRgba(), 'rgba(255,255,255,0.5)'); + equal(oColor.toHex(), 'FFFFFF'); + equal(oColor.getAlpha(), 0.5, 'alpha should be set properly'); + }); + + test('fromHsl', function() { ok(typeof fabric.Color.fromHsl == 'function'); var originalHsl = 'hsl(262,80%,12%)'; @@ -313,4 +324,8 @@ oColor.overlayWith(new fabric.Color('rgb(0,0,0)')); equal(oColor.toRgb(), 'rgb(128,128,128)'); }); + + test('transparent', function() { + deepEqual(new fabric.Color('transparent').getSource(), [255,255,255,0]); + }); })(); diff --git a/test/unit/group.js b/test/unit/group.js index a102fc87..c6e93a21 100644 --- a/test/unit/group.js +++ b/test/unit/group.js @@ -52,17 +52,35 @@ ok(typeof group.getObjects == 'function'); ok(Object.prototype.toString.call(group.getObjects()) == '[object Array]', 'should be an array'); equal(group.getObjects().length, 2, 'should have 2 items'); - deepEqual([ rect1, rect2 ], group.getObjects(), 'should return deepEqual objects as those passed to constructor'); + deepEqual(group.getObjects(), [ rect1, rect2 ], 'should return deepEqual objects as those passed to constructor'); + }); + + test('getObjects with type', function() { + var rect = new fabric.Rect({ width: 10, height: 20 }), + circle = new fabric.Circle({ radius: 30 }); + + var group = new fabric.Group([ rect, circle ]); + + equal(group.size(), 2, 'should have length=2 initially'); + + deepEqual(group.getObjects('rect'), [rect], 'should return rect only'); + deepEqual(group.getObjects('circle'), [circle], 'should return circle only'); }); test('add', function() { var group = makeGroupWith2Objects(); - var rect = new fabric.Rect(); + var rect1 = new fabric.Rect(), + rect2 = new fabric.Rect(), + rect3 = new fabric.Rect(); ok(typeof group.add == 'function'); - equal(group.add(rect), group, 'should be chainable'); - equal(group.getObjects()[group.getObjects().length-1], rect, 'last object should be newly added one'); + equal(group.add(rect1), group, 'should be chainable'); + strictEqual(group.item(group.size()-1), rect1, 'last object should be newly added one'); equal(group.getObjects().length, 3, 'there should be 3 objects'); + + group.add(rect2, rect3); + strictEqual(group.item(group.size()-1), rect3, 'last object should be last added one'); + equal(group.size(), 5, 'there should be 5 objects'); }); test('remove', function() { @@ -72,8 +90,11 @@ group = new fabric.Group([ rect1, rect2, rect3 ]); ok(typeof group.remove == 'function'); - equal(group.remove(rect2), rect2, 'should return removed object'); - deepEqual([rect1, rect3], group.getObjects(), 'should remove object properly'); + equal(group.remove(rect2), group, 'should be chainable'); + deepEqual(group.getObjects(), [rect1, rect3], 'should remove object properly'); + + group.remove(rect1, rect3); + equal(group.isEmpty(), true, 'group should be empty'); }); test('size', function() { @@ -452,7 +473,7 @@ test('toObject without default values', function() { equal(group.item(1), rect1); group.insertAt(rect2, 2); equal(group.item(2), rect2); - equal(group, group.insertAt(rect1, 2), 'should be chainable'); + equal(group.insertAt(rect1, 2), group, 'should be chainable'); }); // asyncTest('cloning group with image', function() { diff --git a/test/unit/image.js b/test/unit/image.js index bc412498..24134a58 100644 --- a/test/unit/image.js +++ b/test/unit/image.js @@ -161,14 +161,16 @@ equal(objRepr.crossOrigin, '', 'toObject should return proper crossOrigin value'); var elImage2 = _createImageElement(); + elImage2.crossOrigin = 'anonymous'; image.setElement(elImage2); - equal(elImage2.crossOrigin, '', 'setElement should set proper crossOrigin on an img element'); + equal(elImage2.crossOrigin, 'anonymous', 'setElement should set proper crossOrigin on an img element'); // fromObject doesn't work on Node :/ if (fabric.isLikelyNode) { start(); return; } + fabric.Image.fromObject(objRepr, function(img) { equal(img.crossOrigin, ''); start(); diff --git a/test/unit/image_filters.js b/test/unit/image_filters.js index 019bfb86..9a87afc6 100644 --- a/test/unit/image_filters.js +++ b/test/unit/image_filters.js @@ -92,7 +92,7 @@ var filter = new fabric.Image.filters.Brightness(); equal(filter.type, 'Brightness'); - equal(filter.brightness, 100); + equal(filter.brightness, 0); var filter2 = new fabric.Image.filters.Brightness({brightness: 30}); equal(filter2.brightness, 30); @@ -108,6 +108,11 @@ ok(typeof filter.toObject == 'function'); var object = filter.toObject(); + equal(JSON.stringify(object), '{"type":"Brightness","brightness":0}'); + + filter.brightness = 100; + + object = filter.toObject(); equal(JSON.stringify(object), '{"type":"Brightness","brightness":100}'); }); @@ -116,6 +121,11 @@ ok(typeof filter.toJSON == 'function'); var json = filter.toJSON(); + equal(JSON.stringify(json), '{"type":"Brightness","brightness":0}'); + + filter.brightness = 100; + + json = filter.toJSON(); equal(JSON.stringify(json), '{"type":"Brightness","brightness":100}'); }); @@ -330,7 +340,7 @@ var filter = new fabric.Image.filters.Noise(); equal(filter.type, 'Noise'); - equal(filter.noise, 100); + equal(filter.noise, 0); var filter2 = new fabric.Image.filters.Noise({noise: 200}); equal(filter2.noise, 200); @@ -346,6 +356,11 @@ ok(typeof filter.toObject == 'function'); var object = filter.toObject(); + equal(JSON.stringify(object), '{"type":"Noise","noise":0}'); + + filter.noise = 100; + + object = filter.toObject(); equal(JSON.stringify(object), '{"type":"Noise","noise":100}'); }); @@ -354,6 +369,11 @@ ok(typeof filter.toJSON == 'function'); var json = filter.toJSON(); + equal(JSON.stringify(json), '{"type":"Noise","noise":0}'); + + filter.noise = 100; + + json = filter.toJSON(); equal(JSON.stringify(json), '{"type":"Noise","noise":100}'); }); diff --git a/test/unit/line.js b/test/unit/line.js index 1c3102e5..1c0ced12 100644 --- a/test/unit/line.js +++ b/test/unit/line.js @@ -4,8 +4,8 @@ 'type': 'line', 'originX': 'left', 'originY': 'top', - 'left': 12, - 'top': 13, + 'left': 11, + 'top': 12, 'width': 2, 'height': 2, 'fill': 'rgb(0,0,0)', @@ -164,4 +164,430 @@ // equal(200, line.height); // }); + var lineCoordsCases = [ + { description: 'default to 0 left and 0 top', + givenLineArgs: {}, + expectedCoords: { + left: 0, + top: 0, + } + }, + { description: 'origin defaults to left-top', + givenLineArgs: { + points: [0, 0, 11, 22], + }, + expectedCoords: { + left: 0, + top: 0, + } + }, + { description: 'equal smallest points when origin is left-top and line not offset', + givenLineArgs: { + points: [0, 0, 12.3, 34.5], + options: { + originX: 'left', + originY: 'top', + }, + }, + expectedCoords: { + left: 0, + top: 0, + } + }, + { description: 'include offsets for left-top origin', + givenLineArgs: { + points: [0+33, 0+44, 11+33, 22+44], + options: { + originX: 'left', + originY: 'top', + }, + }, + expectedCoords: { + left: 33, + top: 44, + } + }, + { description: 'equal half-dimensions when origin is center and line not offset', + givenLineArgs: { + points: [0, 0, 12.3, 34.5], + options: { + originX: 'center', + originY: 'center', + }, + }, + expectedCoords: { + left: 0.5 * 12.3, + top: 0.5 * 34.5, + } + }, + { description: 'include offsets for center-center origin', + givenLineArgs: { + points: [0+9.87, 0-4.32, 12.3+9.87, 34.5-4.32], + options: { + originX: 'center', + originY: 'center', + }, + }, + expectedCoords: { + left: (0.5 * 12.3) + 9.87, + top: (0.5 * 34.5) - 4.32, + } + }, + { description: 'equal full dimensions when origin is right-bottom and line not offset', + givenLineArgs: { + points: [0, 0, 55, 18], + options: { + originX: 'right', + originY: 'bottom', + }, + }, + expectedCoords: { + left: 55, + top: 18, + } + }, + { description: 'include offsets for right-bottom origin', + givenLineArgs: { + points: [0-3.14, 0-1.41, 55-3.14, 18-1.41], + options: { + originX: 'right', + originY: 'bottom', + }, + }, + expectedCoords: { + left: 55 - 3.14, + top: 18 - 1.41, + } + }, + { description: 'arent changed by rotation for left-top origin', + givenLineArgs: { + points: [1, 2, 30, 40], + options: { + originX: 'left', + originY: 'top', + angle: 67, + } + }, + expectedCoords: { + left: 1, + top: 2, + } + }, + { description: 'arent changed by rotation for right-bottom origin', + givenLineArgs: { + points: [1, 2, 30, 40], + options: { + originX: 'right', + originY: 'bottom', + angle: 67, + } + }, + expectedCoords: { + left: 30, + top: 40, + } + }, + { description: 'arent changed by scaling for left-top origin', + givenLineArgs: { + points: [1, 2, 30, 40], + options: { + originX: 'left', + originY: 'top', + scale: 2.1, + } + }, + expectedCoords: { + left: 1, + top: 2, + } + }, + { description: 'arent changed by scaling for right-bottom origin', + givenLineArgs: { + points: [1, 2, 30, 40], + options: { + originX: 'right', + originY: 'bottom', + scale: 1.2, + } + }, + expectedCoords: { + left: 30, + top: 40, + } + }, + { description: 'arent changed by strokeWidth for left-top origin', + givenLineArgs: { + points: [31, 41, 59, 26], + options: { + originX: 'left', + originY: 'top', + stroke: 'black', + strokeWidth: '53' + } + }, + expectedCoords: { + left: 31, + top: 26, + } + }, + { description: 'arent changed by strokeWidth for center-center origin', + givenLineArgs: { + points: [0+31, 15+26, 28+31, 0+26], + options: { + originX: 'center', + originY: 'center', + stroke: 'black', + strokeWidth: '53' + } + }, + expectedCoords: { + left: (0.5 * 28) + 31, + top: (0.5 * 15) + 26, + } + }, + { description: 'arent changed by strokeWidth for right-bottom origin', + givenLineArgs: { + points: [1, 2, 30, 40], + options: { + originX: 'right', + originY: 'bottom', + stroke: 'black', + strokeWidth: '53' + } + }, + expectedCoords: { + left: 30, + top: 40, + } + }, + { description: 'left and top options override points', + givenLineArgs: { + points: [12, 34, 56, 78], + options: { + left: 98, + top: 76, + } + }, + expectedCoords: { + left: 98, + top: 76, + } + }, + { description: '0 left and 0 top options override points', + givenLineArgs: { + points: [12, 34, 56, 78], + options: { + left: 0, + top: 0, + } + }, + expectedCoords: { + left: 0, + top: 0, + } + }, + { description: 'equal x2 and y2 for left-top origin when x1 and y1 are largest and line not offset', + givenLineArgs: { + points: [100, 200, 30, 40], + options: { + originX: 'left', + originY: 'top', + } + }, + expectedCoords: { + left: 30, + top: 40, + } + }, + { description: 'equal half-dimensions for center-center origin when x1 and y1 are largest and line not offset', + givenLineArgs: { + points: [100, 200, 0, 0], + options: { + originX: 'center', + originY: 'center', + } + }, + expectedCoords: { + left: 0.5 * 100, + top: 0.5 * 200, + } + }, + { description: 'equal x1 and y1 for right-bottom origin when x1 and y1 are largest and line not offset', + givenLineArgs: { + points: [100, 200, 0, 0], + options: { + originX: 'right', + originY: 'bottom', + } + }, + expectedCoords: { + left: 100, + top: 200, + } + }, + ]; + + lineCoordsCases.forEach(function (c_) { + test('stroke-less line coords ' + c_.description, function() { + var points = c_.givenLineArgs.points; + var options = c_.givenLineArgs.options; + + var givenLine = new fabric.Line( + points, + options + ); + + equal(givenLine.left, c_.expectedCoords.left); + equal(givenLine.top, c_.expectedCoords.top); + }); + }); + + var getLeftToOriginXCases = [ + { description: 'is x1 for left origin and x1 lesser than x2', + givenOrigin: 'left', + givenPoints: [0, 0, 1, 0], + expectedLeft: 0, + }, + { description: 'is x2 for left origin and x1 greater than x2', + givenOrigin: 'left', + givenPoints: [1, 0, 0, 0], + expectedLeft: 0, + }, + { description: 'includes positive offset for left origin', + givenOrigin: 'left', + givenPoints: [0+20, 0, 1+20, 0], + expectedLeft: 0+20, + }, + { description: 'includes negative offset for left origin', + givenOrigin: 'left', + givenPoints: [0-11, 0, 1-11, 0], + expectedLeft: 0-11, + }, + { description: 'is half of x1 for center origin and x1 > x2', + givenOrigin: 'center', + givenPoints: [4, 0, 0, 0], + expectedLeft: 0.5 * 4, + }, + { description: 'is half of x2 for center origin and x1 < x2', + givenOrigin: 'center', + givenPoints: [0, 0, 7, 0], + expectedLeft: 0.5 * 7, + }, + { description: 'includes positive offset for center origin', + givenOrigin: 'center', + givenPoints: [0+39, 0, 7+39, 0], + expectedLeft: (0.5 * 7) + 39, + }, + { description: 'includes negative offset for center origin', + givenOrigin: 'center', + givenPoints: [4-13, 0, 0-13, 0], + expectedLeft: (0.5 * 4) - 13, + }, + { description: 'is x1 for right origin and x1 > x2', + givenOrigin: 'right', + givenPoints: [9, 0, 0, 0], + expectedLeft: 9, + }, + { description: 'is x2 for right origin and x1 < x2', + givenOrigin: 'right', + givenPoints: [0, 0, 6, 0], + expectedLeft: 6, + }, + { description: 'includes positive offset for right origin', + givenOrigin: 'right', + givenPoints: [0+47, 0, 6+47, 0], + expectedLeft: 6 + 47, + }, + { description: 'includes negative offset for right origin', + givenOrigin: 'right', + givenPoints: [9-17, 0, 0-17, 0], + expectedLeft: 9 - 17, + }, + ]; + + getLeftToOriginXCases.forEach(function (c_) { + test('Line.getLeftToOriginX() ' + c_.description, function () { + var line = new fabric.Line( + c_.givenPoints, + { originX: c_.givenOrigin } + ); + + equal(line._getLeftToOriginX(), c_.expectedLeft); + }); + }); + + var getTopToOriginYCases = [ + { description: 'is y1 for top origin and y1 lesser than y2', + givenOrigin: 'top', + givenPoints: [0, 0, 0, 1], + expectedTop: 0, + }, + { description: 'is y2 for top origin and y1 greater than y2', + givenOrigin: 'top', + givenPoints: [0, 1, 0, 0], + expectedTop: 0, + }, + { description: 'includes positive offset for top origin', + givenOrigin: 'top', + givenPoints: [0, 0+20, 0, 1+20], + expectedTop: 0+20, + }, + { description: 'includes negative offset for top origin', + givenOrigin: 'top', + givenPoints: [0, 0-11, 0, 1-11], + expectedTop: 0-11, + }, + { description: 'is half of y1 for center origin and y1 > y2', + givenOrigin: 'center', + givenPoints: [0, 4, 0, 0], + expectedTop: 0.5 * 4, + }, + { description: 'is half of y2 for center origin and y1 < y2', + givenOrigin: 'center', + givenPoints: [0, 0, 0, 7], + expectedTop: 0.5 * 7, + }, + { description: 'includes positive offset for center origin', + givenOrigin: 'center', + givenPoints: [0, 0+39, 0, 7+39], + expectedTop: (0.5 * 7) + 39, + }, + { description: 'includes negative offset for center origin', + givenOrigin: 'center', + givenPoints: [0, 4-13, 0, 0-13], + expectedTop: (0.5 * 4) - 13, + }, + { description: 'is y1 for bottom origin and y1 > y2', + givenOrigin: 'bottom', + givenPoints: [0, 9, 0, 0], + expectedTop: 9, + }, + { description: 'is y2 for bottom origin and y1 < y2', + givenOrigin: 'bottom', + givenPoints: [0, 0, 0, 6], + expectedTop: 6, + }, + { description: 'includes positive offset for bottom origin', + givenOrigin: 'bottom', + givenPoints: [0, 0+47, 0, 6+47], + expectedTop: 6 + 47, + }, + { description: 'includes negative offset for bottom origin', + givenOrigin: 'bottom', + givenPoints: [0, 9-17, 0, 0-17], + expectedTop: 9 - 17, + }, + ]; + + getTopToOriginYCases.forEach(function (c_) { + test('Line._getTopToOriginY() ' + c_.description, function () { + var line = new fabric.Line( + c_.givenPoints, + { originY: c_.givenOrigin } + ); + + equal(line._getTopToOriginY(), c_.expectedTop); + }); + }); + })(); diff --git a/test/unit/object.js b/test/unit/object.js index 7930c9cc..b8f79e49 100644 --- a/test/unit/object.js +++ b/test/unit/object.js @@ -900,6 +900,18 @@ test('toDataURL & reference to canvas', function() { equal(canvas.getObjects().length, 0); }); + test('object:removed', function() { + var object = new fabric.Object(); + var removedEventFired = false; + + canvas.add(object); + + object.on('removed', function(){ removedEventFired = true; }); + object.remove(); + + ok(removedEventFired); + }); + test('center', function() { var object = new fabric.Object(); diff --git a/test/unit/parser.js b/test/unit/parser.js index 8b36a4b0..2550a91f 100644 --- a/test/unit/parser.js +++ b/test/unit/parser.js @@ -230,8 +230,9 @@ var parsedValue = fabric.parseTransformAttribute(element.getAttribute('transform')); deepEqual(parsedValue, [1,0,0,1,-10,-20]); - var ANGLE = 90; - element.setAttribute('transform', 'rotate(' + ANGLE + ')'); + var ANGLE_DEG = 90; + var ANGLE = ANGLE_DEG * Math.PI / 180; + element.setAttribute('transform', 'rotate(' + ANGLE_DEG + ')'); var parsedValue = fabric.parseTransformAttribute(element.getAttribute('transform')); deepEqual(parsedValue, [Math.cos(ANGLE), Math.sin(ANGLE), -Math.sin(ANGLE), Math.cos(ANGLE), 0, 0]); diff --git a/test/unit/path.js b/test/unit/path.js index fe7f2d55..cb4d5ef4 100644 --- a/test/unit/path.js +++ b/test/unit/path.js @@ -89,6 +89,18 @@ }); }); + asyncTest('path array not shared when cloned', function() { + makePathObject(function(originalPath) { + originalPath.clone(function(clonedPath) { + + clonedPath.path[0][1] = 200; + equal(originalPath.path[0][1], 100); + + start(); + }); + }); + }); + asyncTest('toDatalessObject', function() { makePathObject(function(path) { ok(typeof path.toDatalessObject == 'function'); @@ -148,9 +160,10 @@ transformMatrix: [2, 0, 0, 2, 0, 0] })); - var ANGLE = 90; + var ANGLE_DEG = 90; + var ANGLE = ANGLE_DEG * Math.PI / 180; - elPath.setAttribute('transform', 'rotate(' + ANGLE + ')'); + elPath.setAttribute('transform', 'rotate(' + ANGLE_DEG + ')'); fabric.Path.fromElement(elPath, function(path) { deepEqual( @@ -162,6 +175,22 @@ }); }); + asyncTest('numbers with leading decimal point', function() { + ok(typeof fabric.Path.fromElement == 'function'); + var elPath = fabric.document.createElement('path'); + + elPath.setAttribute('d', 'M 100 100 L 300 100 L 200 300 z'); + elPath.setAttribute('transform', 'scale(.2)'); + + fabric.Path.fromElement(elPath, function(path) { + ok(path instanceof fabric.Path); + + deepEqual(path.toObject().transformMatrix, [0.2, 0, 0, 0.2, 0, 0]); + + start(); + }); + }); + asyncTest('multiple sequences in path commands', function() { var el = getPathElement('M100 100 l 200 200 300 300 400 -50 z'); fabric.Path.fromElement(el, function(obj) { @@ -192,4 +221,17 @@ start(); }); }); + + asyncTest('compressed path commands with e^x', function() { + var el = getPathElement('M56.224e2 84.12E-2c-.047.132-.138.221-.322.215.046-.131.137-.221.322-.215m-.050 -20.100z'); + fabric.Path.fromElement(el, function(obj) { + + deepEqual(obj.path[0], ['M', 5622.4, 0.8412]); + deepEqual(obj.path[1], ['c', -0.047, 0.132, -0.138, 0.221, -0.322, 0.215]); + deepEqual(obj.path[2], ['c', 0.046, -0.131, 0.137, -0.221, 0.322, -0.215]); + deepEqual(obj.path[3], ['m', -0.05, -20.100]); + deepEqual(obj.path[4], ['z']); + start(); + }); + }); })(); diff --git a/test/unit/path_group.js b/test/unit/path_group.js index 620aff1c..29e5e190 100644 --- a/test/unit/path_group.js +++ b/test/unit/path_group.js @@ -66,7 +66,7 @@ function getPathGroupObject(callback) { getPathObjects(function(objects) { callback(new fabric.PathGroup(objects)); - }) + }); } QUnit.module('fabric.PathGroup'); @@ -178,6 +178,12 @@ pathGroup.getObjects()[0].set('fill', 'black'); equal(pathGroup.isSameColor(), false); + + // case + pathGroup.getObjects()[0].set('fill', '#ff5555'); + pathGroup.getObjects()[1].set('fill', '#FF5555'); + equal(pathGroup.isSameColor(), true); + start(); }); }); diff --git a/test/unit/text.js b/test/unit/text.js index dbce4820..73b35dfb 100644 --- a/test/unit/text.js +++ b/test/unit/text.js @@ -151,8 +151,12 @@ // text.width = 20; var expectedObject = fabric.util.object.extend(fabric.util.object.clone(REFERENCE_TEXT_OBJECT), { - left: 10, - top: -26 + left: 4, + top: -10.4, + width: 8, + height: 20.8, + fontSize: 16, + originX: 'center' }); deepEqual(text.toObject(), expectedObject); @@ -203,7 +207,8 @@ fontStyle: 'italic', fontWeight: 'bold', fontSize: 123, - textDecoration: 'underline' + textDecoration: 'underline', + originX: 'center' }); deepEqual(textWithAttrs.toObject(), expectedObject); diff --git a/test/unit/util.js b/test/unit/util.js index 0876017b..77c28fe1 100644 --- a/test/unit/util.js +++ b/test/unit/util.js @@ -476,19 +476,28 @@ asyncTest('fabric.util.groupSVGElements', function() { ok(typeof fabric.util.groupSVGElements == 'function'); - var group1, group2; + var group1; fabric.loadSVGFromString(SVG_WITH_1_ELEMENT, function(objects, options) { group1 = fabric.util.groupSVGElements(objects, options); }); + + setTimeout(function() { + ok(group1 instanceof fabric.Polygon); + start(); + }, 2000); + }); + + asyncTest('fabric.util.groupSVGElements #2', function() { + + var group2; fabric.loadSVGFromString(SVG_WITH_2_ELEMENTS, function(objects, options) { group2 = fabric.util.groupSVGElements(objects, options); }); setTimeout(function() { - ok(group1 instanceof fabric.Polygon); ok(group2 instanceof fabric.PathGroup); start(); - }, 1000); + }, 2000); }); test('Array.prototype.indexOf', function() {