mirror of
https://github.com/Hopiu/fabric.js.git
synced 2026-05-25 05:33:44 +00:00
Merge master
This commit is contained in:
commit
b55c35f865
61 changed files with 4363 additions and 3250 deletions
|
|
@ -1,6 +1,7 @@
|
|||
src/
|
||||
lib/
|
||||
dist/all.min.js
|
||||
dist/all.min.js.gz
|
||||
.DS_Store
|
||||
HEADER.js
|
||||
build_docs.js
|
||||
build.js
|
||||
|
|
@ -2,6 +2,7 @@ language: node_js
|
|||
node_js:
|
||||
- 0.6
|
||||
- 0.8
|
||||
- 0.9
|
||||
before_install:
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install -qq libgif-dev libpng-dev libjpeg8-dev libpango1.0-dev libcairo2-dev
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
/*! Fabric.js Copyright 2008-2012, Printio (Juriy Zaytsev, Maxim Chernyak) */
|
||||
/*! Fabric.js Copyright 2008-2013, Printio (Juriy Zaytsev, Maxim Chernyak) */
|
||||
|
||||
var fabric = fabric || { version: "1.0.0" };
|
||||
var fabric = fabric || { version: "1.0.6" };
|
||||
|
||||
if (typeof exports !== 'undefined') {
|
||||
exports.fabric = fabric;
|
||||
|
|
|
|||
2
LICENSE
2
LICENSE
|
|
@ -1,4 +1,4 @@
|
|||
Copyright (c) 2008-2012 Printio (Juriy Zaytsev, Maxim Chernyak)
|
||||
Copyright (c) 2008-2013 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
|
||||
|
|
|
|||
11
README.md
11
README.md
|
|
@ -6,12 +6,12 @@
|
|||
|
||||
Using Fabric.js, you can create and populate objects on canvas; objects like simple geometrical shapes — rectangles, circles, ellipses, polygons, or more complex shapes consisting of hundreds or thousands of simple paths. You can then scale, move, and rotate these objects with the mouse; modify their properties — color, transparency, z-index, etc. You can also manipulate these objects altogether — grouping them with a simple mouse selection.
|
||||
|
||||
Contributions are very much welcome!
|
||||
[Contributions](https://github.com/kangax/fabric.js/wiki/Love-Fabric%3F-Help-us-by...) are very much welcome!
|
||||
|
||||
### Goals
|
||||
|
||||
- Unit tested (1400+ tests at the moment)
|
||||
- Modular (~20 small "classes" and modules)
|
||||
- Modular (~40 small "classes", modules, mixins)
|
||||
- Cross-browser
|
||||
- [Fast](https://github.com/kangax/fabric.js/wiki/Focus-on-speed)
|
||||
- Encapsulated in one object
|
||||
|
|
@ -89,7 +89,7 @@ Fabric.js started as a foundation for design editor on [printio.ru](http://print
|
|||
|
||||
Documentation is always available at [http://fabricjs.com/docs/](http://fabricjs.com/docs/). You can also build it locally, following step 4 from the "Building" section of this README.
|
||||
|
||||
Also see [presentation from BK.js](http://www.slideshare.net/kangax/fabricjs-building-acanvaslibrarybk) and [presentation from Falsy Values](http://www.slideshare.net/kangax/fabric-falsy-values-8067834) for an overview of fabric.js, how it works, and its features.
|
||||
Also see [official 4-part intro series](http://fabricjs.com/articles), [presentation from BK.js](http://www.slideshare.net/kangax/fabricjs-building-acanvaslibrarybk) and [presentation from Falsy Values](http://www.slideshare.net/kangax/fabric-falsy-values-8067834) for an overview of fabric.js, how it works, and its features.
|
||||
|
||||
### Optional modules
|
||||
|
||||
|
|
@ -103,6 +103,7 @@ These are the optional modules that could be specified for inclusion, when build
|
|||
- **easing** - Adds support for animation easing functions
|
||||
- **node** — Adds support for running fabric under node.js, with help of [jsdom](https://github.com/tmpvar/jsdom) and [node-canvas](https://github.com/learnboost/node-canvas) libraries.
|
||||
- **freedrawing** - Adds support for free drawing
|
||||
- **gestures** - Adds support for multitouch gestures
|
||||
|
||||
### Examples of use
|
||||
|
||||
|
|
@ -131,11 +132,11 @@ Follow [@fabric.js](http://twitter.com/fabricjs) or [@kangax](http://twitter.com
|
|||
- Ernest Delgado for the original idea of [manipulating images on canvas](http://www.ernestdelgado.com/archive/canvas/).
|
||||
- [Maxim "hakunin" Chernyak](http://twitter.com/hakunin) for ideas, and help with various parts of the library throughout its life.
|
||||
- [Sergey Nisnevich](http://nisnya.com) for help with geometry logic.
|
||||
- Github contributors: @Kingsquare, @cleercode, @jarek-itmore, @sunrei, @khronnuz, @ollym, @Kienz, @willmcneilly, @davidjrice
|
||||
- Github contributors: @Kingsquare, @cleercode, @jarek-itmore, @sunrei, @khronnuz, @ollym, @Kienz, @garg, @sjpemberton09, @willmcneilly, @davidjrice, @coulix, and [more](https://github.com/kangax/fabric.js/graphs/contributors)
|
||||
|
||||
### MIT License
|
||||
|
||||
Copyright (c) 2008-2012 Printio (Juriy Zaytsev, Maxim Chernyak)
|
||||
Copyright (c) 2008-2013 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
|
||||
|
|
|
|||
22
build.js
22
build.js
|
|
@ -18,13 +18,13 @@ var minifier = buildArgsAsObject.minifier || 'uglifyjs';
|
|||
var mininfierCmd;
|
||||
|
||||
if (minifier === 'yui') {
|
||||
mininfierCmd = 'java -jar lib/yuicompressor-2.4.2.jar dist/all.js -o dist/all.min.js';
|
||||
mininfierCmd = 'java -jar lib/yuicompressor-2.4.6.jar dist/all.js -o dist/all.min.js';
|
||||
}
|
||||
else if (minifier === 'closure') {
|
||||
mininfierCmd = 'java -jar lib/google_closure_compiler.jar --js dist/all.js --js_output_file dist/all.min.js';
|
||||
}
|
||||
else if (minifier === 'uglifyjs') {
|
||||
mininfierCmd = 'uglifyjs -o dist/all.min.js dist/all.js';
|
||||
mininfierCmd = 'uglifyjs --output dist/all.min.js dist/all.js';
|
||||
}
|
||||
|
||||
var includeAllModules = modulesToInclude.length === 1 && modulesToInclude[0] === 'ALL';
|
||||
|
|
@ -91,7 +91,7 @@ var filesToInclude = [
|
|||
ifSpecifiedInclude('gestures', 'lib/event.js'),
|
||||
|
||||
'src/log.js',
|
||||
'src/observable.js',
|
||||
'src/observable.mixin.js',
|
||||
|
||||
'src/util/misc.js',
|
||||
'src/util/lang_array.js',
|
||||
|
|
@ -109,6 +109,8 @@ var filesToInclude = [
|
|||
ifSpecifiedInclude('parser', 'src/parser.js'),
|
||||
|
||||
'src/gradient.class.js',
|
||||
'src/pattern.class.js',
|
||||
'src/shadow.class.js',
|
||||
'src/point.class.js',
|
||||
'src/intersection.class.js',
|
||||
'src/color.class.js',
|
||||
|
|
@ -123,13 +125,19 @@ var filesToInclude = [
|
|||
ifSpecifiedInclude('freedrawing', 'src/pattern_brush.class.js'),
|
||||
|
||||
ifSpecifiedInclude('interaction', 'src/canvas.class.js'),
|
||||
ifSpecifiedInclude('interaction', 'src/canvas_events.mixin.js'),
|
||||
|
||||
'src/canvas.animation.js',
|
||||
'src/canvas_animation.mixin.js',
|
||||
|
||||
ifSpecifiedInclude('serialization', 'src/canvas.serialization.js'),
|
||||
ifSpecifiedInclude('gestures', 'src/canvas.gestures.js'),
|
||||
ifSpecifiedInclude('serialization', 'src/canvas_serialization.mixin.js'),
|
||||
ifSpecifiedInclude('gestures', 'src/canvas_gestures.mixin.js'),
|
||||
|
||||
'src/object.class.js',
|
||||
'src/object_origin.mixin.js',
|
||||
'src/object_geometry.mixin.js',
|
||||
|
||||
ifSpecifiedInclude('interaction', 'src/object_interactivity.mixin.js'),
|
||||
|
||||
'src/line.class.js',
|
||||
'src/circle.class.js',
|
||||
'src/triangle.class.js',
|
||||
|
|
@ -142,7 +150,7 @@ var filesToInclude = [
|
|||
'src/group.class.js',
|
||||
'src/image.class.js',
|
||||
|
||||
ifSpecifiedInclude('object_straightening', 'src/object_straightening.js'),
|
||||
ifSpecifiedInclude('object_straightening', 'src/object_straightening.mixin.js'),
|
||||
|
||||
ifSpecifiedInclude('image_filters', 'src/image_filters.js'),
|
||||
|
||||
|
|
|
|||
3367
dist/all.js
vendored
3367
dist/all.js
vendored
File diff suppressed because it is too large
Load diff
10
dist/all.min.js
vendored
10
dist/all.min.js
vendored
File diff suppressed because one or more lines are too long
|
|
@ -6,7 +6,6 @@
|
|||
function initAligningGuidelines(canvas) {
|
||||
|
||||
var ctx = canvas.getSelectionContext(),
|
||||
canvasHeight = canvas.getHeight(),
|
||||
aligningLineOffset = 5,
|
||||
aligningLineMargin = 4,
|
||||
aligningLineWidth = 1,
|
||||
|
|
@ -57,11 +56,16 @@ function initAligningGuidelines(canvas) {
|
|||
|
||||
var activeObject = e.target,
|
||||
canvasObjects = canvas.getObjects(),
|
||||
activeObjectLeft = activeObject.get('left'),
|
||||
activeObjectTop = activeObject.get('top'),
|
||||
activeObjectHeight = activeObject.getHeight(),
|
||||
activeObjectWidth = activeObject.getWidth(),
|
||||
noneInTheRange = true;
|
||||
activeObjectCenter = activeObject.getCenterPoint(),
|
||||
activeObjectLeft = activeObjectCenter.x,
|
||||
activeObjectTop = activeObjectCenter.y,
|
||||
activeObjectHeight = activeObject.getBoundingRectHeight(),
|
||||
activeObjectWidth = activeObject.getBoundingRectWidth(),
|
||||
horizontalInTheRange = false,
|
||||
verticalInTheRange = false,
|
||||
transform = canvas._currentTransform;
|
||||
|
||||
if (!transform) return;
|
||||
|
||||
// It should be trivial to DRY this up by encapsulating (repeating) creation of x1, x2, y1, and y2 into functions,
|
||||
// but we're not doing it here for perf. reasons -- as this a function that's invoked on every mouse move
|
||||
|
|
@ -70,14 +74,15 @@ function initAligningGuidelines(canvas) {
|
|||
|
||||
if (canvasObjects[i] === activeObject) continue;
|
||||
|
||||
var objectLeft = canvasObjects[i].get('left'),
|
||||
objectTop = canvasObjects[i].get('top'),
|
||||
objectHeight = canvasObjects[i].getHeight(),
|
||||
objectWidth = canvasObjects[i].getWidth();
|
||||
var objectCenter = canvasObjects[i].getCenterPoint(),
|
||||
objectLeft = objectCenter.x,
|
||||
objectTop = objectCenter.y,
|
||||
objectHeight = canvasObjects[i].getBoundingRectHeight(),
|
||||
objectWidth = canvasObjects[i].getBoundingRectWidth();
|
||||
|
||||
// snap by the horizontal center line
|
||||
if (isInRange(objectLeft, activeObjectLeft)) {
|
||||
noneInTheRange = false;
|
||||
verticalInTheRange = true;
|
||||
verticalLines.push({
|
||||
x: objectLeft,
|
||||
y1: (objectTop < activeObjectTop)
|
||||
|
|
@ -87,12 +92,12 @@ function initAligningGuidelines(canvas) {
|
|||
? (activeObjectTop + activeObjectHeight / 2 + aligningLineOffset)
|
||||
: (activeObjectTop - activeObjectHeight / 2 - aligningLineOffset)
|
||||
});
|
||||
activeObject.set('left', objectLeft);
|
||||
activeObject.setPositionByOrigin(new fabric.Point(objectLeft, activeObjectTop), transform.originX, transform.originY);
|
||||
}
|
||||
|
||||
// snap by the left edge
|
||||
if (isInRange(objectLeft - objectWidth / 2, activeObjectLeft - activeObjectWidth / 2)) {
|
||||
noneInTheRange = false;
|
||||
verticalInTheRange = true;
|
||||
verticalLines.push({
|
||||
x: objectLeft - objectWidth / 2,
|
||||
y1: (objectTop < activeObjectTop)
|
||||
|
|
@ -102,12 +107,12 @@ function initAligningGuidelines(canvas) {
|
|||
? (activeObjectTop + activeObjectHeight / 2 + aligningLineOffset)
|
||||
: (activeObjectTop - activeObjectHeight / 2 - aligningLineOffset)
|
||||
});
|
||||
activeObject.set('left', objectLeft - objectWidth / 2 + activeObjectWidth / 2);
|
||||
activeObject.setPositionByOrigin(new fabric.Point(objectLeft - objectWidth / 2 + activeObjectWidth / 2, activeObjectTop), transform.originX, transform.originY);
|
||||
}
|
||||
|
||||
// snap by the right edge
|
||||
if (isInRange(objectLeft + objectWidth / 2, activeObjectLeft + activeObjectWidth / 2)) {
|
||||
noneInTheRange = false;
|
||||
verticalInTheRange = true;
|
||||
verticalLines.push({
|
||||
x: objectLeft + objectWidth / 2,
|
||||
y1: (objectTop < activeObjectTop)
|
||||
|
|
@ -117,12 +122,12 @@ function initAligningGuidelines(canvas) {
|
|||
? (activeObjectTop + activeObjectHeight / 2 + aligningLineOffset)
|
||||
: (activeObjectTop - activeObjectHeight / 2 - aligningLineOffset)
|
||||
});
|
||||
activeObject.set('left', objectLeft + objectWidth / 2 - activeObjectWidth / 2);
|
||||
activeObject.setPositionByOrigin(new fabric.Point(objectLeft + objectWidth / 2 - activeObjectWidth / 2, activeObjectTop), transform.originX, transform.originY);
|
||||
}
|
||||
|
||||
// snap by the vertical center line
|
||||
if (isInRange(objectTop, activeObjectTop)) {
|
||||
noneInTheRange = false;
|
||||
horizontalInTheRange = true;
|
||||
horizontalLines.push({
|
||||
y: objectTop,
|
||||
x1: (objectLeft < activeObjectLeft)
|
||||
|
|
@ -132,12 +137,12 @@ function initAligningGuidelines(canvas) {
|
|||
? (activeObjectLeft + activeObjectWidth / 2 + aligningLineOffset)
|
||||
: (activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset)
|
||||
});
|
||||
activeObject.set('top', objectTop);
|
||||
activeObject.setPositionByOrigin(new fabric.Point(activeObjectLeft, objectTop), transform.originX, transform.originY);
|
||||
}
|
||||
|
||||
// snap by the top edge
|
||||
if (isInRange(objectTop - objectHeight / 2, activeObjectTop - activeObjectHeight / 2)) {
|
||||
noneInTheRange = false;
|
||||
horizontalInTheRange = true;
|
||||
horizontalLines.push({
|
||||
y: objectTop - objectHeight / 2,
|
||||
x1: (objectLeft < activeObjectLeft)
|
||||
|
|
@ -147,12 +152,12 @@ function initAligningGuidelines(canvas) {
|
|||
? (activeObjectLeft + activeObjectWidth / 2 + aligningLineOffset)
|
||||
: (activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset)
|
||||
});
|
||||
activeObject.set('top', objectTop - objectHeight / 2 + activeObjectHeight / 2);
|
||||
activeObject.setPositionByOrigin(new fabric.Point(activeObjectLeft, objectTop - objectHeight / 2 + activeObjectHeight / 2), transform.originX, transform.originY);
|
||||
}
|
||||
|
||||
// snap by the bottom edge
|
||||
if (isInRange(objectTop + objectHeight / 2, activeObjectTop + activeObjectHeight / 2)) {
|
||||
noneInTheRange = false;
|
||||
horizontalInTheRange = true;
|
||||
horizontalLines.push({
|
||||
y: objectTop + objectHeight / 2,
|
||||
x1: (objectLeft < activeObjectLeft)
|
||||
|
|
@ -162,13 +167,21 @@ function initAligningGuidelines(canvas) {
|
|||
? (activeObjectLeft + activeObjectWidth / 2 + aligningLineOffset)
|
||||
: (activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset)
|
||||
});
|
||||
activeObject.set('top', objectTop + objectHeight / 2 - activeObjectHeight / 2);
|
||||
activeObject.setPositionByOrigin(new fabric.Point(activeObjectLeft, objectTop + objectHeight / 2 - activeObjectHeight / 2), transform.originX, transform.originY);
|
||||
}
|
||||
}
|
||||
|
||||
if (noneInTheRange) {
|
||||
verticalLines.length = horizontalLines.length = 0;
|
||||
if (!horizontalInTheRange) {
|
||||
horizontalLines.length = 0;
|
||||
}
|
||||
|
||||
if (!verticalInTheRange) {
|
||||
verticalLines.length = 0;
|
||||
}
|
||||
});
|
||||
|
||||
canvas.on('before:render', function() {
|
||||
canvas.clearContext(canvas.contextTop);
|
||||
});
|
||||
|
||||
canvas.on('after:render', function() {
|
||||
|
|
@ -184,4 +197,4 @@ function initAligningGuidelines(canvas) {
|
|||
verticalLines.length = horizontalLines.length = 0;
|
||||
canvas.renderAll();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,19 +48,24 @@ function initCenteringGuidelines(canvas) {
|
|||
isInHorizontalCenter;
|
||||
|
||||
canvas.on('object:moving', function(e) {
|
||||
object = e.target;
|
||||
var object = e.target,
|
||||
objectCenter = object.getCenterPoint(),
|
||||
transform = canvas._currentTransform;
|
||||
|
||||
isInVerticalCenter = object.get('left') in canvasWidthCenterMap,
|
||||
isInHorizontalCenter = object.get('top') in canvasHeightCenterMap;
|
||||
if (!transform) return;
|
||||
|
||||
if (isInHorizontalCenter) {
|
||||
object.set('top', canvasHeightCenter);
|
||||
}
|
||||
if (isInVerticalCenter) {
|
||||
object.set('left', canvasWidthCenter);
|
||||
isInVerticalCenter = objectCenter.x in canvasWidthCenterMap,
|
||||
isInHorizontalCenter = objectCenter.y in canvasHeightCenterMap;
|
||||
|
||||
if (isInHorizontalCenter || isInVerticalCenter) {
|
||||
object.setPositionByOrigin(new fabric.Point((isInVerticalCenter ? canvasWidthCenter : objectCenter.x), (isInHorizontalCenter ? canvasHeightCenter : objectCenter.y)), transform.originX, transform.originY);
|
||||
}
|
||||
});
|
||||
|
||||
canvas.on('before:render', function() {
|
||||
canvas.clearContext(canvas.contextTop);
|
||||
});
|
||||
|
||||
canvas.on('after:render', function() {
|
||||
if (isInVerticalCenter) {
|
||||
showVerticalCenterLine();
|
||||
|
|
|
|||
Binary file not shown.
10
package.json
10
package.json
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "fabric",
|
||||
"description": "Object model for HTML5 canvas, and SVG-to-canvas parser. Backed by jsdom and node-canvas.",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.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",
|
||||
|
|
@ -10,19 +10,19 @@
|
|||
"url": "http://github.com/kangax/fabric.js/raw/master/LICENSE"
|
||||
}],
|
||||
"scripts": {
|
||||
"build": "node build.js modules=ALL exclude=json,cufon",
|
||||
"build": "node build.js modules=ALL exclude=json,cufon,gestures",
|
||||
"test": "node test.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"canvas": "~0.13.0",
|
||||
"canvas": "~1.0.0",
|
||||
"jsdom": ">=0.2.3",
|
||||
"xmldom": ">=0.1.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"qunit": "0.5.x",
|
||||
"jshint": "0.9.x",
|
||||
"uglify-js": "1.3.x"
|
||||
"uglify-js": ">=2.0.0"
|
||||
},
|
||||
"engines": { "node": ">=0.4.0 && <0.9.0" },
|
||||
"engines": { "node": ">=0.4.0 && <1.0.0" },
|
||||
"main": "./dist/all.js"
|
||||
}
|
||||
|
|
@ -2,22 +2,8 @@
|
|||
|
||||
var extend = fabric.util.object.extend,
|
||||
getPointer = fabric.util.getPointer,
|
||||
addListener = fabric.util.addListener,
|
||||
removeListener = fabric.util.removeListener,
|
||||
degreesToRadians = fabric.util.degreesToRadians,
|
||||
radiansToDegrees = fabric.util.radiansToDegrees,
|
||||
cursorMap = {
|
||||
'tr': 'ne-resize',
|
||||
'br': 'se-resize',
|
||||
'bl': 'sw-resize',
|
||||
'tl': 'nw-resize',
|
||||
'ml': 'w-resize',
|
||||
'mt': 'n-resize',
|
||||
'mr': 'e-resize',
|
||||
'mb': 's-resize'
|
||||
},
|
||||
|
||||
sqrt = Math.sqrt,
|
||||
atan2 = Math.atan2,
|
||||
abs = Math.abs,
|
||||
min = Math.min,
|
||||
|
|
@ -178,418 +164,6 @@
|
|||
this.calcOffset();
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds mouse listeners to canvas
|
||||
* @method _initEvents
|
||||
* @private
|
||||
* See configuration documentation for more details.
|
||||
*/
|
||||
_initEvents: function () {
|
||||
var _this = this;
|
||||
|
||||
this._onMouseDown = function (e) {
|
||||
_this.__onMouseDown(e);
|
||||
|
||||
addListener(fabric.document, 'mouseup', _this._onMouseUp);
|
||||
fabric.isTouchSupported && addListener(fabric.document, 'touchend', _this._onMouseUp);
|
||||
|
||||
addListener(fabric.document, 'mousemove', _this._onMouseMove);
|
||||
fabric.isTouchSupported && addListener(fabric.document, 'touchmove', _this._onMouseMove);
|
||||
|
||||
removeListener(_this.upperCanvasEl, 'mousemove', _this._onMouseMove);
|
||||
fabric.isTouchSupported && removeListener(_this.upperCanvasEl, 'touchmove', _this._onMouseMove);
|
||||
};
|
||||
|
||||
this._onMouseUp = function (e) {
|
||||
_this.__onMouseUp(e);
|
||||
|
||||
removeListener(fabric.document, 'mouseup', _this._onMouseUp);
|
||||
fabric.isTouchSupported && removeListener(fabric.document, 'touchend', _this._onMouseUp);
|
||||
|
||||
removeListener(fabric.document, 'mousemove', _this._onMouseMove);
|
||||
fabric.isTouchSupported && removeListener(fabric.document, 'touchmove', _this._onMouseMove);
|
||||
|
||||
addListener(_this.upperCanvasEl, 'mousemove', _this._onMouseMove);
|
||||
fabric.isTouchSupported && addListener(_this.upperCanvasEl, 'touchmove', _this._onMouseMove);
|
||||
};
|
||||
|
||||
this._onMouseMove = function (e) {
|
||||
e.preventDefault && e.preventDefault();
|
||||
_this.__onMouseMove(e);
|
||||
};
|
||||
|
||||
this._onResize = function () {
|
||||
_this.calcOffset();
|
||||
};
|
||||
|
||||
|
||||
addListener(fabric.window, 'resize', this._onResize);
|
||||
|
||||
if (fabric.isTouchSupported) {
|
||||
addListener(this.upperCanvasEl, 'touchstart', this._onMouseDown);
|
||||
addListener(this.upperCanvasEl, 'touchmove', this._onMouseMove);
|
||||
|
||||
if (typeof Event !== 'undefined' && 'add' in Event) {
|
||||
Event.add(this.upperCanvasEl, 'gesture', function(e, s) {
|
||||
_this.__onTransformGesture(e, s);
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
addListener(this.upperCanvasEl, 'mousedown', this._onMouseDown);
|
||||
addListener(this.upperCanvasEl, 'mousemove', this._onMouseMove);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Method that defines the actions when mouse is released on canvas.
|
||||
* The method resets the currentTransform parameters, store the image corner
|
||||
* position in the image object and render the canvas on top.
|
||||
* @method __onMouseUp
|
||||
* @param {Event} e Event object fired on mouseup
|
||||
*
|
||||
*/
|
||||
__onMouseUp: function (e) {
|
||||
|
||||
var target;
|
||||
|
||||
if (this.isDrawingMode && this._isCurrentlyDrawing) {
|
||||
this._isCurrentlyDrawing = false;
|
||||
this.freeDrawingBrush.onMouseUp();
|
||||
this.fire('mouse:up', { e: e });
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._currentTransform) {
|
||||
|
||||
var transform = this._currentTransform;
|
||||
|
||||
target = transform.target;
|
||||
if (target._scaling) {
|
||||
target._scaling = false;
|
||||
}
|
||||
|
||||
// determine the new coords everytime the image changes its position
|
||||
var i = this._objects.length;
|
||||
while (i--) {
|
||||
this._objects[i].setCoords();
|
||||
}
|
||||
|
||||
target.isMoving = false;
|
||||
|
||||
// only fire :modified event if target coordinates were changed during mousedown-mouseup
|
||||
if (this.stateful && target.hasStateChanged()) {
|
||||
this.fire('object:modified', { target: target });
|
||||
target.fire('modified');
|
||||
}
|
||||
|
||||
if (this._previousOriginX) {
|
||||
this._adjustPosition(this._currentTransform.target, this._previousOriginX);
|
||||
this._previousOriginX = null;
|
||||
}
|
||||
}
|
||||
|
||||
this._currentTransform = null;
|
||||
|
||||
if (this._groupSelector) {
|
||||
// group selection was completed, determine its bounds
|
||||
this._findSelectedObjects(e);
|
||||
}
|
||||
var activeGroup = this.getActiveGroup();
|
||||
if (activeGroup) {
|
||||
activeGroup.setObjectsCoords();
|
||||
activeGroup.set('isMoving', false);
|
||||
this._setCursor(this.defaultCursor);
|
||||
}
|
||||
|
||||
// clear selection
|
||||
this._groupSelector = null;
|
||||
this.renderAll();
|
||||
|
||||
this._setCursorFromEvent(e, target);
|
||||
|
||||
// fix for FF
|
||||
this._setCursor('');
|
||||
|
||||
var _this = this;
|
||||
setTimeout(function () {
|
||||
_this._setCursorFromEvent(e, target);
|
||||
}, 50);
|
||||
|
||||
this.fire('mouse:up', { target: target, e: e });
|
||||
target && target.fire('mouseup', { e: e });
|
||||
},
|
||||
|
||||
/**
|
||||
* Method that defines the actions when mouse is clic ked on canvas.
|
||||
* The method inits the currentTransform parameters and renders all the
|
||||
* canvas so the current image can be placed on the top canvas and the rest
|
||||
* in on the container one.
|
||||
* @method __onMouseDown
|
||||
* @param e {Event} Event object fired on mousedown
|
||||
*
|
||||
*/
|
||||
__onMouseDown: function (e) {
|
||||
|
||||
var pointer;
|
||||
|
||||
// accept only left clicks
|
||||
var isLeftClick = 'which' in e ? e.which === 1 : e.button === 1;
|
||||
if (!isLeftClick && !fabric.isTouchSupported) return;
|
||||
|
||||
if (this.isDrawingMode) {
|
||||
pointer = this.getPointer(e);
|
||||
|
||||
this._isCurrentlyDrawing = true;
|
||||
this.discardActiveObject().renderAll();
|
||||
|
||||
this.freeDrawingBrush.onMouseDown(pointer);
|
||||
this.fire('mouse:down', { e: e });
|
||||
return;
|
||||
}
|
||||
|
||||
// ignore if some object is being transformed at this moment
|
||||
if (this._currentTransform) return;
|
||||
|
||||
var target = this.findTarget(e), corner;
|
||||
pointer = this.getPointer(e);
|
||||
|
||||
if (this._shouldClearSelection(e)) {
|
||||
this._groupSelector = {
|
||||
ex: pointer.x,
|
||||
ey: pointer.y,
|
||||
top: 0,
|
||||
left: 0
|
||||
};
|
||||
this.deactivateAllWithDispatch();
|
||||
}
|
||||
else {
|
||||
// determine if it's a drag or rotate case
|
||||
this.stateful && target.saveState();
|
||||
|
||||
if ((corner = target._findTargetCorner(e, this._offset))) {
|
||||
this.onBeforeScaleRotate(target);
|
||||
}
|
||||
|
||||
if (this._shouldHandleGroupLogic(e, target)) {
|
||||
this._handleGroupLogic(e, target);
|
||||
target = this.getActiveGroup();
|
||||
}
|
||||
else {
|
||||
if (target !== this.getActiveGroup()) {
|
||||
this.deactivateAll();
|
||||
}
|
||||
this.setActiveObject(target, e);
|
||||
}
|
||||
|
||||
this._setupCurrentTransform(e, target);
|
||||
}
|
||||
// we must renderAll so that active image is placed on the top canvas
|
||||
this.renderAll();
|
||||
|
||||
this.fire('mouse:down', { target: target, e: e });
|
||||
target && target.fire('mousedown', { e: e });
|
||||
|
||||
// center origin when rotating
|
||||
if (corner === 'mtr') {
|
||||
this._previousOriginX = this._currentTransform.target.originX;
|
||||
this._adjustPosition(this._currentTransform.target, 'center');
|
||||
this._currentTransform.left = this._currentTransform.target.left;
|
||||
this._currentTransform.top = this._currentTransform.target.top;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @method _shouldHandleGroupLogic
|
||||
* @param e {Event}
|
||||
* @param target {fabric.Object}
|
||||
* @return {Boolean}
|
||||
*/
|
||||
_shouldHandleGroupLogic: function(e, target) {
|
||||
var activeObject = this.getActiveObject();
|
||||
return e.shiftKey &&
|
||||
(this.getActiveGroup() || (activeObject && activeObject !== target))
|
||||
&& this.selection;
|
||||
},
|
||||
|
||||
/**
|
||||
* Method that defines the actions when mouse is hovering the canvas.
|
||||
* The currentTransform parameter will definde whether the user is rotating/scaling/translating
|
||||
* an image or neither of them (only hovering). A group selection is also possible and would cancel
|
||||
* all any other type of action.
|
||||
* In case of an image transformation only the top canvas will be rendered.
|
||||
* @method __onMouseMove
|
||||
* @param e {Event} Event object fired on mousemove
|
||||
*
|
||||
*/
|
||||
__onMouseMove: function (e) {
|
||||
|
||||
var target, pointer;
|
||||
|
||||
if (this.isDrawingMode) {
|
||||
if (this._isCurrentlyDrawing) {
|
||||
pointer = this.getPointer(e);
|
||||
this.freeDrawingBrush.onMouseMove(pointer);
|
||||
}
|
||||
this.upperCanvasEl.style.cursor = this.freeDrawingCursor;
|
||||
this.fire('mouse:move', { e: e });
|
||||
return;
|
||||
}
|
||||
|
||||
var groupSelector = this._groupSelector;
|
||||
|
||||
// We initially clicked in an empty area, so we draw a box for multiple selection.
|
||||
if (groupSelector !== null) {
|
||||
pointer = getPointer(e);
|
||||
|
||||
groupSelector.left = pointer.x - this._offset.left - groupSelector.ex;
|
||||
groupSelector.top = pointer.y - this._offset.top - groupSelector.ey;
|
||||
this.renderTop();
|
||||
}
|
||||
else if (!this._currentTransform) {
|
||||
|
||||
// alias style to elimintate unnecessary lookup
|
||||
var style = this.upperCanvasEl.style;
|
||||
|
||||
// Here we are hovering the canvas then we will determine
|
||||
// what part of the pictures we are hovering to change the caret symbol.
|
||||
// We won't do that while dragging or rotating in order to improve the
|
||||
// performance.
|
||||
target = this.findTarget(e);
|
||||
|
||||
if (!target) {
|
||||
// image/text was hovered-out from, we remove its borders
|
||||
for (var i = this._objects.length; i--; ) {
|
||||
if (this._objects[i] && !this._objects[i].active) {
|
||||
this._objects[i].setActive(false);
|
||||
}
|
||||
}
|
||||
style.cursor = this.defaultCursor;
|
||||
}
|
||||
else {
|
||||
// set proper cursor
|
||||
this._setCursorFromEvent(e, target);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// object is being transformed (scaled/rotated/moved/etc.)
|
||||
pointer = getPointer(e);
|
||||
|
||||
var x = pointer.x,
|
||||
y = pointer.y;
|
||||
|
||||
this._currentTransform.target.isMoving = true;
|
||||
|
||||
var t = this._currentTransform, reset = false;
|
||||
if (
|
||||
(t.action === 'scale' || t.action === 'scaleX' || t.action === 'scaleY')
|
||||
&&
|
||||
(
|
||||
// Switch from a normal resize to center-based
|
||||
(e.altKey && (t.originX !== 'center' || t.originY !== 'center'))
|
||||
||
|
||||
// Switch from center-based resize to normal one
|
||||
(!e.altKey && t.originX === 'center' && t.originY === 'center')
|
||||
)
|
||||
) {
|
||||
this._resetCurrentTransform(e);
|
||||
reset = true;
|
||||
}
|
||||
|
||||
if (this._currentTransform.action === 'rotate') {
|
||||
this._rotateObject(x, y);
|
||||
|
||||
this.fire('object:rotating', {
|
||||
target: this._currentTransform.target
|
||||
});
|
||||
this._currentTransform.target.fire('rotating');
|
||||
}
|
||||
else if (this._currentTransform.action === 'scale') {
|
||||
// rotate object only if shift key is not pressed
|
||||
// and if it is not a group we are transforming
|
||||
|
||||
// TODO
|
||||
/*if (!e.shiftKey) {
|
||||
this._rotateObject(x, y);
|
||||
|
||||
this.fire('object:rotating', {
|
||||
target: this._currentTransform.target,
|
||||
e: e
|
||||
});
|
||||
this._currentTransform.target.fire('rotating');
|
||||
}*/
|
||||
|
||||
// if (!this._currentTransform.target.hasRotatingPoint) {
|
||||
// this._scaleObject(x, y);
|
||||
// this.fire('object:scaling', {
|
||||
// target: this._currentTransform.target
|
||||
// });
|
||||
// this._currentTransform.target.fire('scaling');
|
||||
// }
|
||||
|
||||
if (e.shiftKey || this.uniScaleTransform) {
|
||||
this._currentTransform.currentAction = 'scale';
|
||||
this._scaleObject(x, y);
|
||||
}
|
||||
else {
|
||||
if (!reset && t.currentAction === 'scale') {
|
||||
// Switch from a normal resize to proportional
|
||||
this._resetCurrentTransform(e);
|
||||
}
|
||||
|
||||
this._currentTransform.currentAction = 'scaleEqually';
|
||||
this._scaleObject(x, y, 'equally');
|
||||
}
|
||||
|
||||
this.fire('object:scaling', {
|
||||
target: this._currentTransform.target,
|
||||
e: e
|
||||
});
|
||||
}
|
||||
// else if (this._currentTransform.action === 'scale') {
|
||||
// this._scaleObject(x, y);
|
||||
// this.fire('object:scaling', {
|
||||
// target: this._currentTransform.target
|
||||
// });
|
||||
// this._currentTransform.target.fire('scaling');
|
||||
// }
|
||||
else if (this._currentTransform.action === 'scaleX') {
|
||||
this._scaleObject(x, y, 'x');
|
||||
|
||||
this.fire('object:scaling', {
|
||||
target: this._currentTransform.target,
|
||||
e: e
|
||||
});
|
||||
this._currentTransform.target.fire('scaling', { e: e });
|
||||
}
|
||||
else if (this._currentTransform.action === 'scaleY') {
|
||||
this._scaleObject(x, y, 'y');
|
||||
|
||||
this.fire('object:scaling', {
|
||||
target: this._currentTransform.target,
|
||||
e: e
|
||||
});
|
||||
this._currentTransform.target.fire('scaling', { e: e });
|
||||
}
|
||||
else {
|
||||
this._translateObject(x, y);
|
||||
|
||||
this.fire('object:moving', {
|
||||
target: this._currentTransform.target,
|
||||
e: e
|
||||
});
|
||||
|
||||
this._setCursor(this.moveCursor);
|
||||
|
||||
this._currentTransform.target.fire('moving', { e: e });
|
||||
}
|
||||
// only commit here. when we are actually moving the pictures
|
||||
this.renderAll();
|
||||
}
|
||||
this.fire('mouse:move', { target: target, e: e });
|
||||
target && target.fire('mousemove', { e: e });
|
||||
},
|
||||
|
||||
/**
|
||||
* Resets the current transform to its original values and chooses the type of resizing based on the event
|
||||
* @method _resetCurrentTransform
|
||||
|
|
@ -597,6 +171,7 @@
|
|||
*/
|
||||
_resetCurrentTransform: function(e) {
|
||||
var t = this._currentTransform;
|
||||
|
||||
t.target.set('scaleX', t.original.scaleX);
|
||||
t.target.set('scaleY', t.original.scaleY);
|
||||
t.target.set('left', t.original.left);
|
||||
|
|
@ -752,7 +327,7 @@
|
|||
_setupCurrentTransform: function (e, target) {
|
||||
var action = 'drag',
|
||||
corner,
|
||||
pointer = getPointer(e);
|
||||
pointer = getPointer(e, target.canvas.upperCanvasEl);
|
||||
|
||||
corner = target._findTargetCorner(e, this._offset);
|
||||
if (corner) {
|
||||
|
|
@ -818,6 +393,19 @@
|
|||
this._resetCurrentTransform(e);
|
||||
},
|
||||
|
||||
/**
|
||||
* @method _shouldHandleGroupLogic
|
||||
* @param e {Event}
|
||||
* @param target {fabric.Object}
|
||||
* @return {Boolean}
|
||||
*/
|
||||
_shouldHandleGroupLogic: function(e, target) {
|
||||
var activeObject = this.getActiveObject();
|
||||
return e.shiftKey &&
|
||||
(this.getActiveGroup() || (activeObject && activeObject !== target))
|
||||
&& this.selection;
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @method _handleGroupLogic
|
||||
|
|
@ -1019,43 +607,6 @@
|
|||
target.setAngle(0);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the cursor depending on where the canvas is being hovered.
|
||||
* Note: very buggy in Opera
|
||||
* @method _setCursorFromEvent
|
||||
* @param e {Event} Event object
|
||||
* @param target {Object} Object that the mouse is hovering, if so.
|
||||
*/
|
||||
_setCursorFromEvent: function (e, target) {
|
||||
var s = this.upperCanvasEl.style;
|
||||
if (!target) {
|
||||
s.cursor = this.defaultCursor;
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
var activeGroup = this.getActiveGroup();
|
||||
// only show proper corner when group selection is not active
|
||||
var corner = !!target._findTargetCorner
|
||||
&& (!activeGroup || !activeGroup.contains(target))
|
||||
&& target._findTargetCorner(e, this._offset);
|
||||
|
||||
if (!corner) {
|
||||
s.cursor = this.hoverCursor;
|
||||
}
|
||||
else {
|
||||
if (corner in cursorMap) {
|
||||
s.cursor = cursorMap[corner];
|
||||
} else if (corner === 'mtr' && target.hasRotatingPoint) {
|
||||
s.cursor = this.rotationCursor;
|
||||
} else {
|
||||
s.cursor = this.defaultCursor;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* @method _drawSelection
|
||||
* @private
|
||||
|
|
@ -1082,13 +633,17 @@
|
|||
|
||||
// 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);
|
||||
|
||||
ctx.beginPath();
|
||||
this.drawDashedLine(ctx, px, py, px+aleft, py, this.selectionDashArray);
|
||||
this.drawDashedLine(ctx, px, py+atop-1, px+aleft, py+atop-1, this.selectionDashArray);
|
||||
this.drawDashedLine(ctx, px, py, px, py+atop, this.selectionDashArray);
|
||||
this.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();
|
||||
}
|
||||
|
|
@ -1102,47 +657,6 @@
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Draws a dashed line between two points
|
||||
*
|
||||
* This method is used to draw dashed line around selection area.
|
||||
* See <a href="http://stackoverflow.com/questions/4576724/dotted-stroke-in-canvas">dotted stroke in canvas</a>
|
||||
*
|
||||
* @method drawDashedLine
|
||||
* @param ctx {Canvas} context
|
||||
* @param x {Number} start x coordinate
|
||||
* @param y {Number} start y coordinate
|
||||
* @param x2 {Number} end x coordinate
|
||||
* @param y2 {Number} end y coordinate
|
||||
* @param da {Array} dash array pattern
|
||||
*/
|
||||
drawDashedLine: function(ctx, x, y, x2, y2, da) {
|
||||
var dx = x2 - x,
|
||||
dy = y2 - y,
|
||||
len = sqrt(dx*dx + dy*dy),
|
||||
rot = atan2(dy, dx),
|
||||
dc = da.length,
|
||||
di = 0,
|
||||
draw = true;
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(x, y);
|
||||
ctx.moveTo(0, 0);
|
||||
ctx.rotate(rot);
|
||||
|
||||
x = 0;
|
||||
while (len > x) {
|
||||
x += da[di++ % dc];
|
||||
if (x > len) {
|
||||
x = len;
|
||||
}
|
||||
ctx[draw ? 'lineTo' : 'moveTo'](x, 0);
|
||||
draw = !draw;
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @method _findSelectedObjects
|
||||
|
|
@ -1199,7 +713,8 @@
|
|||
|
||||
if (this.controlsAboveOverlay &&
|
||||
this.lastRenderedObjectWithControlsAboveOverlay &&
|
||||
this.containsPoint(e, this.lastRenderedObjectWithControlsAboveOverlay)) {
|
||||
this.containsPoint(e, this.lastRenderedObjectWithControlsAboveOverlay) &&
|
||||
this.lastRenderedObjectWithControlsAboveOverlay._findTargetCorner(e, this._offset)) {
|
||||
target = this.lastRenderedObjectWithControlsAboveOverlay;
|
||||
return target;
|
||||
}
|
||||
|
|
@ -1247,7 +762,7 @@
|
|||
* @return {Object} object with "x" and "y" number values
|
||||
*/
|
||||
getPointer: function (e) {
|
||||
var pointer = getPointer(e);
|
||||
var pointer = getPointer(e, this.upperCanvasEl);
|
||||
return {
|
||||
x: pointer.x - this._offset.left,
|
||||
y: pointer.y - this._offset.top
|
||||
|
|
@ -1452,51 +967,6 @@
|
|||
this.fire('selection:cleared');
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @method _adjustPosition
|
||||
* @param obj
|
||||
* @param {String} to One of left, center, right
|
||||
*/
|
||||
_adjustPosition: function(obj, to) {
|
||||
|
||||
var angle = fabric.util.degreesToRadians(obj.angle);
|
||||
|
||||
var hypotHalf = obj.getWidth() / 2;
|
||||
var xHalf = Math.cos(angle) * hypotHalf;
|
||||
var yHalf = Math.sin(angle) * hypotHalf;
|
||||
|
||||
var hypotFull = obj.getWidth();
|
||||
var xFull = Math.cos(angle) * hypotFull;
|
||||
var yFull = Math.sin(angle) * hypotFull;
|
||||
|
||||
if (obj.originX === 'center' && to === 'left' ||
|
||||
obj.originX === 'right' && to === 'center') {
|
||||
// move half left
|
||||
obj.left -= xHalf;
|
||||
obj.top -= yHalf;
|
||||
}
|
||||
else if (obj.originX === 'left' && to === 'center' ||
|
||||
obj.originX === 'center' && to === 'right') {
|
||||
// move half right
|
||||
obj.left += xHalf;
|
||||
obj.top += yHalf;
|
||||
}
|
||||
else if (obj.originX === 'left' && to === 'right') {
|
||||
// move full right
|
||||
obj.left += xFull;
|
||||
obj.top += yFull;
|
||||
}
|
||||
else if (obj.originX === 'right' && to === 'left') {
|
||||
// move full left
|
||||
obj.left -= xFull;
|
||||
obj.top -= yFull;
|
||||
}
|
||||
|
||||
obj.setCoords();
|
||||
obj.originX = to;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -1522,4 +992,4 @@
|
|||
* @constructor
|
||||
*/
|
||||
fabric.Element = fabric.Canvas;
|
||||
})();
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
fabric.util.object.extend(fabric.StaticCanvas.prototype, {
|
||||
fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @scope fabric.StaticCanvas.prototype */ {
|
||||
|
||||
/**
|
||||
* Animation duration (in ms) for fx* methods
|
||||
475
src/canvas_events.mixin.js
Normal file
475
src/canvas_events.mixin.js
Normal file
|
|
@ -0,0 +1,475 @@
|
|||
(function(){
|
||||
|
||||
var cursorMap = {
|
||||
'tr': 'ne-resize',
|
||||
'br': 'se-resize',
|
||||
'bl': 'sw-resize',
|
||||
'tl': 'nw-resize',
|
||||
'ml': 'w-resize',
|
||||
'mt': 'n-resize',
|
||||
'mr': 'e-resize',
|
||||
'mb': 's-resize'
|
||||
},
|
||||
addListener = fabric.util.addListener,
|
||||
removeListener = fabric.util.removeListener,
|
||||
getPointer = fabric.util.getPointer;
|
||||
|
||||
fabric.util.object.extend(fabric.Canvas.prototype, /** @scope fabric.Canvas.prototype */ {
|
||||
|
||||
/**
|
||||
* Adds mouse listeners to canvas
|
||||
* @method _initEvents
|
||||
* @private
|
||||
* See configuration documentation for more details.
|
||||
*/
|
||||
_initEvents: function () {
|
||||
var _this = this;
|
||||
|
||||
this._onMouseDown = this._onMouseDown.bind(this);
|
||||
this._onMouseMove = this._onMouseMove.bind(this);
|
||||
this._onMouseUp = this._onMouseUp.bind(this);
|
||||
this._onResize = this._onResize.bind(this);
|
||||
|
||||
addListener(fabric.window, 'resize', this._onResize);
|
||||
|
||||
if (fabric.isTouchSupported) {
|
||||
addListener(this.upperCanvasEl, 'touchstart', this._onMouseDown);
|
||||
addListener(this.upperCanvasEl, 'touchmove', this._onMouseMove);
|
||||
|
||||
if (typeof Event !== 'undefined' && 'add' in Event) {
|
||||
Event.add(this.upperCanvasEl, 'gesture', function(e, s) {
|
||||
_this.__onTransformGesture(e, s);
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
addListener(this.upperCanvasEl, 'mousedown', this._onMouseDown);
|
||||
addListener(this.upperCanvasEl, 'mousemove', this._onMouseMove);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @method _onMouseDown
|
||||
* @private
|
||||
*/
|
||||
_onMouseDown: function (e) {
|
||||
this.__onMouseDown(e);
|
||||
|
||||
!fabric.isTouchSupported && addListener(fabric.document, 'mouseup', this._onMouseUp);
|
||||
fabric.isTouchSupported && addListener(fabric.document, 'touchend', this._onMouseUp);
|
||||
|
||||
!fabric.isTouchSupported && addListener(fabric.document, 'mousemove', this._onMouseMove);
|
||||
fabric.isTouchSupported && addListener(fabric.document, 'touchmove', this._onMouseMove);
|
||||
|
||||
!fabric.isTouchSupported && removeListener(this.upperCanvasEl, 'mousemove', this._onMouseMove);
|
||||
fabric.isTouchSupported && removeListener(this.upperCanvasEl, 'touchmove', this._onMouseMove);
|
||||
},
|
||||
|
||||
/**
|
||||
* @method _onMouseUp
|
||||
* @private
|
||||
*/
|
||||
_onMouseUp: function (e) {
|
||||
this.__onMouseUp(e);
|
||||
|
||||
!fabric.isTouchSupported && removeListener(fabric.document, 'mouseup', this._onMouseUp);
|
||||
fabric.isTouchSupported && removeListener(fabric.document, 'touchend', this._onMouseUp);
|
||||
|
||||
!fabric.isTouchSupported && removeListener(fabric.document, 'mousemove', this._onMouseMove);
|
||||
fabric.isTouchSupported && removeListener(fabric.document, 'touchmove', this._onMouseMove);
|
||||
|
||||
!fabric.isTouchSupported && addListener(this.upperCanvasEl, 'mousemove', this._onMouseMove);
|
||||
fabric.isTouchSupported && addListener(this.upperCanvasEl, 'touchmove', this._onMouseMove);
|
||||
},
|
||||
|
||||
/**
|
||||
* @method _onMouseMove
|
||||
* @private
|
||||
*/
|
||||
_onMouseMove: function (e) {
|
||||
e.preventDefault && e.preventDefault();
|
||||
this.__onMouseMove(e);
|
||||
},
|
||||
|
||||
/**
|
||||
* @method _onResize
|
||||
* @private
|
||||
*/
|
||||
_onResize: function () {
|
||||
this.calcOffset();
|
||||
},
|
||||
|
||||
/**
|
||||
* Method that defines the actions when mouse is released on canvas.
|
||||
* The method resets the currentTransform parameters, store the image corner
|
||||
* position in the image object and render the canvas on top.
|
||||
* @method __onMouseUp
|
||||
* @param {Event} e Event object fired on mouseup
|
||||
*
|
||||
*/
|
||||
__onMouseUp: function (e) {
|
||||
|
||||
var target;
|
||||
|
||||
if (this.isDrawingMode && this._isCurrentlyDrawing) {
|
||||
this._isCurrentlyDrawing = false;
|
||||
this.freeDrawingBrush.onMouseUp();
|
||||
this.fire('mouse:up', { e: e });
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._currentTransform) {
|
||||
|
||||
var transform = this._currentTransform;
|
||||
|
||||
target = transform.target;
|
||||
if (target._scaling) {
|
||||
target._scaling = false;
|
||||
}
|
||||
|
||||
// determine the new coords everytime the image changes its position
|
||||
var i = this._objects.length;
|
||||
while (i--) {
|
||||
this._objects[i].setCoords();
|
||||
}
|
||||
|
||||
target.isMoving = false;
|
||||
|
||||
// only fire :modified event if target coordinates were changed during mousedown-mouseup
|
||||
if (this.stateful && target.hasStateChanged()) {
|
||||
this.fire('object:modified', { target: target });
|
||||
target.fire('modified');
|
||||
}
|
||||
|
||||
if (this._previousOriginX) {
|
||||
this._currentTransform.target.adjustPosition(this._previousOriginX);
|
||||
this._previousOriginX = null;
|
||||
}
|
||||
}
|
||||
|
||||
this._currentTransform = null;
|
||||
|
||||
if (this._groupSelector) {
|
||||
// group selection was completed, determine its bounds
|
||||
this._findSelectedObjects(e);
|
||||
}
|
||||
var activeGroup = this.getActiveGroup();
|
||||
if (activeGroup) {
|
||||
activeGroup.setObjectsCoords();
|
||||
activeGroup.set('isMoving', false);
|
||||
this._setCursor(this.defaultCursor);
|
||||
}
|
||||
|
||||
// clear selection
|
||||
this._groupSelector = null;
|
||||
this.renderAll();
|
||||
|
||||
this._setCursorFromEvent(e, target);
|
||||
|
||||
// fix for FF
|
||||
this._setCursor('');
|
||||
|
||||
var _this = this;
|
||||
setTimeout(function () {
|
||||
_this._setCursorFromEvent(e, target);
|
||||
}, 50);
|
||||
|
||||
this.fire('mouse:up', { target: target, e: e });
|
||||
target && target.fire('mouseup', { e: e });
|
||||
},
|
||||
|
||||
/**
|
||||
* Method that defines the actions when mouse is clic ked on canvas.
|
||||
* The method inits the currentTransform parameters and renders all the
|
||||
* canvas so the current image can be placed on the top canvas and the rest
|
||||
* in on the container one.
|
||||
* @method __onMouseDown
|
||||
* @param e {Event} Event object fired on mousedown
|
||||
*
|
||||
*/
|
||||
__onMouseDown: function (e) {
|
||||
|
||||
var pointer;
|
||||
|
||||
// accept only left clicks
|
||||
var isLeftClick = 'which' in e ? e.which === 1 : e.button === 1;
|
||||
if (!isLeftClick && !fabric.isTouchSupported) return;
|
||||
|
||||
if (this.isDrawingMode) {
|
||||
pointer = this.getPointer(e);
|
||||
this._isCurrentlyDrawing = true;
|
||||
this.discardActiveObject().renderAll();
|
||||
this.freeDrawingBrush.onMouseDown(pointer);
|
||||
this.fire('mouse:down', { e: e });
|
||||
return;
|
||||
}
|
||||
|
||||
// ignore if some object is being transformed at this moment
|
||||
if (this._currentTransform) return;
|
||||
|
||||
var target = this.findTarget(e), corner;
|
||||
pointer = this.getPointer(e);
|
||||
|
||||
if (this._shouldClearSelection(e)) {
|
||||
this._groupSelector = {
|
||||
ex: pointer.x,
|
||||
ey: pointer.y,
|
||||
top: 0,
|
||||
left: 0
|
||||
};
|
||||
this.deactivateAllWithDispatch();
|
||||
}
|
||||
else {
|
||||
// determine if it's a drag or rotate case
|
||||
this.stateful && target.saveState();
|
||||
|
||||
if ((corner = target._findTargetCorner(e, this._offset))) {
|
||||
this.onBeforeScaleRotate(target);
|
||||
}
|
||||
|
||||
if (this._shouldHandleGroupLogic(e, target)) {
|
||||
this._handleGroupLogic(e, target);
|
||||
target = this.getActiveGroup();
|
||||
}
|
||||
else {
|
||||
if (target !== this.getActiveGroup()) {
|
||||
this.deactivateAll();
|
||||
}
|
||||
this.setActiveObject(target, e);
|
||||
}
|
||||
|
||||
this._setupCurrentTransform(e, target);
|
||||
}
|
||||
// we must renderAll so that active image is placed on the top canvas
|
||||
this.renderAll();
|
||||
|
||||
this.fire('mouse:down', { target: target, e: e });
|
||||
target && target.fire('mousedown', { e: e });
|
||||
|
||||
// center origin when rotating
|
||||
if (corner === 'mtr') {
|
||||
this._previousOriginX = this._currentTransform.target.originX;
|
||||
this._currentTransform.target.adjustPosition('center');
|
||||
this._currentTransform.left = this._currentTransform.target.left;
|
||||
this._currentTransform.top = this._currentTransform.target.top;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Method that defines the actions when mouse is hovering the canvas.
|
||||
* The currentTransform parameter will definde whether the user is rotating/scaling/translating
|
||||
* an image or neither of them (only hovering). A group selection is also possible and would cancel
|
||||
* all any other type of action.
|
||||
* In case of an image transformation only the top canvas will be rendered.
|
||||
* @method __onMouseMove
|
||||
* @param e {Event} Event object fired on mousemove
|
||||
*
|
||||
*/
|
||||
__onMouseMove: function (e) {
|
||||
|
||||
var target, pointer;
|
||||
|
||||
if (this.isDrawingMode) {
|
||||
if (this._isCurrentlyDrawing) {
|
||||
pointer = this.getPointer(e);
|
||||
this.freeDrawingBrush.onMouseMove(pointer);
|
||||
}
|
||||
this.upperCanvasEl.style.cursor = this.freeDrawingCursor;
|
||||
this.fire('mouse:move', { e: e });
|
||||
return;
|
||||
}
|
||||
|
||||
var groupSelector = this._groupSelector;
|
||||
|
||||
// We initially clicked in an empty area, so we draw a box for multiple selection.
|
||||
if (groupSelector !== null) {
|
||||
pointer = getPointer(e, this.upperCanvasEl);
|
||||
|
||||
groupSelector.left = pointer.x - this._offset.left - groupSelector.ex;
|
||||
groupSelector.top = pointer.y - this._offset.top - groupSelector.ey;
|
||||
this.renderTop();
|
||||
}
|
||||
else if (!this._currentTransform) {
|
||||
|
||||
// alias style to elimintate unnecessary lookup
|
||||
var style = this.upperCanvasEl.style;
|
||||
|
||||
// Here we are hovering the canvas then we will determine
|
||||
// what part of the pictures we are hovering to change the caret symbol.
|
||||
// We won't do that while dragging or rotating in order to improve the
|
||||
// performance.
|
||||
target = this.findTarget(e);
|
||||
|
||||
if (!target) {
|
||||
// image/text was hovered-out from, we remove its borders
|
||||
for (var i = this._objects.length; i--; ) {
|
||||
if (this._objects[i] && !this._objects[i].active) {
|
||||
this._objects[i].setActive(false);
|
||||
}
|
||||
}
|
||||
style.cursor = this.defaultCursor;
|
||||
}
|
||||
else {
|
||||
// set proper cursor
|
||||
this._setCursorFromEvent(e, target);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// object is being transformed (scaled/rotated/moved/etc.)
|
||||
pointer = getPointer(e, this.upperCanvasEl);
|
||||
|
||||
var x = pointer.x,
|
||||
y = pointer.y;
|
||||
|
||||
this._currentTransform.target.isMoving = true;
|
||||
|
||||
var t = this._currentTransform, reset = false;
|
||||
if (
|
||||
(t.action === 'scale' || t.action === 'scaleX' || t.action === 'scaleY')
|
||||
&&
|
||||
(
|
||||
// Switch from a normal resize to center-based
|
||||
(e.altKey && (t.originX !== 'center' || t.originY !== 'center'))
|
||||
||
|
||||
// Switch from center-based resize to normal one
|
||||
(!e.altKey && t.originX === 'center' && t.originY === 'center')
|
||||
)
|
||||
) {
|
||||
this._resetCurrentTransform(e);
|
||||
reset = true;
|
||||
}
|
||||
|
||||
if (this._currentTransform.action === 'rotate') {
|
||||
this._rotateObject(x, y);
|
||||
|
||||
this.fire('object:rotating', {
|
||||
target: this._currentTransform.target,
|
||||
e: e
|
||||
});
|
||||
this._currentTransform.target.fire('rotating');
|
||||
}
|
||||
else if (this._currentTransform.action === 'scale') {
|
||||
// rotate object only if shift key is not pressed
|
||||
// and if it is not a group we are transforming
|
||||
|
||||
// TODO
|
||||
/*if (!e.shiftKey) {
|
||||
this._rotateObject(x, y);
|
||||
|
||||
this.fire('object:rotating', {
|
||||
target: this._currentTransform.target,
|
||||
e: e
|
||||
});
|
||||
this._currentTransform.target.fire('rotating');
|
||||
}*/
|
||||
|
||||
// if (!this._currentTransform.target.hasRotatingPoint) {
|
||||
// this._scaleObject(x, y);
|
||||
// this.fire('object:scaling', {
|
||||
// target: this._currentTransform.target
|
||||
// });
|
||||
// this._currentTransform.target.fire('scaling');
|
||||
// }
|
||||
|
||||
if (e.shiftKey || this.uniScaleTransform) {
|
||||
this._currentTransform.currentAction = 'scale';
|
||||
this._scaleObject(x, y);
|
||||
}
|
||||
else {
|
||||
if (!reset && t.currentAction === 'scale') {
|
||||
// Switch from a normal resize to proportional
|
||||
this._resetCurrentTransform(e);
|
||||
}
|
||||
|
||||
this._currentTransform.currentAction = 'scaleEqually';
|
||||
this._scaleObject(x, y, 'equally');
|
||||
}
|
||||
|
||||
this.fire('object:scaling', {
|
||||
target: this._currentTransform.target,
|
||||
e: e
|
||||
});
|
||||
}
|
||||
// else if (this._currentTransform.action === 'scale') {
|
||||
// this._scaleObject(x, y);
|
||||
// this.fire('object:scaling', {
|
||||
// target: this._currentTransform.target
|
||||
// });
|
||||
// this._currentTransform.target.fire('scaling');
|
||||
// }
|
||||
else if (this._currentTransform.action === 'scaleX') {
|
||||
this._scaleObject(x, y, 'x');
|
||||
|
||||
this.fire('object:scaling', {
|
||||
target: this._currentTransform.target,
|
||||
e: e
|
||||
});
|
||||
this._currentTransform.target.fire('scaling', { e: e });
|
||||
}
|
||||
else if (this._currentTransform.action === 'scaleY') {
|
||||
this._scaleObject(x, y, 'y');
|
||||
|
||||
this.fire('object:scaling', {
|
||||
target: this._currentTransform.target,
|
||||
e: e
|
||||
});
|
||||
this._currentTransform.target.fire('scaling', { e: e });
|
||||
}
|
||||
else {
|
||||
this._translateObject(x, y);
|
||||
|
||||
this.fire('object:moving', {
|
||||
target: this._currentTransform.target,
|
||||
e: e
|
||||
});
|
||||
|
||||
this._setCursor(this.moveCursor);
|
||||
|
||||
this._currentTransform.target.fire('moving', { e: e });
|
||||
}
|
||||
// only commit here. when we are actually moving the pictures
|
||||
this.renderAll();
|
||||
}
|
||||
this.fire('mouse:move', { target: target, e: e });
|
||||
target && target.fire('mousemove', { e: e });
|
||||
},
|
||||
/**
|
||||
* Sets the cursor depending on where the canvas is being hovered.
|
||||
* Note: very buggy in Opera
|
||||
* @method _setCursorFromEvent
|
||||
* @param e {Event} Event object
|
||||
* @param target {Object} Object that the mouse is hovering, if so.
|
||||
*/
|
||||
_setCursorFromEvent: function (e, target) {
|
||||
var s = this.upperCanvasEl.style;
|
||||
if (!target) {
|
||||
s.cursor = this.defaultCursor;
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
var activeGroup = this.getActiveGroup();
|
||||
// only show proper corner when group selection is not active
|
||||
var corner = target._findTargetCorner
|
||||
&& (!activeGroup || !activeGroup.contains(target))
|
||||
&& target._findTargetCorner(e, this._offset);
|
||||
|
||||
if (!corner) {
|
||||
s.cursor = this.hoverCursor;
|
||||
}
|
||||
else {
|
||||
if (corner in cursorMap) {
|
||||
s.cursor = cursorMap[corner];
|
||||
}
|
||||
else if (corner === 'mtr' && target.hasRotatingPoint) {
|
||||
s.cursor = this.rotationCursor;
|
||||
}
|
||||
else {
|
||||
s.cursor = this.defaultCursor;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
var degreesToRadians = fabric.util.degreesToRadians,
|
||||
radiansToDegrees = fabric.util.radiansToDegrees;
|
||||
|
||||
fabric.util.object.extend(fabric.Canvas.prototype, {
|
||||
fabric.util.object.extend(fabric.Canvas.prototype, /** @scope fabric.Canvas.prototype */ {
|
||||
|
||||
/**
|
||||
* Method that defines actions when an Event.js gesture is detected on an object. Currently only supports
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
fabric.util.object.extend(fabric.StaticCanvas.prototype, {
|
||||
fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @scope fabric.StaticCanvas.prototype */ {
|
||||
|
||||
/**
|
||||
* Populates canvas with data from the specified dataless JSON
|
||||
|
|
@ -13,9 +13,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
|
|||
*/
|
||||
loadFromDatalessJSON: function (json, callback) {
|
||||
|
||||
if (!json) {
|
||||
return;
|
||||
}
|
||||
if (!json) return;
|
||||
|
||||
// serialize if it wasn't already
|
||||
var serialized = (typeof json === 'string')
|
||||
|
|
@ -26,9 +24,10 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
|
|||
|
||||
this.clear();
|
||||
|
||||
// TODO: test this
|
||||
this.backgroundColor = serialized.background;
|
||||
this._enlivenDatalessObjects(serialized.objects, callback);
|
||||
var _this = this;
|
||||
this._enlivenDatalessObjects(serialized.objects, function() {
|
||||
_this._setBgOverlayImages(serialized, callback);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
@ -37,6 +36,9 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
|
|||
* @param {Function} callback
|
||||
*/
|
||||
_enlivenDatalessObjects: function (objects, callback) {
|
||||
var _this = this,
|
||||
numLoadedObjects = 0,
|
||||
numTotalObjects = objects.length;
|
||||
|
||||
/** @ignore */
|
||||
function onObjectLoaded(object, index) {
|
||||
|
|
@ -128,10 +130,6 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
|
|||
}
|
||||
}
|
||||
|
||||
var _this = this,
|
||||
numLoadedObjects = 0,
|
||||
numTotalObjects = objects.length;
|
||||
|
||||
if (numTotalObjects === 0 && callback) {
|
||||
callback();
|
||||
}
|
||||
|
|
@ -140,7 +138,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
|
|||
objects.forEach(loadObject, this);
|
||||
}
|
||||
catch(e) {
|
||||
fabric.log(e.message);
|
||||
fabric.log(e);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -163,55 +161,72 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
|
|||
? JSON.parse(json)
|
||||
: json;
|
||||
|
||||
if (!serialized || (serialized && !serialized.objects)) return;
|
||||
|
||||
this.clear();
|
||||
|
||||
var _this = this;
|
||||
this._enlivenObjects(serialized.objects, function () {
|
||||
_this.backgroundColor = serialized.background;
|
||||
var backgroundImageLoaded, overlayImageLoaded;
|
||||
|
||||
if (serialized.backgroundImage) {
|
||||
_this.setBackgroundImage(serialized.backgroundImage, function() {
|
||||
|
||||
_this.backgroundImageOpacity = serialized.backgroundImageOpacity;
|
||||
_this.backgroundImageStretch = serialized.backgroundImageStretch;
|
||||
|
||||
_this.renderAll();
|
||||
backgroundImageLoaded = true;
|
||||
|
||||
callback && overlayImageLoaded && callback();
|
||||
});
|
||||
}
|
||||
else {
|
||||
backgroundImageLoaded = true;
|
||||
}
|
||||
|
||||
if (serialized.overlayImage) {
|
||||
_this.setOverlayImage(serialized.overlayImage, function() {
|
||||
|
||||
_this.overlayImageLeft = serialized.overlayImageLeft || 0;
|
||||
_this.overlayImageTop = serialized.overlayImageTop || 0;
|
||||
|
||||
_this.renderAll();
|
||||
overlayImageLoaded = true;
|
||||
|
||||
callback && backgroundImageLoaded && callback();
|
||||
});
|
||||
}
|
||||
else {
|
||||
overlayImageLoaded = true;
|
||||
}
|
||||
|
||||
if (!serialized.backgroundImage && !serialized.overlayImage) {
|
||||
callback && callback();
|
||||
}
|
||||
_this._setBgOverlayImages(serialized, callback);
|
||||
});
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_setBgOverlayImages: function(serialized, callback) {
|
||||
|
||||
var _this = this,
|
||||
backgroundPatternLoaded,
|
||||
backgroundImageLoaded,
|
||||
overlayImageLoaded;
|
||||
|
||||
if (serialized.backgroundImage) {
|
||||
this.setBackgroundImage(serialized.backgroundImage, function() {
|
||||
|
||||
_this.backgroundImageOpacity = serialized.backgroundImageOpacity;
|
||||
_this.backgroundImageStretch = serialized.backgroundImageStretch;
|
||||
|
||||
_this.renderAll();
|
||||
|
||||
backgroundImageLoaded = true;
|
||||
|
||||
callback && overlayImageLoaded && backgroundPatternLoaded && callback();
|
||||
});
|
||||
}
|
||||
else {
|
||||
backgroundImageLoaded = true;
|
||||
}
|
||||
|
||||
if (serialized.overlayImage) {
|
||||
this.setOverlayImage(serialized.overlayImage, function() {
|
||||
|
||||
_this.overlayImageLeft = serialized.overlayImageLeft || 0;
|
||||
_this.overlayImageTop = serialized.overlayImageTop || 0;
|
||||
|
||||
_this.renderAll();
|
||||
overlayImageLoaded = true;
|
||||
|
||||
callback && backgroundImageLoaded && backgroundPatternLoaded && callback();
|
||||
});
|
||||
}
|
||||
else {
|
||||
overlayImageLoaded = true;
|
||||
}
|
||||
|
||||
if (serialized.background) {
|
||||
this.setBackgroundColor(serialized.background, function() {
|
||||
|
||||
_this.renderAll();
|
||||
backgroundPatternLoaded = true;
|
||||
|
||||
callback && overlayImageLoaded && backgroundImageLoaded && callback();
|
||||
});
|
||||
}
|
||||
else {
|
||||
backgroundPatternLoaded = true;
|
||||
}
|
||||
|
||||
if (!serialized.backgroundImage && !serialized.overlayImage && !serialized.background) {
|
||||
callback && callback();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @method _enlivenObjects
|
||||
* @param {Array} objects
|
||||
|
|
@ -81,6 +81,7 @@
|
|||
if (this.fill) {
|
||||
ctx.fill();
|
||||
}
|
||||
this._removeShadow(ctx);
|
||||
if (this.stroke) {
|
||||
ctx.stroke();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,6 +101,7 @@
|
|||
if (this.stroke) {
|
||||
ctx.stroke();
|
||||
}
|
||||
this._removeShadow(ctx);
|
||||
if (this.fill) {
|
||||
ctx.fill();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,11 +65,11 @@
|
|||
|
||||
/**
|
||||
* Returns an instance of CanvasGradient
|
||||
* @method toLiveGradient
|
||||
* @method toLive
|
||||
* @param ctx
|
||||
* @return {CanvasGradient}
|
||||
*/
|
||||
toLiveGradient: function(ctx) {
|
||||
toLive: function(ctx) {
|
||||
var gradient = ctx.createLinearGradient(
|
||||
this.x1, this.y1, this.x2 || ctx.canvas.width, this.y2);
|
||||
|
||||
|
|
|
|||
|
|
@ -102,7 +102,11 @@
|
|||
if (!noTransform) {
|
||||
this.transform(ctx);
|
||||
}
|
||||
|
||||
this._setShadow(ctx);
|
||||
this._render(ctx);
|
||||
this._removeShadow(ctx);
|
||||
|
||||
if (this.active && !noTransform) {
|
||||
this.drawBorders(ctx);
|
||||
this.hideCorners || this.drawCorners(ctx);
|
||||
|
|
@ -186,14 +190,10 @@
|
|||
|
||||
var isLikelyNode = typeof Buffer !== 'undefined' && typeof window === 'undefined',
|
||||
imgEl = this._originalImage,
|
||||
canvasEl = fabric.document.createElement('canvas'),
|
||||
canvasEl = fabric.util.createCanvasElement(),
|
||||
replacement = isLikelyNode ? new (require('canvas').Image)() : fabric.document.createElement('img'),
|
||||
_this = this;
|
||||
|
||||
if (!canvasEl.getContext && typeof G_vmlCanvasManager !== 'undefined') {
|
||||
G_vmlCanvasManager.initElement(canvasEl);
|
||||
}
|
||||
|
||||
canvasEl.width = imgEl.width;
|
||||
canvasEl.height = imgEl.height;
|
||||
|
||||
|
|
|
|||
|
|
@ -620,7 +620,8 @@ fabric.Image.filters.Convolute = fabric.util.createClass(/** @scope fabric.Image
|
|||
0, 1, 0,
|
||||
0, 0, 0 ];
|
||||
|
||||
this.tmpCtx = fabric.document.createElement('canvas').getContext('2d');
|
||||
var canvasEl = fabric.util.createCanvasElement();
|
||||
this.tmpCtx = canvasEl.getContext('2d');
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -121,6 +121,10 @@
|
|||
return this.nodeCanvas.createPNGStream();
|
||||
};
|
||||
|
||||
fabric.StaticCanvas.prototype.createJPEGStream = function(opts) {
|
||||
return this.nodeCanvas.createJPEGStream(opts);
|
||||
};
|
||||
|
||||
var origSetWidth = fabric.StaticCanvas.prototype.setWidth;
|
||||
fabric.StaticCanvas.prototype.setWidth = function(width) {
|
||||
origSetWidth.call(this);
|
||||
|
|
@ -141,4 +145,4 @@
|
|||
fabric.Canvas.prototype.setHeight = fabric.StaticCanvas.prototype.setHeight;
|
||||
}
|
||||
|
||||
})();
|
||||
})();
|
||||
|
|
|
|||
1159
src/object.class.js
1159
src/object.class.js
File diff suppressed because it is too large
Load diff
308
src/object_geometry.mixin.js
Normal file
308
src/object_geometry.mixin.js
Normal file
|
|
@ -0,0 +1,308 @@
|
|||
(function() {
|
||||
|
||||
var degreesToRadians = fabric.util.degreesToRadians;
|
||||
|
||||
fabric.util.object.extend(fabric.Object.prototype, /** @scope fabric.Object.prototype */ {
|
||||
|
||||
/**
|
||||
* Returns true if object intersects with an area formed by 2 points
|
||||
* @method intersectsWithRect
|
||||
* @param {Object} selectionTL
|
||||
* @param {Object} selectionBR
|
||||
* @return {Boolean}
|
||||
*/
|
||||
intersectsWithRect: function(selectionTL, selectionBR) {
|
||||
var oCoords = this.oCoords,
|
||||
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],
|
||||
selectionTL,
|
||||
selectionBR
|
||||
);
|
||||
return intersection.status === 'Intersection';
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if object intersects with another object
|
||||
* @method intersectsWithObject
|
||||
* @param {Object} other Object to test
|
||||
* @return {Boolean}
|
||||
*/
|
||||
intersectsWithObject: function(other) {
|
||||
// extracts coords
|
||||
function getCoords(oCoords) {
|
||||
return {
|
||||
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 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]
|
||||
);
|
||||
|
||||
return intersection.status === 'Intersection';
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if object is fully contained within area of another object
|
||||
* @method isContainedWithinObject
|
||||
* @param {Object} other Object to test
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isContainedWithinObject: function(other) {
|
||||
return this.isContainedWithinRect(other.oCoords.tl, other.oCoords.br);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if object is fully contained within area formed by 2 points
|
||||
* @method isContainedWithinRect
|
||||
* @param {Object} selectionTL
|
||||
* @param {Object} selectionBR
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isContainedWithinRect: function(selectionTL, selectionBR) {
|
||||
var oCoords = this.oCoords,
|
||||
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);
|
||||
|
||||
return tl.x > selectionTL.x
|
||||
&& tr.x < selectionBR.x
|
||||
&& tl.y > selectionTL.y
|
||||
&& bl.y < selectionBR.y;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns width of an object's bounding rectangle
|
||||
* @deprecated since 1.0.4
|
||||
* @method getBoundingRectWidth
|
||||
* @return {Number} width value
|
||||
*/
|
||||
getBoundingRectWidth: function() {
|
||||
return this.getBoundingRect().width;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns height of an object's bounding rectangle
|
||||
* @deprecated since 1.0.4
|
||||
* @method getBoundingRectHeight
|
||||
* @return {Number} height value
|
||||
*/
|
||||
getBoundingRectHeight: function() {
|
||||
return this.getBoundingRect().height;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns coordinates of object's bounding rectangle (left, top, width, height)
|
||||
* @method getBoundingRect
|
||||
* @return {Object} Object with left, top, width, height properties
|
||||
*/
|
||||
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 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);
|
||||
|
||||
return {
|
||||
left: minX,
|
||||
top: minY,
|
||||
width: width,
|
||||
height: height
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns width of an object
|
||||
* @method getWidth
|
||||
* @return {Number} width value
|
||||
*/
|
||||
getWidth: function() {
|
||||
return this.width * this.scaleX;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns height of an object
|
||||
* @method getHeight
|
||||
* @return {Number} height value
|
||||
*/
|
||||
getHeight: function() {
|
||||
return this.height * this.scaleY;
|
||||
},
|
||||
|
||||
/**
|
||||
* Makes sure the scale is valid and modifies it if necessary
|
||||
* @private
|
||||
* @method _constrainScale
|
||||
* @param {Number} value
|
||||
* @return {Number}
|
||||
*/
|
||||
_constrainScale: function(value) {
|
||||
if (Math.abs(value) < this.minScaleLimit) {
|
||||
if (value < 0)
|
||||
return -this.minScaleLimit;
|
||||
else
|
||||
return this.minScaleLimit;
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
|
||||
/**
|
||||
* Scales an object (equally by x and y)
|
||||
* @method scale
|
||||
* @param value {Number} scale factor
|
||||
* @return {fabric.Object} thisArg
|
||||
* @chainable
|
||||
*/
|
||||
scale: function(value) {
|
||||
value = this._constrainScale(value);
|
||||
|
||||
if (value < 0) {
|
||||
this.flipX = !this.flipX;
|
||||
this.flipY = !this.flipY;
|
||||
value *= -1;
|
||||
}
|
||||
|
||||
this.scaleX = value;
|
||||
this.scaleY = value;
|
||||
this.setCoords();
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Scales an object to a given width, with respect to bounding box (scaling by x/y equally)
|
||||
* @method scaleToWidth
|
||||
* @param value {Number} new width value
|
||||
* @return {fabric.Object} thisArg
|
||||
* @chainable
|
||||
*/
|
||||
scaleToWidth: function(value) {
|
||||
// adjust to bounding rect factor so that rotated shapes would fit as well
|
||||
var boundingRectFactor = this.getBoundingRectWidth() / this.getWidth();
|
||||
return this.scale(value / this.width / boundingRectFactor);
|
||||
},
|
||||
|
||||
/**
|
||||
* Scales an object to a given height, with respect to bounding box (scaling by x/y equally)
|
||||
* @method scaleToHeight
|
||||
* @param value {Number} new height value
|
||||
* @return {fabric.Object} thisArg
|
||||
* @chainable
|
||||
*/
|
||||
scaleToHeight: function(value) {
|
||||
// adjust to bounding rect factor so that rotated shapes would fit as well
|
||||
var boundingRectFactor = this.getBoundingRectHeight() / this.getHeight();
|
||||
return this.scale(value / this.height / boundingRectFactor);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets corner position coordinates based on current angle, width and height
|
||||
* @method setCoords
|
||||
* @return {fabric.Object} thisArg
|
||||
* @chainable
|
||||
*/
|
||||
setCoords: function() {
|
||||
|
||||
var strokeWidth = this.strokeWidth > 1 ? this.strokeWidth : 0,
|
||||
padding = this.padding,
|
||||
theta = degreesToRadians(this.angle);
|
||||
|
||||
this.currentWidth = (this.width + strokeWidth) * this.scaleX + padding * 2;
|
||||
this.currentHeight = (this.height + strokeWidth) * this.scaleY + padding * 2;
|
||||
|
||||
// If width is negative, make postive. Fixes path selection issue
|
||||
if (this.currentWidth < 0) {
|
||||
this.currentWidth = Math.abs(this.currentWidth);
|
||||
}
|
||||
|
||||
var _hypotenuse = Math.sqrt(
|
||||
Math.pow(this.currentWidth / 2, 2) +
|
||||
Math.pow(this.currentHeight / 2, 2));
|
||||
|
||||
var _angle = Math.atan(this.currentHeight / this.currentWidth);
|
||||
|
||||
// offset added for rotate and scale actions
|
||||
var offsetX = Math.cos(_angle + theta) * _hypotenuse,
|
||||
offsetY = Math.sin(_angle + theta) * _hypotenuse,
|
||||
sinTh = Math.sin(theta),
|
||||
cosTh = Math.cos(theta);
|
||||
|
||||
var coords = this.getCenterPoint();
|
||||
var tl = {
|
||||
x: coords.x - offsetX,
|
||||
y: coords.y - offsetY
|
||||
};
|
||||
var tr = {
|
||||
x: tl.x + (this.currentWidth * cosTh),
|
||||
y: tl.y + (this.currentWidth * sinTh)
|
||||
};
|
||||
var br = {
|
||||
x: tr.x - (this.currentHeight * sinTh),
|
||||
y: tr.y + (this.currentHeight * cosTh)
|
||||
};
|
||||
var bl = {
|
||||
x: tl.x - (this.currentHeight * sinTh),
|
||||
y: tl.y + (this.currentHeight * cosTh)
|
||||
};
|
||||
var ml = {
|
||||
x: tl.x - (this.currentHeight/2 * sinTh),
|
||||
y: tl.y + (this.currentHeight/2 * cosTh)
|
||||
};
|
||||
var mt = {
|
||||
x: tl.x + (this.currentWidth/2 * cosTh),
|
||||
y: tl.y + (this.currentWidth/2 * sinTh)
|
||||
};
|
||||
var mr = {
|
||||
x: tr.x - (this.currentHeight/2 * sinTh),
|
||||
y: tr.y + (this.currentHeight/2 * cosTh)
|
||||
};
|
||||
var mb = {
|
||||
x: bl.x + (this.currentWidth/2 * cosTh),
|
||||
y: bl.y + (this.currentWidth/2 * sinTh)
|
||||
};
|
||||
var mtr = {
|
||||
x: tl.x + (this.currentWidth/2 * cosTh),
|
||||
y: tl.y + (this.currentWidth/2 * sinTh)
|
||||
};
|
||||
|
||||
// debugging
|
||||
|
||||
// setTimeout(function() {
|
||||
// canvas.contextTop.fillStyle = 'green';
|
||||
// canvas.contextTop.fillRect(mb.x, mb.y, 3, 3);
|
||||
// canvas.contextTop.fillRect(bl.x, bl.y, 3, 3);
|
||||
// canvas.contextTop.fillRect(br.x, br.y, 3, 3);
|
||||
// canvas.contextTop.fillRect(tl.x, tl.y, 3, 3);
|
||||
// canvas.contextTop.fillRect(tr.x, tr.y, 3, 3);
|
||||
// canvas.contextTop.fillRect(ml.x, ml.y, 3, 3);
|
||||
// canvas.contextTop.fillRect(mr.x, mr.y, 3, 3);
|
||||
// canvas.contextTop.fillRect(mt.x, mt.y, 3, 3);
|
||||
// }, 50);
|
||||
|
||||
// clockwise
|
||||
this.oCoords = { tl: tl, tr: tr, br: br, bl: bl, ml: ml, mt: mt, mr: mr, mb: mb, mtr: mtr };
|
||||
|
||||
// set coordinates of the draggable boxes in the corners used to scale/rotate the image
|
||||
this._setCornerCoords();
|
||||
|
||||
return this;
|
||||
}
|
||||
});
|
||||
})();
|
||||
496
src/object_interactivity.mixin.js
Normal file
496
src/object_interactivity.mixin.js
Normal file
|
|
@ -0,0 +1,496 @@
|
|||
(function(){
|
||||
|
||||
var getPointer = fabric.util.getPointer,
|
||||
degreesToRadians = fabric.util.degreesToRadians;
|
||||
|
||||
fabric.util.object.extend(fabric.Object.prototype, /** @scope fabric.Object.prototype */ {
|
||||
|
||||
/**
|
||||
* Determines which one of the four corners has been clicked
|
||||
* @method _findTargetCorner
|
||||
* @private
|
||||
* @param e {Event} event object
|
||||
* @param offset {Object} canvas offset
|
||||
* @return {String|Boolean} corner code (tl, tr, bl, br, etc.), or false if nothing is found
|
||||
*/
|
||||
_findTargetCorner: function(e, offset) {
|
||||
if (!this.hasControls || !this.active) return false;
|
||||
|
||||
var pointer = getPointer(e, this.canvas.upperCanvasEl),
|
||||
ex = pointer.x - offset.left,
|
||||
ey = pointer.y - offset.top,
|
||||
xpoints,
|
||||
lines;
|
||||
|
||||
for (var i in this.oCoords) {
|
||||
|
||||
if (i === 'mtr' && !this.hasRotatingPoint) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.get('lockUniScaling') && (i === 'mt' || i === 'mr' || i === 'mb' || i === 'ml')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
lines = this._getImageLines(this.oCoords[i].corner, i);
|
||||
|
||||
// debugging
|
||||
|
||||
// canvas.contextTop.fillRect(lines.bottomline.d.x, lines.bottomline.d.y, 2, 2);
|
||||
// canvas.contextTop.fillRect(lines.bottomline.o.x, lines.bottomline.o.y, 2, 2);
|
||||
|
||||
// canvas.contextTop.fillRect(lines.leftline.d.x, lines.leftline.d.y, 2, 2);
|
||||
// canvas.contextTop.fillRect(lines.leftline.o.x, lines.leftline.o.y, 2, 2);
|
||||
|
||||
// canvas.contextTop.fillRect(lines.topline.d.x, lines.topline.d.y, 2, 2);
|
||||
// canvas.contextTop.fillRect(lines.topline.o.x, lines.topline.o.y, 2, 2);
|
||||
|
||||
// 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(ex, ey, lines);
|
||||
if (xpoints % 2 === 1 && xpoints !== 0) {
|
||||
this.__corner = i;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper method to determine how many cross points are between the 4 image edges
|
||||
* and the horizontal line determined by the position of our mouse when clicked on canvas
|
||||
* @method _findCrossPoints
|
||||
* @private
|
||||
* @param ex {Number} x coordinate of the mouse
|
||||
* @param ey {Number} y coordinate of the mouse
|
||||
* @param oCoords {Object} Coordinates of the image being evaluated
|
||||
*/
|
||||
_findCrossPoints: function(ex, ey, oCoords) {
|
||||
var b1, b2, a1, a2, xi, yi,
|
||||
xcount = 0,
|
||||
iLine;
|
||||
|
||||
for (var lineKey in oCoords) {
|
||||
iLine = oCoords[lineKey];
|
||||
// optimisation 1: line below dot. no cross
|
||||
if ((iLine.o.y < ey) && (iLine.d.y < ey)) {
|
||||
continue;
|
||||
}
|
||||
// optimisation 2: line above dot. no cross
|
||||
if ((iLine.o.y >= ey) && (iLine.d.y >= ey)) {
|
||||
continue;
|
||||
}
|
||||
// optimisation 3: vertical line case
|
||||
if ((iLine.o.x === iLine.d.x) && (iLine.o.x >= ex)) {
|
||||
xi = iLine.o.x;
|
||||
yi = ey;
|
||||
}
|
||||
// calculate the intersection point
|
||||
else {
|
||||
b1 = 0;
|
||||
b2 = (iLine.d.y-iLine.o.y)/(iLine.d.x-iLine.o.x);
|
||||
a1 = ey-b1*ex;
|
||||
a2 = iLine.o.y-b2*iLine.o.x;
|
||||
|
||||
xi = - (a1-a2)/(b1-b2);
|
||||
yi = a1+b1*xi;
|
||||
}
|
||||
// dont count xi < ex cases
|
||||
if (xi >= ex) {
|
||||
xcount += 1;
|
||||
}
|
||||
// optimisation 4: specific for square images
|
||||
if (xcount === 2) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return xcount;
|
||||
},
|
||||
|
||||
/**
|
||||
* Method that returns an object with the image lines in it given the coordinates of the corners
|
||||
* @method _getImageLines
|
||||
* @private
|
||||
* @param oCoords {Object} coordinates of the image corners
|
||||
*/
|
||||
_getImageLines: function(oCoords) {
|
||||
return {
|
||||
topline: {
|
||||
o: oCoords.tl,
|
||||
d: oCoords.tr
|
||||
},
|
||||
rightline: {
|
||||
o: oCoords.tr,
|
||||
d: oCoords.br
|
||||
},
|
||||
bottomline: {
|
||||
o: oCoords.br,
|
||||
d: oCoords.bl
|
||||
},
|
||||
leftline: {
|
||||
o: oCoords.bl,
|
||||
d: oCoords.tl
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the coordinates of the draggable boxes in the corners of
|
||||
* the image used to scale/rotate it.
|
||||
* @method _setCornerCoords
|
||||
* @private
|
||||
*/
|
||||
_setCornerCoords: function() {
|
||||
var coords = this.oCoords,
|
||||
theta = degreesToRadians(this.angle),
|
||||
newTheta = degreesToRadians(45 - this.angle),
|
||||
cornerHypotenuse = Math.sqrt(2 * Math.pow(this.cornerSize, 2)) / 2,
|
||||
cosHalfOffset = cornerHypotenuse * Math.cos(newTheta),
|
||||
sinHalfOffset = cornerHypotenuse * Math.sin(newTheta),
|
||||
sinTh = Math.sin(theta),
|
||||
cosTh = Math.cos(theta);
|
||||
|
||||
coords.tl.corner = {
|
||||
tl: {
|
||||
x: coords.tl.x - sinHalfOffset,
|
||||
y: coords.tl.y - cosHalfOffset
|
||||
},
|
||||
tr: {
|
||||
x: coords.tl.x + cosHalfOffset,
|
||||
y: coords.tl.y - sinHalfOffset
|
||||
},
|
||||
bl: {
|
||||
x: coords.tl.x - cosHalfOffset,
|
||||
y: coords.tl.y + sinHalfOffset
|
||||
},
|
||||
br: {
|
||||
x: coords.tl.x + sinHalfOffset,
|
||||
y: coords.tl.y + cosHalfOffset
|
||||
}
|
||||
};
|
||||
|
||||
coords.tr.corner = {
|
||||
tl: {
|
||||
x: coords.tr.x - sinHalfOffset,
|
||||
y: coords.tr.y - cosHalfOffset
|
||||
},
|
||||
tr: {
|
||||
x: coords.tr.x + cosHalfOffset,
|
||||
y: coords.tr.y - sinHalfOffset
|
||||
},
|
||||
br: {
|
||||
x: coords.tr.x + sinHalfOffset,
|
||||
y: coords.tr.y + cosHalfOffset
|
||||
},
|
||||
bl: {
|
||||
x: coords.tr.x - cosHalfOffset,
|
||||
y: coords.tr.y + sinHalfOffset
|
||||
}
|
||||
};
|
||||
|
||||
coords.bl.corner = {
|
||||
tl: {
|
||||
x: coords.bl.x - sinHalfOffset,
|
||||
y: coords.bl.y - cosHalfOffset
|
||||
},
|
||||
bl: {
|
||||
x: coords.bl.x - cosHalfOffset,
|
||||
y: coords.bl.y + sinHalfOffset
|
||||
},
|
||||
br: {
|
||||
x: coords.bl.x + sinHalfOffset,
|
||||
y: coords.bl.y + cosHalfOffset
|
||||
},
|
||||
tr: {
|
||||
x: coords.bl.x + cosHalfOffset,
|
||||
y: coords.bl.y - sinHalfOffset
|
||||
}
|
||||
};
|
||||
|
||||
coords.br.corner = {
|
||||
tr: {
|
||||
x: coords.br.x + cosHalfOffset,
|
||||
y: coords.br.y - sinHalfOffset
|
||||
},
|
||||
bl: {
|
||||
x: coords.br.x - cosHalfOffset,
|
||||
y: coords.br.y + sinHalfOffset
|
||||
},
|
||||
br: {
|
||||
x: coords.br.x + sinHalfOffset,
|
||||
y: coords.br.y + cosHalfOffset
|
||||
},
|
||||
tl: {
|
||||
x: coords.br.x - sinHalfOffset,
|
||||
y: coords.br.y - cosHalfOffset
|
||||
}
|
||||
};
|
||||
|
||||
coords.ml.corner = {
|
||||
tl: {
|
||||
x: coords.ml.x - sinHalfOffset,
|
||||
y: coords.ml.y - cosHalfOffset
|
||||
},
|
||||
tr: {
|
||||
x: coords.ml.x + cosHalfOffset,
|
||||
y: coords.ml.y - sinHalfOffset
|
||||
},
|
||||
bl: {
|
||||
x: coords.ml.x - cosHalfOffset,
|
||||
y: coords.ml.y + sinHalfOffset
|
||||
},
|
||||
br: {
|
||||
x: coords.ml.x + sinHalfOffset,
|
||||
y: coords.ml.y + cosHalfOffset
|
||||
}
|
||||
};
|
||||
|
||||
coords.mt.corner = {
|
||||
tl: {
|
||||
x: coords.mt.x - sinHalfOffset,
|
||||
y: coords.mt.y - cosHalfOffset
|
||||
},
|
||||
tr: {
|
||||
x: coords.mt.x + cosHalfOffset,
|
||||
y: coords.mt.y - sinHalfOffset
|
||||
},
|
||||
bl: {
|
||||
x: coords.mt.x - cosHalfOffset,
|
||||
y: coords.mt.y + sinHalfOffset
|
||||
},
|
||||
br: {
|
||||
x: coords.mt.x + sinHalfOffset,
|
||||
y: coords.mt.y + cosHalfOffset
|
||||
}
|
||||
};
|
||||
|
||||
coords.mr.corner = {
|
||||
tl: {
|
||||
x: coords.mr.x - sinHalfOffset,
|
||||
y: coords.mr.y - cosHalfOffset
|
||||
},
|
||||
tr: {
|
||||
x: coords.mr.x + cosHalfOffset,
|
||||
y: coords.mr.y - sinHalfOffset
|
||||
},
|
||||
bl: {
|
||||
x: coords.mr.x - cosHalfOffset,
|
||||
y: coords.mr.y + sinHalfOffset
|
||||
},
|
||||
br: {
|
||||
x: coords.mr.x + sinHalfOffset,
|
||||
y: coords.mr.y + cosHalfOffset
|
||||
}
|
||||
};
|
||||
|
||||
coords.mb.corner = {
|
||||
tl: {
|
||||
x: coords.mb.x - sinHalfOffset,
|
||||
y: coords.mb.y - cosHalfOffset
|
||||
},
|
||||
tr: {
|
||||
x: coords.mb.x + cosHalfOffset,
|
||||
y: coords.mb.y - sinHalfOffset
|
||||
},
|
||||
bl: {
|
||||
x: coords.mb.x - cosHalfOffset,
|
||||
y: coords.mb.y + sinHalfOffset
|
||||
},
|
||||
br: {
|
||||
x: coords.mb.x + sinHalfOffset,
|
||||
y: coords.mb.y + cosHalfOffset
|
||||
}
|
||||
};
|
||||
|
||||
coords.mtr.corner = {
|
||||
tl: {
|
||||
x: coords.mtr.x - sinHalfOffset + (sinTh * this.rotatingPointOffset),
|
||||
y: coords.mtr.y - cosHalfOffset - (cosTh * this.rotatingPointOffset)
|
||||
},
|
||||
tr: {
|
||||
x: coords.mtr.x + cosHalfOffset + (sinTh * this.rotatingPointOffset),
|
||||
y: coords.mtr.y - sinHalfOffset - (cosTh * this.rotatingPointOffset)
|
||||
},
|
||||
bl: {
|
||||
x: coords.mtr.x - cosHalfOffset + (sinTh * this.rotatingPointOffset),
|
||||
y: coords.mtr.y + sinHalfOffset - (cosTh * this.rotatingPointOffset)
|
||||
},
|
||||
br: {
|
||||
x: coords.mtr.x + sinHalfOffset + (sinTh * this.rotatingPointOffset),
|
||||
y: coords.mtr.y + cosHalfOffset - (cosTh * this.rotatingPointOffset)
|
||||
}
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Draws borders of an object's bounding box.
|
||||
* Requires public properties: width, height
|
||||
* Requires public options: padding, borderColor
|
||||
* @method drawBorders
|
||||
* @param {CanvasRenderingContext2D} ctx Context to draw on
|
||||
* @return {fabric.Object} thisArg
|
||||
* @chainable
|
||||
*/
|
||||
drawBorders: function(ctx) {
|
||||
if (!this.hasBorders) return;
|
||||
|
||||
var padding = this.padding,
|
||||
padding2 = padding * 2,
|
||||
strokeWidth = this.strokeWidth > 1 ? this.strokeWidth : 0;
|
||||
|
||||
ctx.save();
|
||||
|
||||
ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
|
||||
ctx.strokeStyle = this.borderColor;
|
||||
|
||||
var scaleX = 1 / this._constrainScale(this.scaleX),
|
||||
scaleY = 1 / this._constrainScale(this.scaleY);
|
||||
|
||||
ctx.lineWidth = 1 / this.borderScaleFactor;
|
||||
|
||||
ctx.scale(scaleX, scaleY);
|
||||
|
||||
var w = this.getWidth(),
|
||||
h = this.getHeight();
|
||||
|
||||
ctx.strokeRect(
|
||||
~~(-(w / 2) - padding - strokeWidth / 2 * this.scaleX) + 0.5, // offset needed to make lines look sharper
|
||||
~~(-(h / 2) - padding - strokeWidth / 2 * this.scaleY) + 0.5,
|
||||
~~(w + padding2 + strokeWidth * this.scaleX),
|
||||
~~(h + padding2 + strokeWidth * this.scaleY)
|
||||
);
|
||||
|
||||
if (this.hasRotatingPoint && !this.get('lockRotation') && this.hasControls) {
|
||||
|
||||
var rotateHeight = (
|
||||
this.flipY
|
||||
? h + (strokeWidth * this.scaleY) + (padding * 2)
|
||||
: -h - (strokeWidth * this.scaleY) - (padding * 2)
|
||||
) / 2;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, rotateHeight);
|
||||
ctx.lineTo(0, rotateHeight + (this.flipY ? this.rotatingPointOffset : -this.rotatingPointOffset));
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Draws corners of an object's bounding box.
|
||||
* Requires public properties: width, height, scaleX, scaleY
|
||||
* Requires public options: cornerSize, padding
|
||||
* @method drawCorners
|
||||
* @param {CanvasRenderingContext2D} ctx Context to draw on
|
||||
* @return {fabric.Object} thisArg
|
||||
* @chainable
|
||||
*/
|
||||
drawCorners: function(ctx) {
|
||||
if (!this.hasControls) return;
|
||||
|
||||
var size = this.cornerSize,
|
||||
size2 = size / 2,
|
||||
strokeWidth2 = this.strokeWidth / 2,
|
||||
left = -(this.width / 2),
|
||||
top = -(this.height / 2),
|
||||
_left,
|
||||
_top,
|
||||
sizeX = size / this.scaleX,
|
||||
sizeY = size / this.scaleY,
|
||||
paddingX = this.padding / this.scaleX,
|
||||
paddingY = this.padding / this.scaleY,
|
||||
scaleOffsetY = size2 / this.scaleY,
|
||||
scaleOffsetX = size2 / this.scaleX,
|
||||
scaleOffsetSizeX = (size2 - size) / this.scaleX,
|
||||
scaleOffsetSizeY = (size2 - size) / this.scaleY,
|
||||
height = this.height,
|
||||
width = this.width,
|
||||
methodName = this.transparentCorners ? 'strokeRect' : 'fillRect',
|
||||
isVML = typeof G_vmlCanvasManager !== 'undefined';
|
||||
|
||||
ctx.save();
|
||||
|
||||
ctx.lineWidth = 1 / Math.max(this.scaleX, this.scaleY);
|
||||
|
||||
ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
|
||||
ctx.strokeStyle = ctx.fillStyle = this.cornerColor;
|
||||
|
||||
// top-left
|
||||
_left = left - scaleOffsetX - strokeWidth2 - paddingX;
|
||||
_top = top - scaleOffsetY - strokeWidth2 - paddingY;
|
||||
|
||||
isVML || ctx.clearRect(_left, _top, sizeX, sizeY);
|
||||
ctx[methodName](_left, _top, sizeX, sizeY);
|
||||
|
||||
// top-right
|
||||
_left = left + width - scaleOffsetX + strokeWidth2 + paddingX;
|
||||
_top = top - scaleOffsetY - strokeWidth2 - paddingY;
|
||||
|
||||
isVML || ctx.clearRect(_left, _top, sizeX, sizeY);
|
||||
ctx[methodName](_left, _top, sizeX, sizeY);
|
||||
|
||||
// bottom-left
|
||||
_left = left - scaleOffsetX - strokeWidth2 - paddingX;
|
||||
_top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY;
|
||||
|
||||
isVML || ctx.clearRect(_left, _top, sizeX, sizeY);
|
||||
ctx[methodName](_left, _top, sizeX, sizeY);
|
||||
|
||||
// bottom-right
|
||||
_left = left + width + scaleOffsetSizeX + strokeWidth2 + paddingX;
|
||||
_top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY;
|
||||
|
||||
isVML || ctx.clearRect(_left, _top, sizeX, sizeY);
|
||||
ctx[methodName](_left, _top, sizeX, sizeY);
|
||||
|
||||
if (!this.get('lockUniScaling')) {
|
||||
// middle-top
|
||||
_left = left + width/2 - scaleOffsetX;
|
||||
_top = top - scaleOffsetY - strokeWidth2 - paddingY;
|
||||
|
||||
isVML || ctx.clearRect(_left, _top, sizeX, sizeY);
|
||||
ctx[methodName](_left, _top, sizeX, sizeY);
|
||||
|
||||
// middle-bottom
|
||||
_left = left + width/2 - scaleOffsetX;
|
||||
_top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY;
|
||||
|
||||
isVML || ctx.clearRect(_left, _top, sizeX, sizeY);
|
||||
ctx[methodName](_left, _top, sizeX, sizeY);
|
||||
|
||||
// middle-right
|
||||
_left = left + width + scaleOffsetSizeX + strokeWidth2 + paddingX;
|
||||
_top = top + height/2 - scaleOffsetY;
|
||||
|
||||
isVML || ctx.clearRect(_left, _top, sizeX, sizeY);
|
||||
ctx[methodName](_left, _top, sizeX, sizeY);
|
||||
|
||||
// middle-left
|
||||
_left = left - scaleOffsetX - strokeWidth2 - paddingX;
|
||||
_top = top + height/2 - scaleOffsetY;
|
||||
|
||||
isVML || ctx.clearRect(_left, _top, sizeX, sizeY);
|
||||
ctx[methodName](_left, _top, sizeX, sizeY);
|
||||
}
|
||||
|
||||
// middle-top-rotate
|
||||
if (this.hasRotatingPoint) {
|
||||
|
||||
_left = left + width/2 - scaleOffsetX;
|
||||
_top = this.flipY ?
|
||||
(top + height + (this.rotatingPointOffset / this.scaleY) - sizeY/2 + strokeWidth2 + paddingY)
|
||||
: (top - (this.rotatingPointOffset / this.scaleY) - sizeY/2 - strokeWidth2 - paddingY);
|
||||
|
||||
isVML || ctx.clearRect(_left, _top, sizeX, sizeY);
|
||||
ctx[methodName](_left, _top, sizeX, sizeY);
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
|
||||
return this;
|
||||
}
|
||||
});
|
||||
})();
|
||||
207
src/object_origin.mixin.js
Normal file
207
src/object_origin.mixin.js
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
(function() {
|
||||
|
||||
var degreesToRadians = fabric.util.degreesToRadians;
|
||||
|
||||
fabric.util.object.extend(fabric.Object.prototype, /** @scope fabric.Object.prototype */ {
|
||||
|
||||
/**
|
||||
* Translates the coordinates from origin to center coordinates (based on the object's dimensions)
|
||||
* @method translateToCenterPoint
|
||||
* @param {fabric.Point} point The point which corresponds to the originX and originY params
|
||||
* @param {string} enum('left', 'center', 'right') Horizontal origin
|
||||
* @param {string} enum('top', 'center', 'bottom') Vertical origin
|
||||
* @return {fabric.Point}
|
||||
*/
|
||||
translateToCenterPoint: function(point, originX, originY) {
|
||||
var cx = point.x, cy = point.y;
|
||||
|
||||
if ( originX === "left" ) {
|
||||
cx = point.x + this.getWidth() / 2;
|
||||
}
|
||||
else if ( originX === "right" ) {
|
||||
cx = point.x - this.getWidth() / 2;
|
||||
}
|
||||
|
||||
if ( originY === "top" ) {
|
||||
cy = point.y + this.getHeight() / 2;
|
||||
}
|
||||
else if ( originY === "bottom" ) {
|
||||
cy = point.y - this.getHeight() / 2;
|
||||
}
|
||||
|
||||
// Apply the reverse rotation to the point (it's already scaled properly)
|
||||
return fabric.util.rotatePoint(new fabric.Point(cx, cy), point, degreesToRadians(this.angle));
|
||||
},
|
||||
|
||||
/**
|
||||
* Translates the coordinates from center to origin coordinates (based on the object's dimensions)
|
||||
* @method translateToOriginPoint
|
||||
* @param {fabric.Point} point The point which corresponds to center of the object
|
||||
* @param {string} enum('left', 'center', 'right') Horizontal origin
|
||||
* @param {string} enum('top', 'center', 'bottom') Vertical origin
|
||||
* @return {fabric.Point}
|
||||
*/
|
||||
translateToOriginPoint: function(center, originX, originY) {
|
||||
var x = center.x, y = center.y;
|
||||
|
||||
// Get the point coordinates
|
||||
if ( originX === "left" ) {
|
||||
x = center.x - this.getWidth() / 2;
|
||||
}
|
||||
else if ( originX === "right" ) {
|
||||
x = center.x + this.getWidth() / 2;
|
||||
}
|
||||
if ( originY === "top" ) {
|
||||
y = center.y - this.getHeight() / 2;
|
||||
}
|
||||
else if ( originY === "bottom" ) {
|
||||
y = center.y + this.getHeight() / 2;
|
||||
}
|
||||
|
||||
// Apply the rotation to the point (it's already scaled properly)
|
||||
return fabric.util.rotatePoint(new fabric.Point(x, y), center, degreesToRadians(this.angle));
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the real center coordinates of the object
|
||||
* @method getCenterPoint
|
||||
* @return {fabric.Point}
|
||||
*/
|
||||
getCenterPoint: function() {
|
||||
return this.translateToCenterPoint(
|
||||
new fabric.Point(this.left, this.top), this.originX, this.originY);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the coordinates of the object based on center coordinates
|
||||
* @method getOriginPoint
|
||||
* @param {fabric.Point} point The point which corresponds to the originX and originY params
|
||||
* @return {fabric.Point}
|
||||
*/
|
||||
// getOriginPoint: function(center) {
|
||||
// return this.translateToOriginPoint(center, this.originX, this.originY);
|
||||
// },
|
||||
|
||||
/**
|
||||
* Returns the coordinates of the object as if it has a different origin
|
||||
* @method getPointByOrigin
|
||||
* @param {string} enum('left', 'center', 'right') Horizontal origin
|
||||
* @param {string} enum('top', 'center', 'bottom') Vertical origin
|
||||
* @return {fabric.Point}
|
||||
*/
|
||||
// getPointByOrigin: function(originX, originY) {
|
||||
// var center = this.getCenterPoint();
|
||||
|
||||
// return this.translateToOriginPoint(center, originX, originY);
|
||||
// },
|
||||
|
||||
/**
|
||||
* Returns the point in local coordinates
|
||||
* @method toLocalPoint
|
||||
* @param {fabric.Point} The point relative to the global coordinate system
|
||||
* @return {fabric.Point}
|
||||
*/
|
||||
toLocalPoint: function(point, originX, originY) {
|
||||
var center = this.getCenterPoint();
|
||||
|
||||
var x, y;
|
||||
if (originX !== undefined && originY !== undefined) {
|
||||
if ( originX === "left" ) {
|
||||
x = center.x - this.getWidth() / 2;
|
||||
}
|
||||
else if ( originX === "right" ) {
|
||||
x = center.x + this.getWidth() / 2;
|
||||
}
|
||||
else {
|
||||
x = center.x;
|
||||
}
|
||||
|
||||
if ( originY === "top" ) {
|
||||
y = center.y - this.getHeight() / 2;
|
||||
}
|
||||
else if ( originY === "bottom" ) {
|
||||
y = center.y + this.getHeight() / 2;
|
||||
}
|
||||
else {
|
||||
y = center.y;
|
||||
}
|
||||
}
|
||||
else {
|
||||
x = this.left;
|
||||
y = this.top;
|
||||
}
|
||||
|
||||
return fabric.util.rotatePoint(new fabric.Point(point.x, point.y), center, -degreesToRadians(this.angle)).subtractEquals(new fabric.Point(x, y));
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the point in global coordinates
|
||||
* @method toGlobalPoint
|
||||
* @param {fabric.Point} The point relative to the local coordinate system
|
||||
* @return {fabric.Point}
|
||||
*/
|
||||
// toGlobalPoint: function(point) {
|
||||
// return fabric.util.rotatePoint(point, this.getCenterPoint(), degreesToRadians(this.angle)).addEquals(new fabric.Point(this.left, this.top));
|
||||
// },
|
||||
|
||||
/**
|
||||
* Sets the position of the object taking into consideration the object's origin
|
||||
* @method setPositionByOrigin
|
||||
* @param {fabric.Point} point The new position of the object
|
||||
* @param {string} enum('left', 'center', 'right') Horizontal origin
|
||||
* @param {string} enum('top', 'center', 'bottom') Vertical origin
|
||||
* @return {void}
|
||||
*/
|
||||
setPositionByOrigin: function(pos, originX, originY) {
|
||||
var center = this.translateToCenterPoint(pos, originX, originY);
|
||||
var position = this.translateToOriginPoint(center, this.originX, this.originY);
|
||||
|
||||
this.set('left', position.x);
|
||||
this.set('top', position.y);
|
||||
},
|
||||
|
||||
/**
|
||||
* @method adjustPosition
|
||||
* @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;
|
||||
|
||||
if (this.originX === 'center' && to === 'left' ||
|
||||
this.originX === 'right' && to === 'center') {
|
||||
// move half left
|
||||
this.left -= xHalf;
|
||||
this.top -= yHalf;
|
||||
}
|
||||
else if (this.originX === 'left' && to === 'center' ||
|
||||
this.originX === 'center' && to === 'right') {
|
||||
// move half right
|
||||
this.left += xHalf;
|
||||
this.top += yHalf;
|
||||
}
|
||||
else if (this.originX === 'left' && to === 'right') {
|
||||
// move full right
|
||||
this.left += xFull;
|
||||
this.top += yFull;
|
||||
}
|
||||
else if (this.originX === 'right' && to === 'left') {
|
||||
// move full left
|
||||
this.left -= xFull;
|
||||
this.top -= yFull;
|
||||
}
|
||||
|
||||
this.setCoords();
|
||||
this.originX = to;
|
||||
}
|
||||
});
|
||||
|
||||
})();
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
fabric.util.object.extend(fabric.Object.prototype, {
|
||||
fabric.util.object.extend(fabric.Object.prototype, /** @scope fabric.Object.prototype */ {
|
||||
|
||||
/**
|
||||
* @private
|
||||
|
|
@ -365,8 +365,8 @@
|
|||
checkIfDone();
|
||||
}
|
||||
}
|
||||
catch(e) {
|
||||
fabric.log(e.message || e);
|
||||
catch(err) {
|
||||
fabric.log(err);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
|
|
|||
|
|
@ -543,21 +543,26 @@
|
|||
ctx.fillStyle = this.overlayFill;
|
||||
}
|
||||
else if (this.fill) {
|
||||
ctx.fillStyle = this.fill.toLiveGradient
|
||||
? this.fill.toLiveGradient(ctx)
|
||||
ctx.fillStyle = this.fill.toLive
|
||||
? this.fill.toLive(ctx)
|
||||
: this.fill;
|
||||
}
|
||||
|
||||
if (this.stroke) {
|
||||
ctx.strokeStyle = this.stroke;
|
||||
ctx.strokeStyle = this.stroke.toLive
|
||||
? this.stroke.toLive(ctx)
|
||||
: this.stroke;
|
||||
}
|
||||
ctx.beginPath();
|
||||
|
||||
this._setShadow(ctx);
|
||||
this._render(ctx);
|
||||
|
||||
if (this.fill) {
|
||||
ctx.fill();
|
||||
}
|
||||
this._removeShadow(ctx);
|
||||
|
||||
if (this.stroke) {
|
||||
ctx.strokeStyle = this.stroke;
|
||||
ctx.lineWidth = this.strokeWidth;
|
||||
|
|
|
|||
|
|
@ -73,9 +73,13 @@
|
|||
}
|
||||
|
||||
this.transform(ctx);
|
||||
|
||||
this._setShadow(ctx);
|
||||
for (var i = 0, l = this.paths.length; i < l; ++i) {
|
||||
this.paths[i].render(ctx, true);
|
||||
}
|
||||
this._removeShadow(ctx);
|
||||
|
||||
if (this.active) {
|
||||
this.drawBorders(ctx);
|
||||
this.hideCorners || this.drawCorners(ctx);
|
||||
|
|
|
|||
69
src/pattern.class.js
Normal file
69
src/pattern.class.js
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
/**
|
||||
* Pattern class
|
||||
* @class Pattern
|
||||
* @memberOf fabric
|
||||
*/
|
||||
fabric.Pattern = fabric.util.createClass(/** @scope fabric.Pattern.prototype */ {
|
||||
|
||||
/**
|
||||
* Repeat property of a pattern (one of repeat, repeat-x, repeat-y)
|
||||
* @property
|
||||
* @type String
|
||||
*/
|
||||
repeat: 'repeat',
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @method initialize
|
||||
* @param {Object} [options]
|
||||
* @return {fabric.Pattern} thisArg
|
||||
*/
|
||||
initialize: function(options) {
|
||||
options || (options = { });
|
||||
|
||||
if (options.source) {
|
||||
this.source = typeof options.source === 'string'
|
||||
? new Function(options.source)
|
||||
: options.source;
|
||||
}
|
||||
if (options.repeat) {
|
||||
this.repeat = options.repeat;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns object representation of a pattern
|
||||
* @method toObject
|
||||
* @return {Object}
|
||||
*/
|
||||
toObject: function() {
|
||||
|
||||
var source;
|
||||
|
||||
// callback
|
||||
if (typeof this.source === 'function') {
|
||||
source = String(this.source)
|
||||
.match(/function\s+\w*\s*\(.*\)\s+\{([\s\S]*)\}/)[1];
|
||||
}
|
||||
// <img> element
|
||||
else if (typeof this.source.src === 'string') {
|
||||
source = this.source.src;
|
||||
}
|
||||
|
||||
return {
|
||||
source: source,
|
||||
repeat: this.repeat
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns an instance of CanvasPattern
|
||||
* @method toLive
|
||||
* @param ctx
|
||||
* @return {CanvasPattern}
|
||||
*/
|
||||
toLive: function(ctx) {
|
||||
var source = typeof this.source === 'function' ? this.source() : this.source;
|
||||
return ctx.createPattern(source, this.repeat);
|
||||
}
|
||||
});
|
||||
|
|
@ -208,6 +208,20 @@
|
|||
return path;
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates fabric.Path object to add on canvas
|
||||
* @method createPath
|
||||
* @param {String} pathData Path data
|
||||
* @return {fabric.Path} path to add on canvas
|
||||
*/
|
||||
createPath: function(pathData) {
|
||||
var path = new fabric.Path(pathData);
|
||||
path.fill = null;
|
||||
path.stroke = this.canvas.freeDrawingColor;
|
||||
path.strokeWidth = this.canvas.freeDrawingLineWidth;
|
||||
return path;
|
||||
},
|
||||
|
||||
/**
|
||||
* On mouseup after drawing the path on contextTop canvas
|
||||
* we use the points captured to create an new fabric path object
|
||||
|
|
@ -219,10 +233,8 @@
|
|||
var ctx = this.canvas.contextTop;
|
||||
ctx.closePath();
|
||||
|
||||
var path = this._getSVGPathData();
|
||||
path = path.join('');
|
||||
|
||||
if (path === "M 0 0 Q 0 0 0 0 L 0 0") {
|
||||
var pathData = this._getSVGPathData().join('');
|
||||
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,
|
||||
|
|
@ -231,27 +243,23 @@
|
|||
return;
|
||||
}
|
||||
|
||||
var p = new fabric.Path(path);
|
||||
p.fill = null;
|
||||
p.stroke = this.color;
|
||||
p.strokeWidth = this.width;
|
||||
this.canvas.add(p);
|
||||
|
||||
// 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;
|
||||
|
||||
this.canvas.contextTop.arc(originLeft, originTop, 3, 0, Math.PI * 2, false);
|
||||
|
||||
p.set({ left: originLeft, top: originTop });
|
||||
var path = this.createPath(pathData);
|
||||
path.set({ left: originLeft, top: originTop });
|
||||
|
||||
// does not change position
|
||||
p.setCoords();
|
||||
this.canvas.add(path);
|
||||
path.setCoords();
|
||||
|
||||
this.canvas.contextTop && this.canvas.clearContext(this.canvas.contextTop);
|
||||
this.canvas.renderAll();
|
||||
|
||||
// fire event 'path' created
|
||||
this.canvas.fire('path:created', { path: p });
|
||||
this.canvas.fire('path:created', { path: path });
|
||||
}
|
||||
});
|
||||
})();
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -56,6 +56,15 @@
|
|||
this.width = (maxX - minX) || 1;
|
||||
this.height = (maxY - minY) || 1;
|
||||
|
||||
// var halfWidth = this.width / 2,
|
||||
// halfHeight = this.height / 2;
|
||||
|
||||
// change points to offset polygon into a bounding box
|
||||
// this.points.forEach(function(p) {
|
||||
// p.x -= halfWidth;
|
||||
// p.y -= halfHeight;
|
||||
// }, this);
|
||||
|
||||
this.minX = minX;
|
||||
this.minY = minY;
|
||||
},
|
||||
|
|
@ -108,6 +117,7 @@
|
|||
if (this.fill) {
|
||||
ctx.fill();
|
||||
}
|
||||
this._removeShadow(ctx);
|
||||
if (this.stroke) {
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@
|
|||
if (this.fill) {
|
||||
ctx.fill();
|
||||
}
|
||||
this._removeShadow(ctx);
|
||||
if (this.stroke) {
|
||||
ctx.stroke();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -121,6 +121,8 @@
|
|||
ctx.fill();
|
||||
}
|
||||
|
||||
this._removeShadow(ctx);
|
||||
|
||||
if (this.strokeDashArray) {
|
||||
this._renderDashedStroke(ctx);
|
||||
}
|
||||
|
|
@ -129,6 +131,67 @@
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @method _renderDashedStroke
|
||||
*/
|
||||
_renderDashedStroke: function(ctx) {
|
||||
|
||||
if (1 & this.strokeDashArray.length /* if odd number of items */) {
|
||||
/* duplicate items */
|
||||
this.strokeDashArray.push.apply(this.strokeDashArray, this.strokeDashArray);
|
||||
}
|
||||
|
||||
var i = 0,
|
||||
x = -this.width/2, y = -this.height/2,
|
||||
_this = this,
|
||||
padding = this.padding,
|
||||
dashedArrayLength = this.strokeDashArray.length;
|
||||
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
|
||||
/** @ignore */
|
||||
function renderSide(xMultiplier, yMultiplier) {
|
||||
|
||||
var lineLength = 0,
|
||||
lengthDiff = 0,
|
||||
sideLength = (yMultiplier ? _this.height : _this.width) + padding * 2;
|
||||
|
||||
while (lineLength < sideLength) {
|
||||
|
||||
var lengthOfSubPath = _this.strokeDashArray[i++];
|
||||
lineLength += lengthOfSubPath;
|
||||
|
||||
if (lineLength > sideLength) {
|
||||
lengthDiff = lineLength - sideLength;
|
||||
}
|
||||
|
||||
// track coords
|
||||
if (xMultiplier) {
|
||||
x += (lengthOfSubPath * xMultiplier) - (lengthDiff * xMultiplier || 0);
|
||||
}
|
||||
else {
|
||||
y += (lengthOfSubPath * yMultiplier) - (lengthDiff * yMultiplier || 0);
|
||||
}
|
||||
|
||||
ctx[1 & i /* odd */ ? 'moveTo' : 'lineTo'](x, y);
|
||||
if (i >= dashedArrayLength) {
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
renderSide(1, 0);
|
||||
renderSide(0, 1);
|
||||
renderSide(-1, 0);
|
||||
renderSide(0, -1);
|
||||
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
ctx.restore();
|
||||
},
|
||||
|
||||
/**
|
||||
* @method _normalizeLeftTopProperties
|
||||
* @private
|
||||
|
|
|
|||
70
src/shadow.class.js
Normal file
70
src/shadow.class.js
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
/**
|
||||
* Shadow class
|
||||
* @class Shadow
|
||||
* @memberOf fabric
|
||||
*/
|
||||
fabric.Shadow = fabric.util.createClass(/** @scope fabric.Shadow.prototype */ {
|
||||
|
||||
/**
|
||||
* Shadow color
|
||||
* @property
|
||||
* @type String
|
||||
*/
|
||||
color: 'rgb(0,0,0)',
|
||||
|
||||
/**
|
||||
* Shadow blur
|
||||
* @property
|
||||
* @type Number
|
||||
*/
|
||||
blur: 0,
|
||||
|
||||
/**
|
||||
* Shadow horizontal offset
|
||||
* @property
|
||||
* @type Number
|
||||
*/
|
||||
offsetX: 0,
|
||||
|
||||
/**
|
||||
* Shadow vertical offset
|
||||
* @property
|
||||
* @type Number
|
||||
*/
|
||||
offsetY: 0,
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @method initialize
|
||||
* @param [options] Options object with any of color, blur, offsetX, offsetX properties
|
||||
* @return {fabric.Shadow} thisArg
|
||||
*/
|
||||
initialize: function(options) {
|
||||
for (var prop in options) {
|
||||
this[prop] = options[prop];
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns object representation of a shadow
|
||||
* @method toObject
|
||||
* @return {Object}
|
||||
*/
|
||||
toObject: function() {
|
||||
return {
|
||||
color: this.color,
|
||||
blur: this.blur,
|
||||
offsetX: this.offsetX,
|
||||
offsetY: this.offsetY
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns SVG representation of a shadow
|
||||
* @method toSVG
|
||||
* @return {String}
|
||||
*/
|
||||
toSVG: function() {
|
||||
|
||||
}
|
||||
});
|
||||
|
|
@ -148,6 +148,9 @@
|
|||
if (options.backgroundImage) {
|
||||
this.setBackgroundImage(options.backgroundImage, this.renderAll.bind(this));
|
||||
}
|
||||
if (options.backgroundColor) {
|
||||
this.setBackgroundColor(options.backgroundColor, this.renderAll.bind(this));
|
||||
}
|
||||
this.calcOffset();
|
||||
},
|
||||
|
||||
|
|
@ -211,6 +214,33 @@
|
|||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets background color for this canvas
|
||||
* @method setBackgroundColor
|
||||
* @param {String|fabric.Pattern} Color of pattern to set background color to
|
||||
* @param {Function} callback callback to invoke when background color is set
|
||||
* @return {fabric.Canvas} thisArg
|
||||
* @chainable
|
||||
*/
|
||||
setBackgroundColor: function(backgroundColor, callback) {
|
||||
if (backgroundColor.source) {
|
||||
var _this = this;
|
||||
fabric.util.loadImage(backgroundColor.source, function(img) {
|
||||
_this.backgroundColor = new fabric.Pattern({
|
||||
source: img,
|
||||
pattern: backgroundColor.pattern
|
||||
});
|
||||
callback && callback();
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.backgroundColor = backgroundColor;
|
||||
callback && callback();
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @method _createCanvasElement
|
||||
|
|
@ -232,12 +262,8 @@
|
|||
* @param {HTMLElement} element
|
||||
*/
|
||||
_initCanvasElement: function(element) {
|
||||
if (typeof element.getContext === 'undefined' &&
|
||||
typeof G_vmlCanvasManager !== 'undefined' &&
|
||||
G_vmlCanvasManager.initElement) {
|
||||
fabric.util.createCanvasElement(element);
|
||||
|
||||
G_vmlCanvasManager.initElement(element);
|
||||
}
|
||||
if (typeof element.getContext === 'undefined') {
|
||||
throw CANVAS_INIT_ERROR;
|
||||
}
|
||||
|
|
@ -510,6 +536,7 @@
|
|||
if (this.contextTop) {
|
||||
this.clearContext(this.contextTop);
|
||||
}
|
||||
this.fire('canvas:cleared');
|
||||
this.renderAll();
|
||||
return this;
|
||||
},
|
||||
|
|
@ -533,12 +560,17 @@
|
|||
this.clearContext(canvasToDrawOn);
|
||||
}
|
||||
|
||||
this.fire('before:render');
|
||||
|
||||
if (this.clipTo) {
|
||||
this._clipCanvas(canvasToDrawOn);
|
||||
}
|
||||
|
||||
if (this.backgroundColor) {
|
||||
canvasToDrawOn.fillStyle = this.backgroundColor;
|
||||
canvasToDrawOn.fillStyle = this.backgroundColor.toLive
|
||||
? this.backgroundColor.toLive(canvasToDrawOn)
|
||||
: this.backgroundColor;
|
||||
|
||||
canvasToDrawOn.fillRect(0, 0, this.width, this.height);
|
||||
}
|
||||
|
||||
|
|
@ -546,8 +578,6 @@
|
|||
this._drawBackroundImage(canvasToDrawOn);
|
||||
}
|
||||
|
||||
this.fire('before:render');
|
||||
|
||||
var activeGroup = this.getActiveGroup();
|
||||
for (var i = 0, length = this._objects.length; i < length; ++i) {
|
||||
if (!activeGroup ||
|
||||
|
|
@ -687,6 +717,8 @@
|
|||
var data = (fabric.StaticCanvas.supports('toDataURLWithQuality'))
|
||||
? canvasEl.toDataURL('image/' + format, quality)
|
||||
: canvasEl.toDataURL('image/' + format);
|
||||
|
||||
this.contextTop && this.clearContext(this.contextTop);
|
||||
this.renderAll();
|
||||
return data;
|
||||
},
|
||||
|
|
@ -740,6 +772,7 @@
|
|||
this.setActiveObject(activeObject);
|
||||
}
|
||||
|
||||
this.contextTop && this.clearContext(this.contextTop);
|
||||
this.renderAll();
|
||||
|
||||
return dataURL;
|
||||
|
|
@ -874,7 +907,9 @@
|
|||
}
|
||||
return object;
|
||||
}, this),
|
||||
background: this.backgroundColor
|
||||
background: (this.backgroundColor && this.backgroundColor.toObject)
|
||||
? this.backgroundColor.toObject()
|
||||
: this.backgroundColor
|
||||
};
|
||||
if (this.backgroundImage) {
|
||||
data.backgroundImage = this.backgroundImage.src;
|
||||
|
|
@ -894,13 +929,22 @@
|
|||
* Returns SVG representation of canvas
|
||||
* @function
|
||||
* @method toSVG
|
||||
* @param {Object} [options] Options for SVG output ("suppressPreamble: true"
|
||||
* will start the svg output directly at "<svg...")
|
||||
* @return {String}
|
||||
*/
|
||||
toSVG: function() {
|
||||
var markup = [
|
||||
'<?xml version="1.0" standalone="no" ?>',
|
||||
'<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" ',
|
||||
'"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">',
|
||||
toSVG: function(options) {
|
||||
options || (options = { });
|
||||
var markup = [];
|
||||
|
||||
if (!options.suppressPreamble) {
|
||||
markup.push(
|
||||
'<?xml version="1.0" standalone="no" ?>',
|
||||
'<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" ',
|
||||
'"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">'
|
||||
);
|
||||
}
|
||||
markup.push(
|
||||
'<svg ',
|
||||
'xmlns="http://www.w3.org/2000/svg" ',
|
||||
'xmlns:xlink="http://www.w3.org/1999/xlink" ',
|
||||
|
|
@ -910,7 +954,7 @@
|
|||
'xml:space="preserve">',
|
||||
'<desc>Created with Fabric.js ', fabric.version, '</desc>',
|
||||
fabric.createSVGFontFacesMarkup(this.getObjects())
|
||||
];
|
||||
);
|
||||
|
||||
if (this.backgroundImage) {
|
||||
markup.push(
|
||||
|
|
@ -959,14 +1003,22 @@
|
|||
* @return {Object} removed object
|
||||
*/
|
||||
remove: function (object) {
|
||||
removeFromArray(this._objects, object);
|
||||
// removing active object should fire "selection:cleared" events
|
||||
if (this.getActiveObject() === object) {
|
||||
|
||||
// removing active object should fire "selection:cleared" events
|
||||
this.fire('before:selection:cleared', { target: object });
|
||||
this.discardActiveObject();
|
||||
this.fire('selection:cleared');
|
||||
}
|
||||
|
||||
var objects = this._objects;
|
||||
var index = objects.indexOf(object);
|
||||
|
||||
// removing any object should fire "objct:removed" events
|
||||
if (index !== -1) {
|
||||
objects.splice(index,1);
|
||||
this.fire('object:removed', { target: object });
|
||||
}
|
||||
|
||||
this.renderAll();
|
||||
return object;
|
||||
},
|
||||
|
|
@ -1191,11 +1243,8 @@
|
|||
* `null` if canvas element or context can not be initialized
|
||||
*/
|
||||
supports: function (methodName) {
|
||||
var el = fabric.document.createElement('canvas');
|
||||
var el = fabric.util.createCanvasElement();
|
||||
|
||||
if (typeof G_vmlCanvasManager !== 'undefined') {
|
||||
G_vmlCanvasManager.initElement(el);
|
||||
}
|
||||
if (!el || !el.getContext) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
fabric.Text = fabric.util.createClass(fabric.Object, /** @scope fabric.Text.prototype */ {
|
||||
|
||||
/**
|
||||
* Font size
|
||||
* Font size (in pixels)
|
||||
* @property
|
||||
* @type Number
|
||||
*/
|
||||
|
|
@ -147,11 +147,7 @@
|
|||
* @method _initDimensions
|
||||
*/
|
||||
_initDimensions: function() {
|
||||
var canvasEl = fabric.document.createElement('canvas');
|
||||
|
||||
if (!canvasEl.getContext && typeof G_vmlCanvasManager !== 'undefined') {
|
||||
G_vmlCanvasManager.initElement(canvasEl);
|
||||
}
|
||||
var canvasEl = fabric.util.createCanvasElement();
|
||||
|
||||
this._render(canvasEl.getContext('2d'));
|
||||
},
|
||||
|
|
@ -322,8 +318,8 @@
|
|||
* @method _setTextStyles
|
||||
*/
|
||||
_setTextStyles: function(ctx) {
|
||||
ctx.fillStyle = this.fill.toLiveGradient
|
||||
? this.fill.toLiveGradient(ctx)
|
||||
ctx.fillStyle = this.fill.toLive
|
||||
? this.fill.toLive(ctx)
|
||||
: this.fill;
|
||||
ctx.strokeStyle = this.strokeStyle;
|
||||
ctx.lineWidth = this.strokeWidth;
|
||||
|
|
|
|||
|
|
@ -172,27 +172,41 @@
|
|||
* @method getPointer
|
||||
* @memberOf fabric.util
|
||||
* @param {Event} event
|
||||
* @param {HTMLCanvasElement} upperCanvasEl <canvas> element on which object selection is drawn
|
||||
*/
|
||||
function getPointer(event) {
|
||||
function getPointer(event, upperCanvasEl) {
|
||||
event || (event = fabric.window.event);
|
||||
|
||||
var element = event.target || (typeof event.srcElement !== 'unknown' ? event.srcElement : null),
|
||||
body = fabric.document.body || {scrollLeft: 0, scrollTop: 0},
|
||||
docElement = fabric.document.documentElement,
|
||||
orgElement = element,
|
||||
scrollLeft = 0,
|
||||
scrollTop = 0,
|
||||
firstFixedAncestor;
|
||||
|
||||
while (element && element.parentNode && !firstFixedAncestor) {
|
||||
element = element.parentNode;
|
||||
element = element.parentNode;
|
||||
|
||||
if (element !== fabric.document && fabric.util.getElementPosition(element) === 'fixed') firstFixedAncestor = element;
|
||||
if (element !== fabric.document && fabric.util.getElementPosition(element) === 'fixed') firstFixedAncestor = element;
|
||||
|
||||
if (element !== fabric.document && orgElement !== upperCanvasEl && fabric.util.getElementPosition(element) === 'absolute') {
|
||||
scrollLeft = 0;
|
||||
scrollTop = 0;
|
||||
}
|
||||
else if (element === fabric.document && orgElement !== upperCanvasEl) {
|
||||
scrollLeft = body.scrollLeft || docElement.scrollLeft || 0;
|
||||
scrollTop = body.scrollTop || docElement.scrollTop || 0;
|
||||
}
|
||||
else {
|
||||
scrollLeft += element.scrollLeft || 0;
|
||||
scrollTop += element.scrollTop || 0;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
x: pointerX(event) + scrollLeft,
|
||||
y: pointerY(event) + scrollTop
|
||||
x: pointerX(event) + scrollLeft,
|
||||
y: pointerY(event) + scrollTop
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -209,10 +223,10 @@
|
|||
|
||||
if (fabric.isTouchSupported) {
|
||||
pointerX = function(event) {
|
||||
return event.touches && event.touches[0] && event.touches[0].pageX || event.clientX;
|
||||
return (event.touches && event.touches[0] ? (event.touches[0].pageX - (event.touches[0].pageX - event.touches[0].clientX)) || event.clientX : event.clientX);
|
||||
};
|
||||
pointerY = function(event) {
|
||||
return event.touches && event.touches[0] && event.touches[0].pageY || event.clientY;
|
||||
return (event.touches && event.touches[0] ? (event.touches[0].pageY - (event.touches[0].pageY - event.touches[0].clientY)) || event.clientY : event.clientY);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
(function() {
|
||||
|
||||
var sqrt = Math.sqrt,
|
||||
atan2 = Math.atan2;
|
||||
|
||||
/**
|
||||
* @namespace
|
||||
*/
|
||||
|
|
@ -295,6 +298,55 @@
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a dashed line between two points
|
||||
*
|
||||
* This method is used to draw dashed line around selection area.
|
||||
* See <a href="http://stackoverflow.com/questions/4576724/dotted-stroke-in-canvas">dotted stroke in canvas</a>
|
||||
*
|
||||
* @method drawDashedLine
|
||||
* @param ctx {Canvas} context
|
||||
* @param x {Number} start x coordinate
|
||||
* @param y {Number} start y coordinate
|
||||
* @param x2 {Number} end x coordinate
|
||||
* @param y2 {Number} end y coordinate
|
||||
* @param da {Array} dash array pattern
|
||||
*/
|
||||
function drawDashedLine(ctx, x, y, x2, y2, da) {
|
||||
var dx = x2 - x,
|
||||
dy = y2 - y,
|
||||
len = sqrt(dx*dx + dy*dy),
|
||||
rot = atan2(dy, dx),
|
||||
dc = da.length,
|
||||
di = 0,
|
||||
draw = true;
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(x, y);
|
||||
ctx.moveTo(0, 0);
|
||||
ctx.rotate(rot);
|
||||
|
||||
x = 0;
|
||||
while (len > x) {
|
||||
x += da[di++ % dc];
|
||||
if (x > len) {
|
||||
x = len;
|
||||
}
|
||||
ctx[draw ? 'lineTo' : 'moveTo'](x, 0);
|
||||
draw = !draw;
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function createCanvasElement() {
|
||||
var canvasEl = fabric.document.createElement('canvas');
|
||||
if (!canvasEl.getContext && typeof G_vmlCanvasManager !== 'undefined') {
|
||||
G_vmlCanvasManager.initElement(canvasEl);
|
||||
}
|
||||
return canvasEl;
|
||||
}
|
||||
|
||||
fabric.util.removeFromArray = removeFromArray;
|
||||
fabric.util.degreesToRadians = degreesToRadians;
|
||||
fabric.util.radiansToDegrees = radiansToDegrees;
|
||||
|
|
@ -308,4 +360,7 @@
|
|||
fabric.util.enlivenObjects = enlivenObjects;
|
||||
fabric.util.groupSVGElements = groupSVGElements;
|
||||
fabric.util.populateWithProperties = populateWithProperties;
|
||||
fabric.util.drawDashedLine = drawDashedLine;
|
||||
fabric.util.createCanvasElement = createCanvasElement;
|
||||
|
||||
})();
|
||||
4
test.js
4
test.js
|
|
@ -25,7 +25,9 @@ testrunner.run({
|
|||
'./test/unit/parser.js',
|
||||
'./test/unit/canvas.js',
|
||||
'./test/unit/canvas_static.js',
|
||||
'./test/unit/gradient.js'
|
||||
'./test/unit/gradient.js',
|
||||
'./test/unit/pattern.js',
|
||||
'./test/unit/shadow.js'
|
||||
]
|
||||
}, function(err, report) {
|
||||
if(report.failed > 0){
|
||||
|
|
|
|||
BIN
test/fixtures/greyfloral.png
vendored
Normal file
BIN
test/fixtures/greyfloral.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6 KiB |
|
|
@ -23,12 +23,12 @@
|
|||
|
||||
var PATH_DATALESS_JSON = '{"objects":[{"type":"path","originX":"center","originY":"center","left":200,"top":200,"width":200,"height":200,"fill":"rgb(0,0,0)",'+
|
||||
'"overlayFill":null,"stroke":null,"strokeWidth":1,"strokeDashArray":null,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,'+
|
||||
'"flipY":false,"opacity":1,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,"perPixelTargetFind":false,'+
|
||||
'"flipY":false,"opacity":1,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,"perPixelTargetFind":false,"shadow":null,'+
|
||||
'"path":"http://example.com/"}],"background":""}';
|
||||
|
||||
var RECT_JSON = '{"objects":[{"type":"rect","originX":"center","originY":"center","left":0,"top":0,"width":10,"height":10,"fill":"rgb(0,0,0)","overlayFill":null,'+
|
||||
'"stroke":null,"strokeWidth":1,"strokeDashArray":null,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,'+
|
||||
'"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,"perPixelTargetFind":false,"rx":0,"ry":0}],'+
|
||||
'"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,"perPixelTargetFind":false,"shadow":null,"rx":0,"ry":0}],'+
|
||||
'"background":"#ff5555"}';
|
||||
|
||||
var el = fabric.document.createElement('canvas');
|
||||
|
|
|
|||
|
|
@ -21,17 +21,17 @@
|
|||
|
||||
var PATH_DATALESS_JSON = '{"objects":[{"type":"path","originX":"center","originY":"center","left":200,"top":200,"width":200,"height":200,"fill":"rgb(0,0,0)",'+
|
||||
'"overlayFill":null,"stroke":null,"strokeWidth":1,"strokeDashArray":null,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,'+
|
||||
'"flipY":false,"opacity":1,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,"perPixelTargetFind":false,'+
|
||||
'"flipY":false,"opacity":1,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,"perPixelTargetFind":false,"shadow":null,'+
|
||||
'"path":"http://example.com/"}],"background":""}';
|
||||
|
||||
var RECT_JSON = '{"objects":[{"type":"rect","originX":"center","originY":"center","left":0,"top":0,"width":10,"height":10,"fill":"rgb(0,0,0)","overlayFill":null,'+
|
||||
'"stroke":null,"strokeWidth":1,"strokeDashArray":null,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"selectable":true,'+
|
||||
'"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,"perPixelTargetFind":false,"rx":0,"ry":0}],'+
|
||||
'"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,"perPixelTargetFind":false,"shadow":null,"rx":0,"ry":0}],'+
|
||||
'"background":"#ff5555"}';
|
||||
|
||||
var RECT_JSON_WITH_PADDING = '{"objects":[{"type":"rect","originX":"center","originY":"center","left":0,"top":0,"width":10,"height":20,"fill":"rgb(0,0,0)","overlayFill":null,'+
|
||||
'"stroke":null,"strokeWidth":1,"strokeDashArray":null,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"selectable":true,'+
|
||||
'"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,"perPixelTargetFind":false,"padding":123,"foo":"bar","rx":0,"ry":0}],'+
|
||||
'"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,"perPixelTargetFind":false,"shadow":null,"padding":123,"foo":"bar","rx":0,"ry":0}],'+
|
||||
'"background":""}';
|
||||
|
||||
// force creation of static canvas
|
||||
|
|
@ -193,6 +193,15 @@
|
|||
equal(rect.getAngle(), 90, 'angle should be coerced to 90 (from 100)');
|
||||
});
|
||||
|
||||
test('toSVG without preamble', function() {
|
||||
ok(typeof canvas.toSVG == 'function');
|
||||
var withPreamble = canvas.toSVG();
|
||||
var withoutPreamble = canvas.toSVG({suppressPreamble: true});
|
||||
ok(withPreamble != withoutPreamble);
|
||||
equal(withoutPreamble.slice(0, 4), '<svg', 'svg should start with root node when premable is suppressed');
|
||||
});
|
||||
|
||||
|
||||
test('toJSON', function() {
|
||||
ok(typeof canvas.toJSON == 'function');
|
||||
equal(JSON.stringify(canvas.toJSON()), '{"objects":[],"background":""}');
|
||||
|
|
@ -682,4 +691,4 @@
|
|||
});
|
||||
});
|
||||
|
||||
})();
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@
|
|||
'hasRotatingPoint': true,
|
||||
'transparentCorners': true,
|
||||
'perPixelTargetFind': false,
|
||||
'shadow': null,
|
||||
'radius': 0
|
||||
};
|
||||
ok(typeof circle.toObject == 'function');
|
||||
|
|
|
|||
|
|
@ -47,7 +47,8 @@
|
|||
'hasBorders': true,
|
||||
'hasRotatingPoint': true,
|
||||
'transparentCorners': true,
|
||||
'perPixelTargetFind': false
|
||||
'perPixelTargetFind': false,
|
||||
'shadow': null
|
||||
};
|
||||
ok(typeof ellipse.toObject == 'function');
|
||||
deepEqual(defaultProperties, ellipse.toObject());
|
||||
|
|
|
|||
|
|
@ -48,10 +48,10 @@
|
|||
equal(object.colorStops, gradient.colorStops);
|
||||
});
|
||||
|
||||
test('toLiveGradient', function() {
|
||||
test('toLive', function() {
|
||||
var gradient = createGradient();
|
||||
|
||||
ok(typeof gradient.toLiveGradient == 'function');
|
||||
ok(typeof gradient.toLive == 'function');
|
||||
});
|
||||
|
||||
test('fromElement', function() {
|
||||
|
|
|
|||
|
|
@ -143,6 +143,7 @@
|
|||
'hasRotatingPoint': true,
|
||||
'transparentCorners': true,
|
||||
'perPixelTargetFind': false,
|
||||
'shadow': null,
|
||||
'angle': 0,
|
||||
'flipX': false,
|
||||
'flipY': false,
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@
|
|||
'hasRotatingPoint': true,
|
||||
'transparentCorners': true,
|
||||
'perPixelTargetFind': false,
|
||||
'shadow': null,
|
||||
'filters': []
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@
|
|||
'hasBorders': true,
|
||||
'hasRotatingPoint': true,
|
||||
'transparentCorners': true,
|
||||
'perPixelTargetFind': false
|
||||
'perPixelTargetFind': false,
|
||||
'shadow': null
|
||||
};
|
||||
|
||||
QUnit.module('fabric.Line');
|
||||
|
|
|
|||
|
|
@ -111,11 +111,11 @@
|
|||
test('toJSON', function() {
|
||||
var emptyObjectJSON = '{"type":"object","originX":"center","originY":"center","left":0,"top":0,"width":0,"height":0,"fill":"rgb(0,0,0)",'+
|
||||
'"overlayFill":null,"stroke":null,"strokeWidth":1,"strokeDashArray":null,"scaleX":1,"scaleY":1,"angle":0,'+
|
||||
'"flipX":false,"flipY":false,"opacity":1,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,"perPixelTargetFind":false}';
|
||||
'"flipX":false,"flipY":false,"opacity":1,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,"perPixelTargetFind":false,"shadow":null}';
|
||||
|
||||
var augmentedJSON = '{"type":"object","originX":"center","originY":"center","left":0,"top":0,"width":122,"height":0,"fill":"rgb(0,0,0)",'+
|
||||
'"overlayFill":null,"stroke":null,"strokeWidth":1,"strokeDashArray":null,"scaleX":1.3,"scaleY":1,"angle":0,'+
|
||||
'"flipX":false,"flipY":true,"opacity":0.88,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,"perPixelTargetFind":false}';
|
||||
'"flipX":false,"flipY":true,"opacity":0.88,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,"perPixelTargetFind":false,"shadow":null}';
|
||||
|
||||
var cObj = new fabric.Object();
|
||||
ok(typeof cObj.toJSON == 'function');
|
||||
|
|
@ -150,7 +150,8 @@
|
|||
'hasBorders': true,
|
||||
'hasRotatingPoint': true,
|
||||
'transparentCorners': true,
|
||||
'perPixelTargetFind': false
|
||||
'perPixelTargetFind': false,
|
||||
'shadow': null
|
||||
};
|
||||
|
||||
var augmentedObjectRepr = {
|
||||
|
|
@ -177,7 +178,8 @@
|
|||
'hasBorders': true,
|
||||
'hasRotatingPoint': true,
|
||||
'transparentCorners': true,
|
||||
'perPixelTargetFind': false
|
||||
'perPixelTargetFind': false,
|
||||
'shadow': null
|
||||
};
|
||||
|
||||
var cObj = new fabric.Object();
|
||||
|
|
@ -811,4 +813,22 @@
|
|||
equal(object.toObject().fill.colorStops['1'], 'green');
|
||||
});
|
||||
|
||||
test('setShadow', function() {
|
||||
var object = new fabric.Object();
|
||||
|
||||
object.setShadow({
|
||||
color: 'red',
|
||||
blur: 10,
|
||||
offsetX: 5,
|
||||
offsetY: 15
|
||||
});
|
||||
|
||||
ok(object.shadow instanceof fabric.Shadow);
|
||||
|
||||
equal(object.shadow.color, 'red');
|
||||
equal(object.shadow.blur, 10);
|
||||
equal(object.shadow.offsetX, 5);
|
||||
equal(object.shadow.offsetY, 15);
|
||||
});
|
||||
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@
|
|||
'hasBorders': true,
|
||||
'hasRotatingPoint': true,
|
||||
'transparentCorners': true,
|
||||
'perPixelTargetFind': false
|
||||
'perPixelTargetFind': false,
|
||||
'shadow': null
|
||||
};
|
||||
|
||||
function getPathElement(path) {
|
||||
|
|
|
|||
|
|
@ -1,31 +1,32 @@
|
|||
(function(){
|
||||
|
||||
var REFERENCE_PATH_GROUP_OBJECT = {
|
||||
'type': 'path-group',
|
||||
'originX': 'center',
|
||||
'originY': 'center',
|
||||
'left': 0,
|
||||
'top': 0,
|
||||
'width': 0,
|
||||
'height': 0,
|
||||
'fill': '',
|
||||
'overlayFill': null,
|
||||
'stroke': null,
|
||||
'strokeWidth': 1,
|
||||
'strokeDashArray': null,
|
||||
'scaleX': 1,
|
||||
'scaleY': 1,
|
||||
'angle': 0,
|
||||
'flipX': false,
|
||||
'flipY': false,
|
||||
'opacity': 1,
|
||||
'selectable': true,
|
||||
'hasControls': true,
|
||||
'hasBorders': true,
|
||||
'hasRotatingPoint': true,
|
||||
'type': 'path-group',
|
||||
'originX': 'center',
|
||||
'originY': 'center',
|
||||
'left': 0,
|
||||
'top': 0,
|
||||
'width': 0,
|
||||
'height': 0,
|
||||
'fill': '',
|
||||
'overlayFill': null,
|
||||
'stroke': null,
|
||||
'strokeWidth': 1,
|
||||
'strokeDashArray': null,
|
||||
'scaleX': 1,
|
||||
'scaleY': 1,
|
||||
'angle': 0,
|
||||
'flipX': false,
|
||||
'flipY': false,
|
||||
'opacity': 1,
|
||||
'selectable': true,
|
||||
'hasControls': true,
|
||||
'hasBorders': true,
|
||||
'hasRotatingPoint': true,
|
||||
'transparentCorners': true,
|
||||
'perPixelTargetFind': false,
|
||||
'paths': getPathObjects()
|
||||
'shadow': null,
|
||||
'paths': getPathObjects()
|
||||
};
|
||||
|
||||
function getPathElement(path) {
|
||||
|
|
|
|||
77
test/unit/pattern.js
Normal file
77
test/unit/pattern.js
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
(function() {
|
||||
|
||||
function createImageElement() {
|
||||
return fabric.isLikelyNode ? new (require('canvas').Image) : fabric.document.createElement('img');
|
||||
}
|
||||
function setSrc(img, src, callback) {
|
||||
if (fabric.isLikelyNode) {
|
||||
require('fs').readFile(src, function(err, imgData) {
|
||||
if (err) throw err;
|
||||
img.src = imgData;
|
||||
img._src = src;
|
||||
callback && callback();
|
||||
});
|
||||
}
|
||||
else {
|
||||
img.src = src;
|
||||
callback && callback();
|
||||
}
|
||||
}
|
||||
|
||||
QUnit.module('fabric.Pattern');
|
||||
|
||||
var img = createImageElement();
|
||||
setSrc(img, fabric.isLikelyNode ?
|
||||
(__dirname + '/../fixtures/greyfloral.png')
|
||||
: '../fixtures/greyfloral.png');
|
||||
|
||||
function createPattern() {
|
||||
return new fabric.Pattern({
|
||||
source: img
|
||||
});
|
||||
}
|
||||
|
||||
test('constructor', function() {
|
||||
ok(fabric.Pattern);
|
||||
|
||||
var pattern = createPattern();
|
||||
ok(pattern instanceof fabric.Pattern, 'should inherit from fabric.Pattern');
|
||||
});
|
||||
|
||||
test('properties', function() {
|
||||
var pattern = createPattern();
|
||||
|
||||
equal(pattern.source, img);
|
||||
equal(pattern.repeat, 'repeat');
|
||||
});
|
||||
|
||||
test('toObject', function() {
|
||||
var pattern = createPattern();
|
||||
|
||||
ok(typeof pattern.toObject == 'function');
|
||||
|
||||
var object = pattern.toObject();
|
||||
|
||||
// node-canvas doesn't give <img> "src"
|
||||
if (img.src) {
|
||||
equal(object.source, '../fixtures/greyfloral.png');
|
||||
}
|
||||
equal(object.repeat, 'repeat');
|
||||
|
||||
var sourceExecuted;
|
||||
var patternWithGetSource = new fabric.Pattern({
|
||||
source: function() {return fabric.document.createElement("canvas")}
|
||||
});
|
||||
|
||||
var object2 = patternWithGetSource.toObject();
|
||||
equal(object2.source, 'return fabric.document.createElement("canvas")');
|
||||
equal(object2.repeat, 'repeat');
|
||||
});
|
||||
|
||||
test('toLive', function() {
|
||||
var pattern = createPattern();
|
||||
|
||||
ok(typeof pattern.toLive == 'function');
|
||||
});
|
||||
|
||||
})();
|
||||
|
|
@ -32,7 +32,8 @@
|
|||
'hasBorders': true,
|
||||
'hasRotatingPoint': true,
|
||||
'transparentCorners': true,
|
||||
'perPixelTargetFind': false
|
||||
'perPixelTargetFind': false,
|
||||
'shadow': null
|
||||
};
|
||||
|
||||
QUnit.module('fabric.Polygon');
|
||||
|
|
@ -46,7 +47,7 @@
|
|||
ok(polygon instanceof fabric.Object);
|
||||
|
||||
equal(polygon.type, 'polygon');
|
||||
deepEqual(getPoints(), polygon.get('points'));
|
||||
deepEqual([ { x: 5, y: 7 }, { x: 15, y: 17 } ], polygon.get('points'));
|
||||
});
|
||||
|
||||
test('complexity', function() {
|
||||
|
|
@ -58,7 +59,11 @@
|
|||
var polygon = new fabric.Polygon(getPoints());
|
||||
ok(typeof polygon.toObject == 'function');
|
||||
|
||||
deepEqual(REFERENCE_OBJECT, polygon.toObject());
|
||||
var objectWithOriginalPoints = fabric.util.object.extend(polygon.toObject(), {
|
||||
points: getPoints()
|
||||
});
|
||||
|
||||
deepEqual(objectWithOriginalPoints, REFERENCE_OBJECT);
|
||||
});
|
||||
|
||||
test('fromObject', function() {
|
||||
|
|
@ -89,7 +94,12 @@
|
|||
elPolygonWithAttrs.setAttribute('transform', 'translate(-10,-20) scale(2)');
|
||||
|
||||
var polygonWithAttrs = fabric.Polygon.fromElement(elPolygonWithAttrs);
|
||||
var expectedPoints = [{x: 10, y: 10}, {x: 20, y: 20}, {x: 30, y: 30}, {x: 10, y: 10}];
|
||||
var expectedPoints = [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 10, y: 10 },
|
||||
{ x: 20, y: 20 },
|
||||
{ x: 0, y: 0 }
|
||||
];
|
||||
|
||||
deepEqual(fabric.util.object.extend(REFERENCE_OBJECT, {
|
||||
'width': 20,
|
||||
|
|
|
|||
|
|
@ -32,7 +32,8 @@
|
|||
'hasBorders': true,
|
||||
'hasRotatingPoint': true,
|
||||
'transparentCorners': true,
|
||||
'perPixelTargetFind': false
|
||||
'perPixelTargetFind': false,
|
||||
'shadow': null
|
||||
};
|
||||
|
||||
QUnit.module('fabric.Polyline');
|
||||
|
|
@ -46,7 +47,7 @@
|
|||
ok(polyline instanceof fabric.Object);
|
||||
|
||||
equal(polyline.type, 'polyline');
|
||||
deepEqual(polyline.get('points'), getPoints());
|
||||
deepEqual(polyline.get('points'), [ { x: 5, y: 7 }, { x: 15, y: 17 } ]);
|
||||
});
|
||||
|
||||
test('complexity', function() {
|
||||
|
|
@ -57,7 +58,11 @@
|
|||
test('toObject', function() {
|
||||
var polyline = new fabric.Polyline(getPoints());
|
||||
ok(typeof polyline.toObject == 'function');
|
||||
deepEqual(polyline.toObject(), REFERENCE_OBJECT);
|
||||
var objectWithOriginalPoints = fabric.util.object.extend(polyline.toObject(), {
|
||||
points: getPoints()
|
||||
});
|
||||
|
||||
deepEqual(objectWithOriginalPoints, REFERENCE_OBJECT);
|
||||
});
|
||||
|
||||
test('fromObject', function() {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
'hasRotatingPoint': true,
|
||||
'transparentCorners': true,
|
||||
'perPixelTargetFind': false,
|
||||
'shadow': null,
|
||||
'rx': 0,
|
||||
'ry': 0
|
||||
};
|
||||
|
|
|
|||
34
test/unit/shadow.js
Normal file
34
test/unit/shadow.js
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
(function() {
|
||||
|
||||
QUnit.module('fabric.Shadow');
|
||||
|
||||
test('constructor', function() {
|
||||
ok(fabric.Shadow);
|
||||
|
||||
var shadow = new fabric.Shadow();
|
||||
ok(shadow instanceof fabric.Shadow, 'should inherit from fabric.Shadow');
|
||||
});
|
||||
|
||||
test('properties', function() {
|
||||
var shadow = new fabric.Shadow();
|
||||
|
||||
equal(shadow.blur, 0);
|
||||
equal(shadow.color, 'rgb(0,0,0)');
|
||||
equal(shadow.offsetX, 0);
|
||||
equal(shadow.offsetY, 0);
|
||||
});
|
||||
|
||||
test('toObject', function() {
|
||||
var shadow = new fabric.Shadow();
|
||||
ok(typeof shadow.toObject == 'function');
|
||||
|
||||
var object = shadow.toObject();
|
||||
equal(JSON.stringify(object), '{"color":"rgb(0,0,0)","blur":0,"offsetX":0,"offsetY":0}');
|
||||
});
|
||||
|
||||
// TODO: implement and test this
|
||||
// test('toSVG', function() {
|
||||
//
|
||||
// });
|
||||
|
||||
})();
|
||||
|
|
@ -31,6 +31,7 @@
|
|||
'hasRotatingPoint': true,
|
||||
'transparentCorners': true,
|
||||
'perPixelTargetFind': false,
|
||||
'shadow': null,
|
||||
'text': 'x',
|
||||
'fontSize': 40,
|
||||
'fontWeight': 400,
|
||||
|
|
|
|||
Loading…
Reference in a new issue