Merge remote-tracking branch 'upstream/master'

Conflicts:
	.gitignore
	dist/fabric.js
	dist/fabric.require.js
	src/brushes/circle_brush.class.js
	src/canvas.class.js
	src/mixins/canvas_events.mixin.js
	src/mixins/object_geometry.mixin.js
	src/mixins/object_interactivity.mixin.js
	src/shapes/group.class.js
	src/shapes/image.class.js
	src/shapes/object.class.js
	src/shapes/path_group.class.js
	src/shapes/text.class.js
This commit is contained in:
Tom French 2014-05-31 17:29:55 +01:00
commit 0d8db88e71
111 changed files with 7883 additions and 4866 deletions

4
.gitignore vendored
View file

@ -1,4 +0,0 @@
node_modules
.DS_Store
before_commit
dist/fabric.js

37
.jscs.json Normal file
View file

@ -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"
}

View file

@ -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
}

View file

@ -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
build.js

13
.sublime-project vendored
View file

@ -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
}
}

View file

@ -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'

View file

@ -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

View file

@ -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

View file

@ -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",

View file

@ -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.
SOFTWARE.

View file

@ -1,12 +1,15 @@
<a href="http://fabricjs.challengepost.com/?utm_source=partner&utm_medium=banner&utm_campaign=fabricjs" style="display: block">
<img src="https://dl.dropboxusercontent.com/u/822184/fabric-js-promo-widget-github.gif" style="width: auto">
</a>
### 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)
<a href="https://npmjs.org/package/fabric"><img src="https://badge.fury.io/js/fabric.png"></a>
[![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
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<canvas id="canvas" width="300" height="300"></canvas>
...
var canvas = new fabric.Canvas('canvas');
var rect = new fabric.Rect({
top: 100,
left: 100,
width: 60,
height: 70,
fill: 'red'
});
<script src="lib/fabric.js"></script>
<script>
var canvas = new fabric.Canvas('canvas');
canvas.add(rect);
var rect = new fabric.Rect({
top : 100,
left : 100,
width : 60,
height : 70,
fill : 'red'
});
canvas.add(rect);
</script>
</body>
</html>
```
### 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")

28
bower.json Normal file
View file

@ -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"
}

View file

@ -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)');
});
});

View file

@ -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"
]
}

7
dist/all.min.js vendored

File diff suppressed because one or more lines are too long

BIN
dist/all.min.js.gz vendored

Binary file not shown.

File diff suppressed because it is too large Load diff

7
dist/fabric.min.js vendored Normal file

File diff suppressed because one or more lines are too long

BIN
dist/fabric.min.js.gz vendored Normal file

Binary file not shown.

File diff suppressed because it is too large Load diff

View file

@ -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
*
*/

View file

@ -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) {

View file

@ -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(' <div style="position:absolute;');
@ -94,21 +94,21 @@ Index: excanvas.js
- 'Dy=', mr(d.y / Z), '');
+ // Scaling images using width & height instead of Transform Matrix
+ // because of quality loss
+ var c = mc(this.rotation_);
+ var c = mc(this.rotation_);
+ var s = ms(this.rotation_);
+
+
+ // Inverse rotation matrix
+ var irm = [
+ [c, -s, 0],
+ [s, c, 0],
+ [c, -s, 0],
+ [s, c, 0],
+ [0, 0, 1]
+ ];
+
+ ];
+
+ // Get unrotated matrix to get only scaling values
+ var urm = matrixMultiply(irm, this.m_);
+ var urm = matrixMultiply(irm, this.m_);
+ scaleX = urm[0][0];
+ scaleY = urm[1][1];
+
+
+ // Apply only rotation and translation to Matrix
+ filter.push('M11=', c, ',',
+ 'M12=', -s, ',',
@ -149,25 +149,25 @@ Index: excanvas.js
+ ' filter:progid:DxImageTransform.Microsoft.Matrix(Dx=',
+ -sx * dw / sw * scaleX, ',Dy=', -sy * dh / sh * scaleY, ');">');
+ }
+
+
+
+
+ // Apply scales to width and height
+ vmlStr.push('<div style="width:', Math.round(scaleX * w * dw / sw), 'px;',
+ ' height:', Math.round(scaleY * h * dh / sh), 'px;',
+ ' filter:');
+
+
+ // If there is a globalAlpha, apply it to image
+ if(this.globalAlpha < 1) {
+ vmlStr.push(' progid:DXImageTransform.Microsoft.Alpha(opacity=' + (this.globalAlpha * 100) + ')');
+ }
+
+
+ vmlStr.push(' progid:DXImageTransform.Microsoft.AlphaImageLoader(src=', image.src, ',sizingMethod=scale)">');
+
+ // Close the crop div if necessary
+
+ // Close the crop div if necessary
+ if (sx || sy) vmlStr.push('</div>');
+
+
+ vmlStr.push('</div></div>');
+
+
+ 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.

View file

@ -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(' <div style="position:absolute;');
@ -846,21 +846,21 @@ if (!document.createElement('canvas').getContext) {
// Scaling images using width & height instead of Transform Matrix
// because of quality loss
var c = mc(this.rotation_);
var c = mc(this.rotation_);
var s = ms(this.rotation_);
// Inverse rotation matrix
var irm = [
[c, -s, 0],
[s, c, 0],
[0, 0, 1]
];
];
// Get unrotated matrix to get only scaling values
var urm = matrixMultiply(irm, this.m_);
var urm = matrixMultiply(irm, this.m_);
scaleX = urm[0][0];
scaleY = urm[1][1];
// Apply only rotation and translation to Matrix
filter.push('M11=', c, ',',
'M12=', -s, ',',
@ -896,25 +896,25 @@ if (!document.createElement('canvas').getContext) {
' filter:progid:DxImageTransform.Microsoft.Matrix(Dx=',
-sx * dw / sw * scaleX, ',Dy=', -sy * dh / sh * scaleY, ');">');
}
// Apply scales to width and height
vmlStr.push('<div style="width:', Math.round(scaleX * w * dw / sw), 'px;',
' height:', Math.round(scaleY * h * dh / sh), 'px;',
' filter:');
// If there is a globalAlpha, apply it to image
if(this.globalAlpha < 1) {
vmlStr.push(' progid:DXImageTransform.Microsoft.Alpha(opacity=' + (this.globalAlpha * 100) + ')');
}
vmlStr.push(' progid:DXImageTransform.Microsoft.AlphaImageLoader(src=', image.src, ',sizingMethod=scale)">');
// Close the crop div if necessary
// Close the crop div if necessary
if (sx || sy) vmlStr.push('</div>');
vmlStr.push('</div></div>');
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.

View file

@ -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');
};
}
}());
}());

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View file

@ -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 <kangax@gmail.com>",
"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"
}

View file

@ -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 });
}

View file

@ -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();

View file

@ -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);

View file

@ -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;
}
});

View file

@ -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
};
},

View file

@ -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);

View file

@ -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);
}
};

View file

@ -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

View file

@ -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;
},
/**

View file

@ -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<h; y++) {
for (var x=0; x<w; x++) {
var sy = y;
var sx = x;
var dstOff = (y*w+x)*4;
// calculate the weighed sum of the source image pixels that
// fall under the convolution matrix
var r=0, g=0, b=0, a=0;
for (var y = 0; y < h; y++) {
for (var x = 0; x < w; x++) {
var sy = y,
sx = x,
dstOff = (y * w + x) * 4,
// calculate the weighed sum of the source image pixels that
// fall under the convolution matrix
r = 0, g = 0, b = 0, a = 0;
for (var cy=0; cy<side; cy++) {
for (var cx=0; cx<side; cx++) {
for (var cy = 0; cy < side; cy++) {
for (var cx = 0; cx < side; cx++) {
var scy = sy + cy - halfSide;
var scx = sx + cx - halfSide;
var scy = sy + cy - halfSide,
scx = sx + cx - halfSide;
/* jshint maxdepth:5 */
if (scy < 0 || scy > 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);

View file

@ -1,6 +1,6 @@
(function(global) {
"use strict";
'use strict';
var fabric = global.fabric || (global.fabric = { }),
extend = fabric.util.object.extend;

View file

@ -1,6 +1,6 @@
(function(global) {
"use strict";
'use strict';
var fabric = global.fabric || (global.fabric = { });

View file

@ -1,6 +1,6 @@
(function(global) {
"use strict";
'use strict';
var fabric = global.fabric || (global.fabric = { });

View file

@ -1,6 +1,6 @@
(function(global) {
"use strict";
'use strict';
var fabric = global.fabric || (global.fabric = { }),
extend = fabric.util.object.extend;

View file

@ -0,0 +1,92 @@
(function(global) {
'use strict';
var fabric = global.fabric || (global.fabric = { }),
extend = fabric.util.object.extend;
/**
* Multiply filter class
* Adapted from <a href="http://www.laurenscorijn.com/articles/colormath-basics">http://www.laurenscorijn.com/articles/colormath-basics</a>
* @class fabric.Image.filters.Multiply
* @memberOf fabric.Image.filters
* @extends fabric.Image.filters.BaseFilter
* @example <caption>Multiply filter with hex color</caption>
* var filter = new fabric.Image.filters.Multiply({
* color: '#F0F'
* });
* object.filters.push(filter);
* object.applyFilters(canvas.renderAll.bind(canvas));
* @example <caption>Multiply filter with rgb color</caption>
* 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);

View file

@ -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;
},
/**

View file

@ -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

View file

@ -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;
}
}

View file

@ -1,6 +1,6 @@
(function(global) {
"use strict";
'use strict';
var fabric = global.fabric || (global.fabric = { });

View file

@ -1,6 +1,6 @@
(function(global) {
"use strict";
'use strict';
var fabric = global.fabric || (global.fabric = { });

View file

@ -1,6 +1,6 @@
(function(global) {
"use strict";
'use strict';
var fabric = global.fabric || (global.fabric = { }),
extend = fabric.util.object.extend;

View file

@ -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;
},

View file

@ -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);

View file

@ -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<len; i++) {
for (var i = 0, len = propsToAnimate.length; i < len; i++) {
prop = propsToAnimate[i];
skipCallbacks = i !== len - 1;
this._animate(prop, arguments[0][prop], arguments[1], skipCallbacks);
@ -162,7 +162,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
* @param {Boolean} [skipCallbacks] When true, callbacks like onchange and oncomplete are not invoked
*/
_animate: function(property, to, options, skipCallbacks) {
var obj = this, propPair;
var _this = this, propPair;
to = to.toString();
@ -199,14 +199,14 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
easing: options.easing,
duration: options.duration,
abort: options.abort && function() {
return options.abort.call(obj);
return options.abort.call(_this);
},
onChange: function(value) {
if (propPair) {
obj[propPair[0]][propPair[1]] = value;
_this[propPair[0]][propPair[1]] = value;
}
else {
obj.set(property, value);
_this.set(property, value);
}
if (skipCallbacks) return;
options.onChange && options.onChange();
@ -214,7 +214,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
onComplete: function() {
if (skipCallbacks) return;
obj.setCoords();
_this.setCoords();
options.onComplete && options.onComplete();
}
});

View file

@ -59,8 +59,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') {
@ -87,9 +87,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;
@ -122,7 +121,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) {
@ -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

View file

@ -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];
}
});
})();

View file

@ -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;

View file

@ -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',

View file

@ -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;

View file

@ -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;
},
/**

View file

@ -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] = { };

View file

@ -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;
}
});

View file

@ -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--;

View file

@ -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('');
},
/**

View file

@ -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),

View file

@ -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;

View file

@ -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
*/

View file

@ -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;
},
/**

View file

@ -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;

View file

@ -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(/<!DOCTYPE[\s\S]*?(\[[\s\S]*\])*?>/i,''));
}
if (!xml.documentElement) return;
if (!xml || !xml.documentElement) return;
fabric.parseSVGDocument(xml.documentElement, function (results, options) {
svgCache.set(url, {

View file

@ -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);
}

View file

@ -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;
},
/**

View file

@ -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 {

View file

@ -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);
},
/**

View file

@ -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;

View file

@ -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_ */

View file

@ -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

View file

@ -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
* </pre>
*
* <p>Supported mouse/touch combination</p>
@ -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 = [ ];
})();

View file

@ -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);

View file

@ -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 "#<fabric." + capitalize(this.type) + ">";
return '#<fabric.' + capitalize(this.type) + '>';
},
/**
@ -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', &hellip;)`)
* @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;
}
}
});

View file

@ -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);

View file

@ -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 = [
'<g ',
'style="', this.getSvgStyles(), '" ',
'transform="', this.getSvgTransform(), '" ',
'>'
];
var objects = this.getObjects(),
markup = [
'<g ',
'style="', this.getSvgStyles(), '" ',
'transform="', this.getSvgTransform(), '" ',
'>'
];
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;
});
},

View file

@ -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();

View file

@ -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);
}
},

View file

@ -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('');
},

View file

@ -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 <tspan> elements since setting opacity
// on containing <text> 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 (<b>not yet implemented</b>)
@ -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);
/*

View file

@ -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(
'<polygon ',

View file

@ -1,6 +1,6 @@
(function () {
"use strict";
'use strict';
if (fabric.StaticCanvas) {
fabric.warn('fabric.StaticCanvas is already defined.');
@ -124,7 +124,14 @@
* @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
@ -151,6 +158,7 @@
this._createLowerCanvas(el);
this._initOptions(options);
this._setImageSmoothing();
if (options.overlayImage) {
this.setOverlayImage(options.overlayImage, this.renderAll.bind(this));
@ -310,6 +318,20 @@
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}
@ -397,11 +419,14 @@
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';
},
@ -723,8 +748,8 @@
*/
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);
@ -765,12 +790,21 @@
* @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]);
}
}
}
},
/**
@ -859,9 +893,7 @@
activeGroup.render(ctx);
}
if (this.overlayImage) {
ctx.drawImage(this.overlayImage, this.overlayImageLeft, this.overlayImageTop);
}
this._renderOverlay(ctx);
this.fire('after:render');
@ -978,11 +1010,20 @@
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;
},
@ -1119,7 +1160,7 @@
'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="' +
@ -1251,7 +1292,7 @@
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]) ||
@ -1281,7 +1322,7 @@
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);
@ -1314,7 +1355,7 @@
}
}
else {
newIdx = idx+1;
newIdx = idx + 1;
}
return newIdx;
@ -1349,7 +1390,7 @@
* @return {String} string representation of an instance
*/
toString: function () {
return '#<fabric.Canvas (' + this.complexity() + '): '+
return '#<fabric.Canvas (' + this.complexity() + '): ' +
'{ objects: ' + this.getObjects().length + ' }>';
}
});

View file

@ -1,8 +1,13 @@
(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 };
}
@ -17,7 +22,7 @@
* @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;
}
/**
@ -26,8 +31,10 @@
*/
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;
}
/**
@ -35,7 +42,7 @@
* @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;
}
/**
@ -43,7 +50,7 @@
* @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;
}
/**
@ -51,9 +58,11 @@
* @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;
}
/**
@ -61,7 +70,7 @@
* @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;
}
/**
@ -69,7 +78,7 @@
* @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;
}
/**
@ -77,9 +86,11 @@
* @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;
}
/**
@ -87,7 +98,7 @@
* @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;
}
/**
@ -95,7 +106,7 @@
* @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;
}
/**
@ -103,7 +114,7 @@
* @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;
}
/**
@ -111,7 +122,7 @@
* @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;
}
/**
@ -119,7 +130,7 @@
* @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;
}
/**
@ -127,11 +138,17 @@
* @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;
}
/**
@ -139,7 +156,7 @@
* @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;
}
/**
@ -147,7 +164,7 @@
* @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;
}
/**
@ -155,9 +172,11 @@
* @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;
}
/**
@ -165,11 +184,17 @@
* @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;
}
@ -179,13 +204,19 @@
* @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;
}
/**
@ -193,14 +224,22 @@
* @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;
}
/**
@ -208,8 +247,10 @@
* @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;
}
/**
@ -217,8 +258,10 @@
* @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;
}
/**
@ -226,10 +269,14 @@
* @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;
}
/**
@ -237,7 +284,7 @@
* @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;
}
/**
@ -245,14 +292,17 @@
* @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;
}
}
@ -261,8 +311,10 @@
* @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;
}
/**
@ -277,7 +329,7 @@
* @memberOf fabric.util.ease
*/
easeInQuad: function(t, b, c, d) {
return c*(t/=d)*t + b;
return c * (t /= d) * t + b;
},
/**
@ -285,7 +337,7 @@
* @memberOf fabric.util.ease
*/
easeOutQuad: function(t, b, c, d) {
return -c *(t/=d)*(t-2) + b;
return -c * (t /= d) * (t - 2) + b;
},
/**
@ -293,9 +345,11 @@
* @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;
},
/**
@ -303,7 +357,7 @@
* @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,

View file

@ -10,7 +10,7 @@
* @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) {
@ -22,7 +22,7 @@
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;
@ -62,9 +62,9 @@
* @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;

View file

@ -14,37 +14,43 @@
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<segments; i++) {
var th2 = th0 + i * th_arc / segments;
var th3 = th0 + (i+1) * th_arc / segments;
result[i] = [xc, yc, th2, th3, rx, ry, coords.sin_th, coords.cos_th];
var sfactor = Math.sqrt(sfactorSq);
if (sweep === large) {
sfactor = -sfactor;
}
var xc = 0.5 * (coords.x0 + coords.x1) - sfactor * (coords.y1 - coords.y0),
yc = 0.5 * (coords.y0 + coords.y1) + sfactor * (coords.x1 - coords.x0),
th0 = Math.atan2(coords.y0 - yc, coords.x0 - xc),
th1 = Math.atan2(coords.y1 - yc, coords.x1 - xc),
thArc = th1 - th0;
if (thArc < 0 && sweep === 1) {
thArc += 2 * Math.PI;
}
else if (thArc > 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;
@ -53,56 +59,59 @@
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,
@ -121,17 +130,18 @@
* @param {Array} coords
*/
fabric.util.drawArc = function(ctx, x, y, coords) {
var rx = coords[0];
var ry = coords[1];
var rot = coords[2];
var large = coords[3];
var sweep = coords[4];
var ex = coords[5];
var ey = coords[6];
var segs = arcToSegments(ex, ey, rx, ry, large, sweep, rot, x, y);
for (var i=0; i<segs.length; i++) {
var bez = segmentToBezier.apply(this, segs[i]);
ctx.bezierCurveTo.apply(ctx, bez);
var rx = coords[0],
ry = coords[1],
rot = coords[2],
large = coords[3],
sweep = coords[4],
ex = coords[5],
ey = coords[6],
segs = arcToSegments(ex, ey, rx, ry, large, sweep, rot, x, y);
for (var i = 0; i < segs.length; i++) {
var bez = segmentToBezier.apply(this, segs[i]);
ctx.bezierCurveTo.apply(ctx, bez);
}
};
})();

View file

@ -13,15 +13,16 @@
}
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 = { };
@ -177,9 +178,9 @@
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,
@ -192,9 +193,9 @@
// 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);
};

View file

@ -12,21 +12,21 @@
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) {
@ -68,7 +68,7 @@
* @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;
}
}
@ -91,7 +91,14 @@
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,
@ -142,19 +149,19 @@
*/
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) {
@ -162,7 +169,7 @@
}
docElem = doc.documentElement;
if ( typeof element.getBoundingClientRect !== "undefined" ) {
if ( typeof element.getBoundingClientRect !== 'undefined' ) {
box = element.getBoundingClientRect();
}
@ -181,33 +188,33 @@
* @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
@ -260,7 +267,7 @@
* @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;

View file

@ -6,9 +6,9 @@
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--; ) {

View file

@ -215,7 +215,7 @@
* @private
*/
function find(array, byProperty, condition) {
if (!array || array.length === 0) return undefined;
if (!array || array.length === 0) return;
var i = array.length - 1,
result = byProperty ? array[i][byProperty] : array[i];

View file

@ -1,50 +1,50 @@
(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() { }

View file

@ -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;

View file

@ -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 <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/Trim">String#trim on MDN</a>
*/
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 <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/Trim">String#trim on MDN</a>
* 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, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&apos;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
/**
* 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, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&apos;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
/**
* String utilities
* @namespace fabric.util.string
*/
fabric.util.string = {
camelize: camelize,
capitalize: capitalize,
escapeXml: escapeXml
};
}());

View file

@ -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) {

View file

@ -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',

View file

@ -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);
}

View file

@ -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);

View file

@ -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);
});
})();

Some files were not shown because too many files have changed in this diff Show more