Comparing version 4.6.0 to 5.0.0-browser
/*! Fabric.js Copyright 2008-2015, Printio (Juriy Zaytsev, Maxim Chernyak) */ | ||
var fabric = fabric || { version: '4.6.0' }; | ||
var fabric = fabric || { version: '5.0.0' }; | ||
if (typeof exports !== 'undefined') { | ||
@@ -5,0 +5,0 @@ exports.fabric = fabric; |
@@ -1371,3 +1371,7 @@ /*: | ||
var distance = Math.sqrt(dx * dx + dy * dy); | ||
scale += distance / start.distance; | ||
// If touch start.distance from centroid is 0, scale should not be updated. | ||
// This prevents dividing by 0 in cases where start.distance is oddly 0. | ||
if (start.distance !== 0) { | ||
scale += distance / start.distance; | ||
} | ||
// Calculate rotation. | ||
@@ -1769,4 +1773,4 @@ var angle = Math.atan2(dx, dy) / RAD_DEG; | ||
if (!pt) continue; | ||
var x = (touch.pageX - bbox.x1); | ||
var y = (touch.pageY - bbox.y1); | ||
var x = (touch.pageX - bbox.x1 - parseInt(window.scrollX)); | ||
var y = (touch.pageY - bbox.y1 - parseInt(window.scrollY)); | ||
/// | ||
@@ -1773,0 +1777,0 @@ var dx = x - pt.start.x; |
175
package.json
{ | ||
"name": "fabric", | ||
"description": "Object model for HTML5 canvas, and SVG-to-canvas parser. Backed by jsdom and node-canvas.", | ||
"homepage": "http://fabricjs.com/", | ||
"version": "4.6.0", | ||
"author": "Juriy Zaytsev <kangax@gmail.com>", | ||
"contributors": [ | ||
{ | ||
"name": "Andrea Bogazzi", | ||
"email": "andreabogazzi79@gmail.com" | ||
}, | ||
{ | ||
"name": "Steve Eberhardt", | ||
"email": "melchiar2@gmail.com" | ||
} | ||
], | ||
"keywords": [ | ||
"canvas", | ||
"graphic", | ||
"graphics", | ||
"SVG", | ||
"node-canvas", | ||
"parser", | ||
"HTML5", | ||
"object model" | ||
], | ||
"browser": { | ||
"canvas": false, | ||
"fs": false, | ||
"jsdom": false, | ||
"jsdom/lib/jsdom/living/generated/utils": false, | ||
"jsdom/lib/jsdom/utils": false, | ||
"http": false, | ||
"https": false, | ||
"xmldom": false, | ||
"url": false | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/fabricjs/fabric.js" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/fabricjs/fabric.js/issues" | ||
}, | ||
"license": "MIT", | ||
"scripts": { | ||
"build": "node build.js modules=ALL requirejs exclude=gestures,accessors,erasing", | ||
"build:fast": "node build.js modules=ALL requirejs fast exclude=gestures,accessors,erasing", | ||
"build:watch": "onchange 'src/**/**' 'HEADER.js' 'lib/**/**' -- npm run build_export", | ||
"link:watch": "onchange 'src/**/**' 'HEADER.js' 'lib/**/**' -- npm link", | ||
"build_with_gestures": "node build.js modules=ALL exclude=accessors", | ||
"build_export": "npm run build:fast && npm run export_dist_to_site", | ||
"test:single": "qunit test/node_test_setup.js test/lib", | ||
"test:coverage": "nyc --silent qunit test/node_test_setup.js test/lib test/unit", | ||
"test:visual:coverage": "nyc --silent --no-clean qunit test/node_test_setup.js test/lib test/visual", | ||
"coverage:report": "nyc report --reporter=lcov --reporter=text", | ||
"test": "qunit test/node_test_setup.js test/lib test/unit", | ||
"test:visual": "qunit test/node_test_setup.js test/lib test/visual", | ||
"test:visual:single": "qunit test/node_test_setup.js test/lib", | ||
"test:all": "npm run test && npm run test:visual", | ||
"lint": "eslint --config .eslintrc.json src", | ||
"lint_tests": "eslint test/unit --config .eslintrc_tests && eslint test/visual --config .eslintrc_tests", | ||
"export_gesture_to_site": "cp dist/fabric.js ../fabricjs.com/lib/fabric_with_gestures.js", | ||
"export_dist_to_site": "cp dist/fabric.js ../fabricjs.com/lib/fabric.js && cp package.json ../fabricjs.com/lib/package.json && cp -r src HEADER.js lib ../fabricjs.com/build/files/", | ||
"export_tests_to_site": "cp test/unit/*.js ../fabricjs.com/test/unit && cp -r test/visual/* ../fabricjs.com/test/visual && cp -r test/fixtures/* ../fabricjs.com/test/fixtures && cp -r test/lib/* ../fabricjs.com/test/lib", | ||
"all": "npm run build && npm run test && npm run test:visual && npm run lint && npm run lint_tests && npm run export_dist_to_site && npm run export_tests_to_site", | ||
"testem": "testem .", | ||
"testem:ci": "testem ci" | ||
}, | ||
"optionalDependencies": { | ||
"canvas": "^2.6.1", | ||
"jsdom": "^15.2.1" | ||
}, | ||
"devDependencies": { | ||
"chalk": "^2.4.1", | ||
"eslint": "4.18.x", | ||
"nyc": "^15.1.0", | ||
"onchange": "^3.x.x", | ||
"pixelmatch": "^4.0.2", | ||
"qunit": "^2.13.0", | ||
"testem": "^3.2.0", | ||
"uglify-js": "3.3.x" | ||
}, | ||
"engines": { | ||
"node": ">=8.0.0" | ||
}, | ||
"main": "./dist/fabric.js", | ||
"dependencies": {} | ||
} | ||
"name": "fabric", | ||
"description": "Object model for HTML5 canvas, and SVG-to-canvas parser. Backed by jsdom and node-canvas.", | ||
"homepage": "http://fabricjs.com/", | ||
"version": "5.0.0-browser", | ||
"author": "Juriy Zaytsev <kangax@gmail.com>", | ||
"contributors": [ | ||
{ | ||
"name": "Andrea Bogazzi", | ||
"email": "andreabogazzi79@gmail.com" | ||
}, | ||
{ | ||
"name": "Steve Eberhardt", | ||
"email": "melchiar2@gmail.com" | ||
} | ||
], | ||
"keywords": [ | ||
"canvas", | ||
"graphic", | ||
"graphics", | ||
"SVG", | ||
"node-canvas", | ||
"parser", | ||
"HTML5", | ||
"object model" | ||
], | ||
"browser": { | ||
"canvas": false, | ||
"fs": false, | ||
"jsdom": false, | ||
"jsdom/lib/jsdom/living/generated/utils": false, | ||
"jsdom/lib/jsdom/utils": false, | ||
"http": false, | ||
"https": false, | ||
"xmldom": false, | ||
"url": false | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/fabricjs/fabric.js" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/fabricjs/fabric.js/issues" | ||
}, | ||
"license": "MIT", | ||
"scripts": { | ||
"changelog": "auto-changelog -o change-output.md --unreleased-only", | ||
"build": "node build.js modules=ALL requirejs exclude=gestures,accessors,erasing", | ||
"build:fast": "node build.js modules=ALL requirejs fast exclude=gestures,accessors,erasing", | ||
"build:watch": "onchange 'src/**/**' 'HEADER.js' 'lib/**/**' -- npm run build_export", | ||
"link:watch": "onchange 'src/**/**' 'HEADER.js' 'lib/**/**' -- npm link", | ||
"build_with_gestures": "node build.js modules=ALL exclude=accessors", | ||
"build_export": "npm run build:fast && npm run export_dist_to_site", | ||
"test:single": "qunit test/node_test_setup.js test/lib", | ||
"test:coverage": "nyc --silent qunit test/node_test_setup.js test/lib test/unit", | ||
"test:visual:coverage": "nyc --silent --no-clean qunit test/node_test_setup.js test/lib test/visual", | ||
"coverage:report": "nyc report --reporter=lcov --reporter=text", | ||
"test": "qunit test/node_test_setup.js test/lib test/unit", | ||
"test:visual": "qunit test/node_test_setup.js test/lib test/visual", | ||
"test:visual:single": "qunit test/node_test_setup.js test/lib", | ||
"test:all": "npm run test && npm run test:visual", | ||
"lint": "eslint --config .eslintrc.json src", | ||
"lint_tests": "eslint test/unit --config .eslintrc_tests && eslint test/visual --config .eslintrc_tests", | ||
"export_gesture_to_site": "cp dist/fabric.js ../fabricjs.com/lib/fabric_with_gestures.js", | ||
"export_dist_to_site": "cp dist/fabric.js ../fabricjs.com/lib/fabric.js && cp package.json ../fabricjs.com/lib/package.json && cp -r src HEADER.js lib ../fabricjs.com/build/files/", | ||
"export_tests_to_site": "cp test/unit/*.js ../fabricjs.com/test/unit && cp -r test/visual/* ../fabricjs.com/test/visual && cp -r test/fixtures/* ../fabricjs.com/test/fixtures && cp -r test/lib/* ../fabricjs.com/test/lib", | ||
"all": "npm run build && npm run test && npm run test:visual && npm run lint && npm run lint_tests && npm run export_dist_to_site && npm run export_tests_to_site", | ||
"testem": "testem .", | ||
"testem:ci": "testem ci" | ||
}, | ||
"optionalDependencies": {}, | ||
"devDependencies": { | ||
"auto-changelog": "^2.3.0", | ||
"chalk": "^2.4.1", | ||
"eslint": "4.18.x", | ||
"nyc": "^15.1.0", | ||
"onchange": "^7.1.0", | ||
"pixelmatch": "^4.0.2", | ||
"qunit": "^2.13.0", | ||
"testem": "^3.2.0", | ||
"uglify-js": "3.3.x" | ||
}, | ||
"engines": { | ||
"node": ">=14.0.0" | ||
}, | ||
"main": "./dist/fabric.js", | ||
"dependencies": {} | ||
} |
@@ -17,7 +17,2 @@ ## Fabric.js | ||
<!-- deps status --> | ||
[![Dependency Status](https://david-dm.org/kangax/fabric.js.svg?theme=shields.io)](https://david-dm.org/kangax/fabric.js) | ||
[![devDependency Status](https://david-dm.org/kangax/fabric.js/dev-status.svg?theme=shields.io)](https://david-dm.org/kangax/fabric.js#info=devDependencies) | ||
<!-- bounties, tips --> | ||
@@ -60,3 +55,3 @@ | ||
- Edge (chromium based, all versions) | ||
- IE11 and Edge legacy, supported but deprecated. | ||
- IE11 and Edge legacy, not supported. Fabric up to 5.0 is written with ES5 in mind, but no specific tests are run for those browsers. | ||
@@ -63,0 +58,0 @@ You can [run automated unit tests](http://fabricjs.com/test/unit/) right in the browser. |
@@ -71,5 +71,5 @@ /** | ||
* @private | ||
* @param {CanvasRenderingContext2D} ctx | ||
*/ | ||
_setBrushStyles: function() { | ||
var ctx = this.canvas.contextTop; | ||
_setBrushStyles: function (ctx) { | ||
ctx.strokeStyle = this.color; | ||
@@ -76,0 +76,0 @@ ctx.lineWidth = this.width; |
@@ -32,5 +32,6 @@ /** | ||
* Creates "pattern" instance property | ||
* @param {CanvasRenderingContext2D} ctx | ||
*/ | ||
getPattern: function() { | ||
return this.canvas.contextTop.createPattern(this.source || this.getPatternSrc(), 'repeat'); | ||
getPattern: function(ctx) { | ||
return ctx.createPattern(this.source || this.getPatternSrc(), 'repeat'); | ||
}, | ||
@@ -40,6 +41,7 @@ | ||
* Sets brush styles | ||
* @param {CanvasRenderingContext2D} ctx | ||
*/ | ||
_setBrushStyles: function() { | ||
this.callSuper('_setBrushStyles'); | ||
this.canvas.contextTop.strokeStyle = this.getPattern(); | ||
_setBrushStyles: function(ctx) { | ||
this.callSuper('_setBrushStyles', ctx); | ||
ctx.strokeStyle = this.getPattern(ctx); | ||
}, | ||
@@ -46,0 +48,0 @@ |
@@ -17,2 +17,18 @@ (function() { | ||
/** | ||
* Draws a straight line between last recorded point to current pointer | ||
* Used for `shift` functionality | ||
* | ||
* @type boolean | ||
* @default false | ||
*/ | ||
drawStraightLine: false, | ||
/** | ||
* The event modifier key that makes the brush draw a straight line. | ||
* If `null` or 'none' or any other string that is not a modifier key the feature is disabled. | ||
* @type {'altKey' | 'shiftKey' | 'ctrlKey' | 'none' | undefined | null} | ||
*/ | ||
straightLineKey: 'shiftKey', | ||
/** | ||
* Constructor | ||
@@ -27,2 +43,6 @@ * @param {fabric.Canvas} canvas | ||
needsFullRender: function () { | ||
return this.callSuper('needsFullRender') || this._hasStraightLine; | ||
}, | ||
/** | ||
@@ -46,2 +66,3 @@ * Invoked inside on mouse down and mouse move | ||
} | ||
this.drawStraightLine = options.e[this.straightLineKey]; | ||
this._prepareForDrawing(pointer); | ||
@@ -62,2 +83,3 @@ // capture coordinates immediately | ||
} | ||
this.drawStraightLine = options.e[this.straightLineKey]; | ||
if (this.limitedToCanvasSize === true && this._isOutSideCanvas(pointer)) { | ||
@@ -95,2 +117,3 @@ return; | ||
} | ||
this.drawStraightLine = false; | ||
this.oldEnd = undefined; | ||
@@ -122,2 +145,6 @@ this._finalizeAndAddPath(); | ||
} | ||
if (this.drawStraightLine && this._points.length > 1) { | ||
this._hasStraightLine = true; | ||
this._points.pop(); | ||
} | ||
this._points.push(point); | ||
@@ -133,4 +160,5 @@ return true; | ||
this._points = []; | ||
this._setBrushStyles(); | ||
this._setBrushStyles(this.canvas.contextTop); | ||
this._setShadow(); | ||
this._hasStraightLine = false; | ||
}, | ||
@@ -150,8 +178,9 @@ | ||
* @private | ||
* @param {CanvasRenderingContext2D} [ctx] | ||
*/ | ||
_render: function() { | ||
var ctx = this.canvas.contextTop, i, len, | ||
_render: function(ctx) { | ||
var i, len, | ||
p1 = this._points[0], | ||
p2 = this._points[1]; | ||
ctx = ctx || this.canvas.contextTop; | ||
this._saveAndTransform(ctx); | ||
@@ -158,0 +187,0 @@ ctx.beginPath(); |
@@ -40,2 +40,3 @@ (function() { | ||
* @fires dragleave | ||
* @fires drop:before before drop event. same native event. This is added to handle edge cases | ||
* @fires drop | ||
@@ -45,7 +46,2 @@ * @fires after:render at the end of the render process, receives the context in the callback | ||
* | ||
* the following events are deprecated: | ||
* @fires object:rotated at the end of a rotation transform | ||
* @fires object:scaled at the end of a scale transform | ||
* @fires object:moved at the end of translation transform | ||
* @fires object:skewed at the end of a skew transform | ||
*/ | ||
@@ -236,9 +232,2 @@ fabric.Canvas = fabric.util.createClass(fabric.StaticCanvas, /** @lends fabric.Canvas.prototype */ { | ||
/** | ||
* Cursor value used for rotation point | ||
* @type String | ||
* @default | ||
*/ | ||
rotationCursor: 'crosshair', | ||
/** | ||
* Cursor value used for disabled elements ( corners with disabled action ) | ||
@@ -349,2 +338,9 @@ * @type String | ||
/** | ||
* When the option is enabled, PointerEvent is used instead of MouseEvent. | ||
* @type Boolean | ||
* @default | ||
*/ | ||
enablePointerEvents: false, | ||
/** | ||
* Keep track of the hovered target | ||
@@ -424,2 +420,3 @@ * @type fabric.Object | ||
this.renderTopLayer(this.contextTop); | ||
this.hasLostContext = false; | ||
} | ||
@@ -1094,6 +1091,2 @@ var canvasToDrawOn = this.contextContainer; | ||
deselected: removed, | ||
// added for backward compatibility | ||
// deprecated | ||
updated: added[0] || removed[0], | ||
target: this._activeObject, | ||
}); | ||
@@ -1105,3 +1098,2 @@ } | ||
selected: added, | ||
target: this._activeObject, | ||
}); | ||
@@ -1108,0 +1100,0 @@ } |
@@ -16,4 +16,3 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { | ||
* @param {Function} [callbacks.onChange] Invoked on every step of animation | ||
* @return {fabric.Canvas} thisArg | ||
* @chainable | ||
* @return {fabric.AnimationContext} context | ||
*/ | ||
@@ -28,3 +27,4 @@ fxCenterObjectH: function (object, callbacks) { | ||
fabric.util.animate({ | ||
return fabric.util.animate({ | ||
target: this, | ||
startValue: object.left, | ||
@@ -43,4 +43,2 @@ endValue: this.getCenter().left, | ||
}); | ||
return this; | ||
}, | ||
@@ -54,4 +52,3 @@ | ||
* @param {Function} [callbacks.onChange] Invoked on every step of animation | ||
* @return {fabric.Canvas} thisArg | ||
* @chainable | ||
* @return {fabric.AnimationContext} context | ||
*/ | ||
@@ -66,3 +63,4 @@ fxCenterObjectV: function (object, callbacks) { | ||
fabric.util.animate({ | ||
return fabric.util.animate({ | ||
target: this, | ||
startValue: object.top, | ||
@@ -81,4 +79,2 @@ endValue: this.getCenter().top, | ||
}); | ||
return this; | ||
}, | ||
@@ -92,4 +88,3 @@ | ||
* @param {Function} [callbacks.onChange] Invoked on every step of animation | ||
* @return {fabric.Canvas} thisArg | ||
* @chainable | ||
* @return {fabric.AnimationContext} context | ||
*/ | ||
@@ -104,3 +99,4 @@ fxRemove: function (object, callbacks) { | ||
fabric.util.animate({ | ||
return fabric.util.animate({ | ||
target: this, | ||
startValue: object.opacity, | ||
@@ -119,4 +115,2 @@ endValue: 0, | ||
}); | ||
return this; | ||
} | ||
@@ -132,3 +126,3 @@ }); | ||
* @tutorial {@link http://fabricjs.com/fabric-intro-part-2#animation} | ||
* @chainable | ||
* @return {fabric.AnimationContext | fabric.AnimationContext[]} animation context (or an array if passed multiple properties) | ||
* | ||
@@ -146,5 +140,5 @@ * As object — multiple properties | ||
*/ | ||
animate: function() { | ||
animate: function () { | ||
if (arguments[0] && typeof arguments[0] === 'object') { | ||
var propsToAnimate = [], prop, skipCallbacks; | ||
var propsToAnimate = [], prop, skipCallbacks, out = []; | ||
for (prop in arguments[0]) { | ||
@@ -156,9 +150,9 @@ propsToAnimate.push(prop); | ||
skipCallbacks = i !== len - 1; | ||
this._animate(prop, arguments[0][prop], arguments[1], skipCallbacks); | ||
out.push(this._animate(prop, arguments[0][prop], arguments[1], skipCallbacks)); | ||
} | ||
return out; | ||
} | ||
else { | ||
this._animate.apply(this, arguments); | ||
return this._animate.apply(this, arguments); | ||
} | ||
return this; | ||
}, | ||
@@ -211,2 +205,3 @@ | ||
var _options = { | ||
target: this, | ||
startValue: options.from, | ||
@@ -213,0 +208,0 @@ endValue: to, |
@@ -109,3 +109,3 @@ (function() { | ||
this._onDragLeave = this._simpleEventHandler.bind(this, 'dragleave'); | ||
this._onDrop = this._simpleEventHandler.bind(this, 'drop'); | ||
this._onDrop = this._onDrop.bind(this); | ||
this.eventsBound = true; | ||
@@ -223,2 +223,14 @@ }, | ||
/** | ||
* `drop:before` is a an event that allow you to schedule logic | ||
* before the `drop` event. Prefer `drop` event always, but if you need | ||
* to run some drop-disabling logic on an event, since there is no way | ||
* to handle event handlers ordering, use `drop:before` | ||
* @param {Event} e | ||
*/ | ||
_onDrop: function (e) { | ||
this._simpleEventHandler('drop:before', e); | ||
return this._simpleEventHandler('drop', e); | ||
}, | ||
/** | ||
* @private | ||
@@ -455,3 +467,8 @@ * @param {Event} e Event object fired on mousedown | ||
} | ||
var corner, pointer; | ||
if (target) { | ||
corner = target._findTargetCorner( | ||
this.getPointer(e, true), | ||
fabric.util.isTouchEvent(e) | ||
); | ||
if (target.selectable && target !== this._activeObject && target.activeOn === 'up') { | ||
@@ -462,10 +479,6 @@ this.setActiveObject(target, e); | ||
else { | ||
var corner = target._findTargetCorner( | ||
this.getPointer(e, true), | ||
fabric.util.isTouchEvent(e) | ||
); | ||
var control = target.controls[corner], | ||
mouseUpHandler = control && control.getMouseUpHandler(e, target, control); | ||
if (mouseUpHandler) { | ||
var pointer = this.getPointer(e); | ||
pointer = this.getPointer(e); | ||
mouseUpHandler(e, transform, pointer.x, pointer.y); | ||
@@ -476,2 +489,10 @@ } | ||
} | ||
// if we are ending up a transform on a different control or a new object | ||
// fire the original mouse up from the corner that started the transform | ||
if (transform && (transform.target !== target || transform.corner !== corner)) { | ||
var originalControl = transform.target && transform.target.controls[transform.corner], | ||
originalMouseUpHandler = originalControl && originalControl.getMouseUpHandler(e, target, control); | ||
pointer = pointer || this.getPointer(e); | ||
originalMouseUpHandler && originalMouseUpHandler(e, transform, pointer.x, pointer.y); | ||
} | ||
this._setCursorFromEvent(e, target); | ||
@@ -558,3 +579,2 @@ this._handleEvent(e, 'up', LEFT_CLICK, isClick); | ||
target = transform.target, | ||
eventName, | ||
options = { | ||
@@ -574,8 +594,2 @@ e: e, | ||
if (transform.actionPerformed || (this.stateful && target.hasStateChanged())) { | ||
if (transform.actionPerformed) { | ||
// this is not friendly to the new control api. | ||
// is deprecated. | ||
eventName = this._addEventOptions(options, transform); | ||
this._fire(eventName, options); | ||
} | ||
this._fire('modified', options); | ||
@@ -586,46 +600,3 @@ } | ||
/** | ||
* Mutate option object in order to add by property and give back the event name. | ||
* @private | ||
* @deprecated since 4.2.0 | ||
* @param {Object} options to mutate | ||
* @param {Object} transform to inspect action from | ||
*/ | ||
_addEventOptions: function(options, transform) { | ||
// we can probably add more details at low cost | ||
// scale change, rotation changes, translation changes | ||
var eventName, by; | ||
switch (transform.action) { | ||
case 'scaleX': | ||
eventName = 'scaled'; | ||
by = 'x'; | ||
break; | ||
case 'scaleY': | ||
eventName = 'scaled'; | ||
by = 'y'; | ||
break; | ||
case 'skewX': | ||
eventName = 'skewed'; | ||
by = 'x'; | ||
break; | ||
case 'skewY': | ||
eventName = 'skewed'; | ||
by = 'y'; | ||
break; | ||
case 'scale': | ||
eventName = 'scaled'; | ||
by = 'equally'; | ||
break; | ||
case 'rotate': | ||
eventName = 'rotated'; | ||
break; | ||
case 'drag': | ||
eventName = 'moved'; | ||
break; | ||
} | ||
options.by = by; | ||
return eventName; | ||
}, | ||
/** | ||
* @private | ||
* @param {Event} e Event object fired on mousedown | ||
@@ -632,0 +603,0 @@ */ |
(function () { | ||
/** ERASER_START */ | ||
var __set = fabric.Object.prototype._set; | ||
var _render = fabric.Object.prototype.render; | ||
/** | ||
* add `eraser` to enlivened props | ||
*/ | ||
fabric.Object.ENLIVEN_PROPS.push('eraser'); | ||
var __drawClipPath = fabric.Object.prototype._drawClipPath; | ||
var _needsItsOwnCache = fabric.Object.prototype.needsItsOwnCache; | ||
var _toObject = fabric.Object.prototype.toObject; | ||
var _getSvgCommons = fabric.Object.prototype.getSvgCommons; | ||
var __createBaseClipPathSVGMarkup = fabric.Object.prototype._createBaseClipPathSVGMarkup; | ||
var __createBaseSVGMarkup = fabric.Object.prototype._createBaseSVGMarkup; | ||
@@ -24,74 +32,35 @@ /** | ||
/** | ||
* | ||
* @returns {fabric.Group | undefined} | ||
* @tutorial {@link http://fabricjs.com/erasing#eraser} | ||
* @type fabric.Eraser | ||
*/ | ||
getEraser: function () { | ||
return this.clipPath && this.clipPath.eraser ? this.clipPath : undefined; | ||
}, | ||
eraser: undefined, | ||
/** | ||
* Get the object's actual clip path regardless of clipping done by erasing | ||
* @returns {fabric.Object | undefined} | ||
* @override | ||
* @returns Boolean | ||
*/ | ||
getClipPath: function () { | ||
var eraser = this.getEraser(); | ||
return eraser ? eraser._objects[0].clipPath : this.clipPath; | ||
needsItsOwnCache: function () { | ||
return _needsItsOwnCache.call(this) || !!this.eraser; | ||
}, | ||
/** | ||
* Set the object's actual clip path regardless of clipping done by erasing | ||
* @param {fabric.Object} [clipPath] | ||
*/ | ||
setClipPath: function (clipPath) { | ||
var eraser = this.getEraser(); | ||
var target = eraser ? eraser._objects[0] : this; | ||
target.set('clipPath', clipPath); | ||
this.set('dirty', true); | ||
}, | ||
/** | ||
* Updates eraser size and position to match object's size | ||
* draw eraser above clip path | ||
* @override | ||
* @private | ||
* @param {Object} [dimensions] uses object's dimensions if unspecified | ||
* @param {number} [dimensions.width] | ||
* @param {number} [dimensions.height] | ||
* @param {boolean} [center=false] postion the eraser relative to object's center or it's top left corner | ||
* @param {CanvasRenderingContext2D} ctx | ||
* @param {fabric.Object} clipPath | ||
*/ | ||
_updateEraserDimensions: function (dimensions, center) { | ||
var eraser = this.getEraser(); | ||
if (eraser) { | ||
var rect = eraser._objects[0]; | ||
var eraserSize = { width: rect.width, height: rect.height }; | ||
_drawClipPath: function (ctx, clipPath) { | ||
__drawClipPath.call(this, ctx, clipPath); | ||
if (this.eraser) { | ||
// update eraser size to match instance | ||
var size = this._getNonTransformedDimensions(); | ||
var newSize = fabric.util.object.extend({ width: size.x, height: size.y }, dimensions); | ||
if (eraserSize.width === newSize.width && eraserSize.height === newSize.height) { | ||
return; | ||
} | ||
var offset = new fabric.Point((eraserSize.width - newSize.width) / 2, (eraserSize.height - newSize.height) / 2); | ||
eraser.set(newSize); | ||
eraser.setPositionByOrigin(new fabric.Point(0, 0), 'center', 'center'); | ||
rect.set(newSize); | ||
eraser.set('dirty', true); | ||
if (!center) { | ||
eraser.getObjects('path').forEach(function (path) { | ||
path.setPositionByOrigin(path.getCenterPoint().add(offset), 'center', 'center'); | ||
}); | ||
} | ||
this.setCoords(); | ||
this.eraser.isType('eraser') && this.eraser.set({ | ||
width: size.x, | ||
height: size.y | ||
}); | ||
__drawClipPath.call(this, ctx, this.eraser); | ||
} | ||
}, | ||
_set: function (key, value) { | ||
__set.call(this, key, value); | ||
if (key === 'width' || key === 'height') { | ||
this._updateEraserDimensions(); | ||
} | ||
return this; | ||
}, | ||
render: function (ctx) { | ||
this._updateEraserDimensions(); | ||
_render.call(this, ctx); | ||
}, | ||
/** | ||
@@ -102,69 +71,47 @@ * Returns an object representation of an instance | ||
*/ | ||
toObject: function (additionalProperties) { | ||
return _toObject.call(this, ['erasable'].concat(additionalProperties)); | ||
toObject: function (propertiesToInclude) { | ||
var object = _toObject.call(this, ['erasable'].concat(propertiesToInclude)); | ||
if (this.eraser && !this.eraser.excludeFromExport) { | ||
object.eraser = this.eraser.toObject(propertiesToInclude); | ||
} | ||
return object; | ||
}, | ||
/* _TO_SVG_START_ */ | ||
/** | ||
* use <mask> to achieve erasing for svg | ||
* credit: https://travishorn.com/removing-parts-of-shapes-in-svg-b539a89e5649 | ||
* @param {Function} reviver | ||
* @returns {string} markup | ||
* Returns id attribute for svg output | ||
* @override | ||
* @return {String} | ||
*/ | ||
eraserToSVG: function (options) { | ||
var eraser = this.getEraser(); | ||
if (eraser) { | ||
var fill = eraser._objects[0].fill; | ||
eraser._objects[0].fill = 'white'; | ||
eraser.clipPathId = 'CLIPPATH_' + fabric.Object.__uid++; | ||
var commons = [ | ||
'id="' + eraser.clipPathId + '"', | ||
/*options.additionalTransform ? ' transform="' + options.additionalTransform + '" ' : ''*/ | ||
].join(' '); | ||
var objectMarkup = ['<defs>', '<mask ' + commons + ' >', eraser.toSVG(options.reviver), '</mask>', '</defs>']; | ||
eraser._objects[0].fill = fill; | ||
return objectMarkup.join('\n'); | ||
} | ||
return ''; | ||
getSvgCommons: function () { | ||
return _getSvgCommons.call(this) + (this.eraser ? 'mask="url(#' + this.eraser.clipPathId + ')" ' : ''); | ||
}, | ||
/** | ||
* use <mask> to achieve erasing for svg, override <clipPath> | ||
* @param {string[]} objectMarkup | ||
* @param {Object} options | ||
* create svg markup for eraser | ||
* use <mask> to achieve erasing for svg, credit: https://travishorn.com/removing-parts-of-shapes-in-svg-b539a89e5649 | ||
* must be called before object markup creation as it relies on the `clipPathId` property of the mask | ||
* @param {Function} [reviver] | ||
* @returns | ||
*/ | ||
_createBaseSVGMarkup: function (objectMarkup, options) { | ||
var eraser = this.getEraser(); | ||
if (eraser) { | ||
var eraserMarkup = this.eraserToSVG(options); | ||
this.clipPath = null; | ||
var markup = __createBaseSVGMarkup.call(this, objectMarkup, options); | ||
this.clipPath = eraser; | ||
_createEraserSVGMarkup: function (reviver) { | ||
if (this.eraser) { | ||
this.eraser.clipPathId = 'MASK_' + fabric.Object.__uid++; | ||
return [ | ||
eraserMarkup, | ||
markup.replace('>', 'mask="url(#' + eraser.clipPathId + ')" >') | ||
].join('\n'); | ||
'<mask id="', this.eraser.clipPathId, '" >', | ||
this.eraser.toSVG(reviver), | ||
'</mask>', '\n' | ||
].join(''); | ||
} | ||
else { | ||
return __createBaseSVGMarkup.call(this, objectMarkup, options); | ||
} | ||
} | ||
}); | ||
return ''; | ||
}, | ||
var __restoreObjectsState = fabric.Group.prototype._restoreObjectsState; | ||
var _groupToObject = fabric.Group.prototype.toObject; | ||
var __getBounds = fabric.Group.prototype._getBounds; | ||
fabric.util.object.extend(fabric.Group.prototype, { | ||
/** | ||
* If group is an eraser then dimensions should not change when paths are added or removed and should remain the size of the base rect | ||
* @private | ||
*/ | ||
_getBounds: function (aX, aY, onlyWidthHeight) { | ||
if (this.eraser) { | ||
this.width = this._objects[0].width; | ||
this.height = this._objects[0].height; | ||
return; | ||
} | ||
__getBounds.call(this, aX, aY, onlyWidthHeight); | ||
_createBaseClipPathSVGMarkup: function (objectMarkup, options) { | ||
return [ | ||
this._createEraserSVGMarkup(options && options.reviver), | ||
__createBaseClipPathSVGMarkup.call(this, objectMarkup, options) | ||
].join(''); | ||
}, | ||
@@ -174,2 +121,16 @@ | ||
* @private | ||
*/ | ||
_createBaseSVGMarkup: function (objectMarkup, options) { | ||
return [ | ||
this._createEraserSVGMarkup(options && options.reviver), | ||
__createBaseSVGMarkup.call(this, objectMarkup, options) | ||
].join(''); | ||
} | ||
/* _TO_SVG_END_ */ | ||
}); | ||
var __restoreObjectsState = fabric.Group.prototype._restoreObjectsState; | ||
fabric.util.object.extend(fabric.Group.prototype, { | ||
/** | ||
* @private | ||
* @param {fabric.Path} path | ||
@@ -192,8 +153,8 @@ */ | ||
applyEraserToObjects: function () { | ||
var _this = this; | ||
if (this.getEraser()) { | ||
var _this = this, eraser = this.eraser; | ||
if (eraser) { | ||
delete this.eraser; | ||
var transform = _this.calcTransformMatrix(); | ||
_this.getEraser().clone(function (eraser) { | ||
var clipPath = eraser._objects[0].clipPath; | ||
_this.clipPath = clipPath ? clipPath : undefined; | ||
eraser.clone(function (eraser) { | ||
var clipPath = _this.clipPath; | ||
eraser.getObjects('path') | ||
@@ -209,3 +170,3 @@ .forEach(function (path) { | ||
clipPath.clone(function (_clipPath) { | ||
fabric.EraserBrush.prototype.applyClipPathToPath.call( | ||
var eraserPath = fabric.EraserBrush.prototype.applyClipPathToPath.call( | ||
fabric.EraserBrush.prototype, | ||
@@ -216,4 +177,4 @@ path, | ||
); | ||
_this._addEraserPathToObjects(path); | ||
}); | ||
_this._addEraserPathToObjects(eraserPath); | ||
}, ['absolutePositioned', 'inverted']); | ||
} | ||
@@ -235,15 +196,96 @@ else { | ||
return __restoreObjectsState.call(this); | ||
} | ||
}); | ||
/** | ||
* An object's Eraser | ||
* @private | ||
* @class fabric.Eraser | ||
* @extends fabric.Group | ||
* @memberof fabric | ||
*/ | ||
fabric.Eraser = fabric.util.createClass(fabric.Group, { | ||
/** | ||
* @readonly | ||
* @static | ||
*/ | ||
type: 'eraser', | ||
/** | ||
* @default | ||
*/ | ||
originX: 'center', | ||
/** | ||
* @default | ||
*/ | ||
originY: 'center', | ||
drawObject: function (ctx) { | ||
ctx.save(); | ||
ctx.fillStyle = 'black'; | ||
ctx.fillRect(-this.width / 2, -this.height / 2, this.width, this.height); | ||
ctx.restore(); | ||
this.callSuper('drawObject', ctx); | ||
}, | ||
/** | ||
* Returns an object representation of an instance | ||
* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output | ||
* @return {Object} Object representation of an instance | ||
* eraser should retain size | ||
* dimensions should not change when paths are added or removed | ||
* handled by {@link fabric.Object#_drawClipPath} | ||
* @override | ||
* @private | ||
*/ | ||
toObject: function (additionalProperties) { | ||
return _groupToObject.call(this, ['eraser'].concat(additionalProperties)); | ||
} | ||
_getBounds: function () { | ||
// noop | ||
}, | ||
/* _TO_SVG_START_ */ | ||
/** | ||
* Returns svg representation of an instance | ||
* use <mask> to achieve erasing for svg, credit: https://travishorn.com/removing-parts-of-shapes-in-svg-b539a89e5649 | ||
* for masking we need to add a white rect before all paths | ||
* | ||
* @param {Function} [reviver] Method for further parsing of svg representation. | ||
* @return {String} svg representation of an instance | ||
*/ | ||
_toSVG: function (reviver) { | ||
var svgString = ['<g ', 'COMMON_PARTS', ' >\n']; | ||
var x = -this.width / 2, y = -this.height / 2; | ||
var rectSvg = [ | ||
'<rect ', 'fill="white" ', | ||
'x="', x, '" y="', y, | ||
'" width="', this.width, '" height="', this.height, | ||
'" />\n' | ||
].join(''); | ||
svgString.push('\t\t', rectSvg); | ||
for (var i = 0, len = this._objects.length; i < len; i++) { | ||
svgString.push('\t\t', this._objects[i].toSVG(reviver)); | ||
} | ||
svgString.push('</g>\n'); | ||
return svgString; | ||
}, | ||
/* _TO_SVG_END_ */ | ||
}); | ||
/** | ||
* Returns {@link fabric.Eraser} instance from an object representation | ||
* @static | ||
* @memberOf fabric.Eraser | ||
* @param {Object} object Object to create an Eraser from | ||
* @param {Function} [callback] Callback to invoke when an eraser instance is created | ||
*/ | ||
fabric.Eraser.fromObject = function (object, callback) { | ||
var objects = object.objects; | ||
fabric.util.enlivenObjects(objects, function (enlivenedObjects) { | ||
var options = fabric.util.object.clone(object, true); | ||
delete options.objects; | ||
fabric.util.enlivenObjectEnlivables(object, options, function () { | ||
callback && callback(new fabric.Eraser(enlivenedObjects, options, true)); | ||
}); | ||
}); | ||
}; | ||
var __renderOverlay = fabric.Canvas.prototype._renderOverlay; | ||
/** | ||
* @fires erasing:start | ||
@@ -267,23 +309,11 @@ * @fires erasing:end | ||
/** | ||
* While erasing, the brush is in charge of rendering the canvas | ||
* It uses both layers to achieve diserd erasing effect | ||
* | ||
* @returns fabric.Canvas | ||
* While erasing the brush clips out the erasing path from canvas | ||
* so we need to render it on top of canvas every render | ||
* @param {CanvasRenderingContext2D} ctx | ||
*/ | ||
renderAll: function () { | ||
if (this.contextTopDirty && !this._groupSelector && !this.isDrawingMode) { | ||
this.clearContext(this.contextTop); | ||
this.contextTopDirty = false; | ||
} | ||
// while erasing the brush is in charge of rendering the canvas so we return | ||
if (this.isErasing()) { | ||
_renderOverlay: function (ctx) { | ||
__renderOverlay.call(this, ctx); | ||
if (this.isErasing() && !this.freeDrawingBrush.inverted) { | ||
this.freeDrawingBrush._render(); | ||
return; | ||
} | ||
if (this.hasLostContext) { | ||
this.renderTopLayer(this.contextTop); | ||
} | ||
var canvasToDrawOn = this.contextContainer; | ||
this.renderCanvas(canvasToDrawOn, this._chooseObjectsToRender()); | ||
return this; | ||
} | ||
@@ -295,12 +325,17 @@ }); | ||
* Supports selective erasing meaning that only erasable objects are affected by the eraser brush. | ||
* In order to support selective erasing all non erasable objects are rendered on the main/bottom ctx | ||
* while the entire canvas is rendered on the top ctx. | ||
* Canvas background/overlay images are handled as well. | ||
* When erasing occurs, the path clips the top ctx and reveals the bottom ctx. | ||
* This achieves the desired effect of seeming to erase only erasable objects. | ||
* After erasing is done the created path is added to all intersected objects' `clipPath` property. | ||
* Supports **inverted** erasing meaning that the brush can "undo" erasing. | ||
* | ||
* In order to support selective erasing, the brush clips the entire canvas | ||
* and then draws all non-erasable objects over the erased path using a pattern brush so to speak (masking). | ||
* If brush is **inverted** there is no need to clip canvas. The brush draws all erasable objects without their eraser. | ||
* This achieves the desired effect of seeming to erase or unerase only erasable objects. | ||
* After erasing is done the created path is added to all intersected objects' `eraser` property. | ||
* | ||
* In order to update the EraserBrush call `preparePattern`. | ||
* It may come in handy when canvas changes during erasing (i.e animations) and you want the eraser to reflect the changes. | ||
* | ||
* @tutorial {@link http://fabricjs.com/erasing} | ||
* @class fabric.EraserBrush | ||
* @extends fabric.PencilBrush | ||
* @memberof fabric | ||
*/ | ||
@@ -313,8 +348,5 @@ fabric.EraserBrush = fabric.util.createClass( | ||
/** | ||
* Indicates that the ctx is ready and rendering can begin. | ||
* Used to prevent a race condition caused by {@link fabric.EraserBrush#onMouseMove} firing before {@link fabric.EraserBrush#onMouseDown} has completed | ||
* | ||
* @private | ||
* When set to `true` the brush will create a visual effect of undoing erasing | ||
*/ | ||
_ready: false, | ||
inverted: false, | ||
@@ -324,39 +356,5 @@ /** | ||
*/ | ||
_drawOverlayOnTop: false, | ||
/** | ||
* @private | ||
*/ | ||
_isErasing: false, | ||
initialize: function (canvas) { | ||
this.callSuper('initialize', canvas); | ||
this._renderBound = this._render.bind(this); | ||
this.render = this.render.bind(this); | ||
}, | ||
/** | ||
* Used to hide a drawable from the rendering process | ||
* @param {fabric.Object} object | ||
*/ | ||
hideObject: function (object) { | ||
if (object) { | ||
object._originalOpacity = object.opacity; | ||
object.set({ opacity: 0 }); | ||
} | ||
}, | ||
/** | ||
* Restores hiding an object | ||
* {@link fabric.EraserBrush#hideObject} | ||
* @param {fabric.Object} object | ||
*/ | ||
restoreObjectVisibility: function (object) { | ||
if (object && object._originalOpacity) { | ||
object.set({ opacity: object._originalOpacity }); | ||
object._originalOpacity = undefined; | ||
} | ||
}, | ||
/** | ||
* | ||
@@ -372,191 +370,126 @@ * @private | ||
/** | ||
* Drawing Logic For background drawables: (`backgroundImage`) | ||
* 1. if erasable = true: | ||
* we need to hide the drawable on the bottom ctx so when the brush is erasing it will clip the top ctx and reveal white space underneath | ||
* 2. if erasable = false: | ||
* we need to draw the drawable only on the bottom ctx so the brush won't affect it | ||
* @param {'bottom' | 'top' | 'overlay'} layer | ||
*/ | ||
prepareCanvasBackgroundForLayer: function (layer) { | ||
if (layer === 'overlay') { | ||
return; | ||
} | ||
var canvas = this.canvas; | ||
var image = canvas.backgroundImage; | ||
var erasablesOnLayer = layer === 'top'; | ||
if (image && this._isErasable(image) === !erasablesOnLayer) { | ||
this.hideObject(image); | ||
} | ||
}, | ||
/** | ||
* Drawing Logic For overlay drawables (`overlayImage`) | ||
* We must draw on top ctx to be on top of visible canvas | ||
* 1. if erasable = true: | ||
* we need to draw the drawable on the top ctx as a normal object | ||
* 2. if erasable = false: | ||
* we need to draw the drawable on top of the brush, | ||
* this means we need to repaint for every stroke | ||
* | ||
* @param {'bottom' | 'top' | 'overlay'} layer | ||
* @returns boolean render overlay above brush | ||
*/ | ||
prepareCanvasOverlayForLayer: function (layer) { | ||
var canvas = this.canvas; | ||
var image = canvas.overlayImage; | ||
var hasOverlayColor = !!canvas.overlayColor; | ||
if (canvas.overlayColor && layer !== 'overlay') { | ||
this.__overlayColor = canvas.overlayColor; | ||
delete canvas.overlayColor; | ||
} | ||
if (layer === 'bottom') { | ||
this.hideObject(image); | ||
return false; | ||
}; | ||
var erasablesOnLayer = layer === 'top'; | ||
var renderOverlayOnTop = (image && !this._isErasable(image)) || hasOverlayColor; | ||
if (image && this._isErasable(image) === !erasablesOnLayer) { | ||
this.hideObject(image); | ||
} | ||
return renderOverlayOnTop; | ||
}, | ||
/** | ||
* @private | ||
*/ | ||
restoreCanvasDrawables: function () { | ||
var canvas = this.canvas; | ||
if (this.__overlayColor) { | ||
canvas.overlayColor = this.__overlayColor; | ||
delete this.__overlayColor; | ||
} | ||
this.restoreObjectVisibility(canvas.backgroundImage); | ||
this.restoreObjectVisibility(canvas.overlayImage); | ||
}, | ||
/** | ||
* @private | ||
* This is designed to support erasing a group with both erasable and non-erasable objects. | ||
* This is designed to support erasing a collection with both erasable and non-erasable objects. | ||
* Iterates over collections to allow nested selective erasing. | ||
* Used by {@link fabric.EraserBrush#prepareCanvasObjectsForLayer} | ||
* to prepare the bottom layer by hiding erasable nested objects | ||
* Prepares the pattern brush that will draw on the top context to achieve the desired visual effect. | ||
* If brush is **NOT** inverted render all non-erasable objects. | ||
* If brush is inverted render all erasable objects that have been erased with their clip path inverted. | ||
* This will render the erased parts as if they were not erased. | ||
* | ||
* @param {fabric.Collection} collection | ||
* @param {CanvasRenderingContext2D} ctx | ||
* @param {{ visibility: fabric.Object[], eraser: fabric.Object[], collection: fabric.Object[] }} restorationContext | ||
*/ | ||
prepareCollectionTraversal: function (collection) { | ||
var _this = this; | ||
_prepareCollectionTraversal: function (collection, ctx, restorationContext) { | ||
collection.forEachObject(function (obj) { | ||
if (obj.forEachObject && obj.erasable === 'deep') { | ||
_this.prepareCollectionTraversal(obj); | ||
// traverse | ||
this._prepareCollectionTraversal(obj, ctx, restorationContext); | ||
} | ||
else if (obj.erasable) { | ||
_this.hideObject(obj); | ||
else if (!this.inverted && obj.erasable && obj.visible) { | ||
// render only non-erasable objects | ||
obj.visible = false; | ||
collection.dirty = true; | ||
restorationContext.visibility.push(obj); | ||
restorationContext.collection.push(collection); | ||
} | ||
}); | ||
else if (this.inverted && obj.visible) { | ||
// render only erasable objects that were erased | ||
if (obj.erasable && obj.eraser) { | ||
obj.eraser.inverted = true; | ||
obj.dirty = true; | ||
collection.dirty = true; | ||
restorationContext.eraser.push(obj); | ||
restorationContext.collection.push(collection); | ||
} | ||
else { | ||
obj.visible = false; | ||
collection.dirty = true; | ||
restorationContext.visibility.push(obj); | ||
restorationContext.collection.push(collection); | ||
} | ||
} | ||
}, this); | ||
}, | ||
/** | ||
* Prepare the pattern for the erasing brush | ||
* This pattern will be drawn on the top context, achieving a visual effect of erasing only erasable objects | ||
* @todo decide how overlay color should behave when `inverted === true`, currently draws over it which is undesirable | ||
* @private | ||
* Used by {@link fabric.EraserBrush#prepareCanvasObjectsForLayer} | ||
* to reverse the action of {@link fabric.EraserBrush#prepareCollectionTraversal} | ||
* | ||
* @param {fabric.Collection} collection | ||
*/ | ||
restoreCollectionTraversal: function (collection) { | ||
var _this = this; | ||
collection.forEachObject(function (obj) { | ||
if (obj.forEachObject && obj.erasable === 'deep') { | ||
_this.restoreCollectionTraversal(obj); | ||
} | ||
else { | ||
_this.restoreObjectVisibility(obj); | ||
} | ||
preparePattern: function () { | ||
if (!this._patternCanvas) { | ||
this._patternCanvas = fabric.util.createCanvasElement(); | ||
} | ||
var canvas = this._patternCanvas; | ||
canvas.width = this.canvas.width; | ||
canvas.height = this.canvas.height; | ||
var patternCtx = canvas.getContext('2d'); | ||
if (this.canvas._isRetinaScaling()) { | ||
var retinaScaling = this.canvas.getRetinaScaling(); | ||
this.canvas.__initRetinaScaling(retinaScaling, canvas, patternCtx); | ||
} | ||
var backgroundImage = this.canvas.backgroundImage, | ||
bgErasable = backgroundImage && this._isErasable(backgroundImage), | ||
overlayImage = this.canvas.overlayImage, | ||
overlayErasable = overlayImage && this._isErasable(overlayImage); | ||
if (!this.inverted && ((backgroundImage && !bgErasable) || !!this.canvas.backgroundColor)) { | ||
if (bgErasable) { this.canvas.backgroundImage = undefined; } | ||
this.canvas._renderBackground(patternCtx); | ||
if (bgErasable) { this.canvas.backgroundImage = backgroundImage; } | ||
} | ||
else if (this.inverted && (backgroundImage && bgErasable)) { | ||
var color = this.canvas.backgroundColor; | ||
this.canvas.backgroundColor = undefined; | ||
this.canvas._renderBackground(patternCtx); | ||
this.canvas.backgroundColor = color; | ||
} | ||
patternCtx.save(); | ||
patternCtx.transform.apply(patternCtx, this.canvas.viewportTransform); | ||
var restorationContext = { visibility: [], eraser: [], collection: [] }; | ||
this._prepareCollectionTraversal(this.canvas, patternCtx, restorationContext); | ||
this.canvas._renderObjects(patternCtx, this.canvas._objects); | ||
restorationContext.visibility.forEach(function (obj) { obj.visible = true; }); | ||
restorationContext.eraser.forEach(function (obj) { | ||
obj.eraser.inverted = false; | ||
obj.dirty = true; | ||
}); | ||
restorationContext.collection.forEach(function (obj) { obj.dirty = true; }); | ||
patternCtx.restore(); | ||
if (!this.inverted && ((overlayImage && !overlayErasable) || !!this.canvas.overlayColor)) { | ||
if (overlayErasable) { this.canvas.overlayImage = undefined; } | ||
__renderOverlay.call(this.canvas, patternCtx); | ||
if (overlayErasable) { this.canvas.overlayImage = overlayImage; } | ||
} | ||
else if (this.inverted && (overlayImage && overlayErasable)) { | ||
var color = this.canvas.overlayColor; | ||
this.canvas.overlayColor = undefined; | ||
__renderOverlay.call(this.canvas, patternCtx); | ||
this.canvas.overlayColor = color; | ||
} | ||
}, | ||
/** | ||
* Sets brush styles | ||
* @private | ||
* This is designed to support erasing a group with both erasable and non-erasable objects. | ||
* | ||
* @param {'bottom' | 'top' | 'overlay'} layer | ||
* @param {CanvasRenderingContext2D} ctx | ||
*/ | ||
prepareCanvasObjectsForLayer: function (layer) { | ||
if (layer !== 'bottom') { return; } | ||
this.prepareCollectionTraversal(this.canvas); | ||
_setBrushStyles: function (ctx) { | ||
this.callSuper('_setBrushStyles', ctx); | ||
ctx.strokeStyle = 'black'; | ||
}, | ||
/** | ||
* @private | ||
* @param {'bottom' | 'top' | 'overlay'} layer | ||
*/ | ||
restoreCanvasObjectsFromLayer: function (layer) { | ||
if (layer !== 'bottom') { return; } | ||
this.restoreCollectionTraversal(this.canvas); | ||
}, | ||
/** | ||
* @private | ||
* @param {'bottom' | 'top' | 'overlay'} layer | ||
* @returns boolean render overlay above brush | ||
*/ | ||
prepareCanvasForLayer: function (layer) { | ||
this.prepareCanvasBackgroundForLayer(layer); | ||
this.prepareCanvasObjectsForLayer(layer); | ||
return this.prepareCanvasOverlayForLayer(layer); | ||
}, | ||
/** | ||
* @private | ||
* @param {'bottom' | 'top' | 'overlay'} layer | ||
*/ | ||
restoreCanvasFromLayer: function (layer) { | ||
this.restoreCanvasDrawables(); | ||
this.restoreCanvasObjectsFromLayer(layer); | ||
}, | ||
/** | ||
* Render all non-erasable objects on bottom layer with the exception of overlays to avoid being clipped by the brush. | ||
* Groups are rendered for nested selective erasing, non-erasable objects are visible while erasable objects are not. | ||
*/ | ||
renderBottomLayer: function () { | ||
var canvas = this.canvas; | ||
this.prepareCanvasForLayer('bottom'); | ||
canvas.renderCanvas( | ||
canvas.getContext(), | ||
canvas.getObjects().filter(function (obj) { | ||
return !obj.erasable || obj.forEachObject; | ||
}) | ||
); | ||
this.restoreCanvasFromLayer('bottom'); | ||
}, | ||
/** | ||
* 1. Render all objects on top layer, erasable and non-erasable | ||
* This is important for cases such as overlapping objects, the background object erasable and the foreground object not erasable. | ||
* 2. Render the brush | ||
*/ | ||
renderTopLayer: function () { | ||
var canvas = this.canvas; | ||
this._drawOverlayOnTop = this.prepareCanvasForLayer('top'); | ||
canvas.renderCanvas( | ||
canvas.contextTop, | ||
canvas.getObjects() | ||
); | ||
this.callSuper('_render'); | ||
this.restoreCanvasFromLayer('top'); | ||
}, | ||
/** | ||
* Render all non-erasable overlays on top of the brush so that they won't get erased | ||
*/ | ||
renderOverlay: function () { | ||
this.prepareCanvasForLayer('overlay'); | ||
var canvas = this.canvas; | ||
var ctx = canvas.contextTop; | ||
canvas._renderOverlay(ctx); | ||
this.restoreCanvasFromLayer('overlay'); | ||
}, | ||
/** | ||
* @extends @class fabric.BaseBrush | ||
* **Customiztion** | ||
* | ||
* if you need the eraser to update on each render (i.e animating during erasing) override this method by **adding** the following (performance may suffer): | ||
* @example | ||
* ``` | ||
* if(ctx === this.canvas.contextTop) { | ||
* this.preparePattern(); | ||
* } | ||
* ``` | ||
* | ||
* @override fabric.BaseBrush#_saveAndTransform | ||
* @param {CanvasRenderingContext2D} ctx | ||
@@ -566,3 +499,4 @@ */ | ||
this.callSuper('_saveAndTransform', ctx); | ||
ctx.globalCompositeOperation = 'destination-out'; | ||
this._setBrushStyles(ctx); | ||
ctx.globalCompositeOperation = ctx === this.canvas.getContext() ? 'destination-out' : 'source-over'; | ||
}, | ||
@@ -575,3 +509,3 @@ | ||
needsFullRender: function () { | ||
return this.callSuper('needsFullRender') || this._drawOverlayOnTop; | ||
return true; | ||
}, | ||
@@ -594,5 +528,6 @@ | ||
// prepare for erasing | ||
this.preparePattern(); | ||
this._isErasing = true; | ||
this.canvas.fire('erasing:start'); | ||
this._ready = true; | ||
this._render(); | ||
@@ -602,35 +537,39 @@ }, | ||
/** | ||
* Rendering is done in 4 steps: | ||
* 1. Draw all non-erasable objects on bottom ctx with the exception of overlays {@link fabric.EraserBrush#renderBottomLayer} | ||
* 2. Draw all objects on top ctx including erasable drawables {@link fabric.EraserBrush#renderTopLayer} | ||
* 3. Draw eraser {@link fabric.PencilBrush#_render} at {@link fabric.EraserBrush#renderTopLayer} | ||
* 4. Draw non-erasable overlays {@link fabric.EraserBrush#renderOverlay} | ||
* Rendering Logic: | ||
* 1. Use brush to clip canvas by rendering it on top of canvas (unnecessary if `inverted === true`) | ||
* 2. Render brush with canvas pattern on top context | ||
* | ||
* @param {fabric.Canvas} canvas | ||
*/ | ||
_render: function () { | ||
if (!this._ready) { | ||
return; | ||
var ctx; | ||
if (!this.inverted) { | ||
// clip canvas | ||
ctx = this.canvas.getContext(); | ||
this.callSuper('_render', ctx); | ||
} | ||
this.isRendering = 1; | ||
this.renderBottomLayer(); | ||
this.renderTopLayer(); | ||
this.renderOverlay(); | ||
this.isRendering = 0; | ||
// render brush and mask it with image of non erasables | ||
ctx = this.canvas.contextTop; | ||
this.canvas.clearContext(ctx); | ||
this.callSuper('_render', ctx); | ||
ctx.save(); | ||
var t = this.canvas.getRetinaScaling(), s = 1 / t; | ||
ctx.scale(s, s); | ||
ctx.globalCompositeOperation = 'source-in'; | ||
ctx.drawImage(this._patternCanvas, 0, 0); | ||
ctx.restore(); | ||
}, | ||
/** | ||
* @public | ||
* Creates fabric.Path object | ||
* @override | ||
* @private | ||
* @param {(string|number)[][]} pathData Path data | ||
* @return {fabric.Path} Path to add on canvas | ||
* @returns | ||
*/ | ||
render: function () { | ||
if (this._isErasing) { | ||
if (this.isRendering) { | ||
this.isRendering = fabric.util.requestAnimFrame(this._renderBound); | ||
} | ||
else { | ||
this._render(); | ||
} | ||
return true; | ||
} | ||
return false; | ||
createPath: function (pathData) { | ||
var path = this.callSuper('createPath', pathData); | ||
path.globalCompositeOperation = this.inverted ? 'source-over' : 'destination-out'; | ||
path.stroke = this.inverted ? 'white' : 'black'; | ||
return path; | ||
}, | ||
@@ -642,3 +581,3 @@ | ||
* Called when a group has a clip path that should be applied to the path before applying erasing on the group's objects. | ||
* @param {fabric.Path} path The eraser path | ||
* @param {fabric.Path} path The eraser path in canvas coordinate plane | ||
* @param {fabric.Object} clipPath The clipPath to apply to the path | ||
@@ -649,8 +588,13 @@ * @param {number[]} clipPathContainerTransformMatrix The transform matrix of the object that the clip path belongs to | ||
applyClipPathToPath: function (path, clipPath, clipPathContainerTransformMatrix) { | ||
var pathTransform = path.calcTransformMatrix(); | ||
var clipPathTransform = clipPath.calcTransformMatrix(); | ||
var transform = fabric.util.multiplyTransformMatrices( | ||
fabric.util.invertTransform(pathTransform), | ||
clipPathContainerTransformMatrix | ||
); | ||
var pathInvTransform = fabric.util.invertTransform(path.calcTransformMatrix()), | ||
clipPathTransform = clipPath.calcTransformMatrix(), | ||
transform = clipPath.absolutePositioned ? | ||
pathInvTransform : | ||
fabric.util.multiplyTransformMatrices( | ||
pathInvTransform, | ||
clipPathContainerTransformMatrix | ||
); | ||
// when passing down a clip path it becomes relative to the parent | ||
// so we transform it acoordingly and set `absolutePositioned` to false | ||
clipPath.absolutePositioned = false; | ||
fabric.util.applyTransformToObject( | ||
@@ -663,3 +607,7 @@ clipPath, | ||
); | ||
path.clipPath = clipPath; | ||
// We need to clip `path` with both `clipPath` and it's own clip path if existing (`path.clipPath`) | ||
// so in turn `path` erases an object only where it overlaps with all it's clip paths, regardless of how many there are. | ||
// this is done because both clip paths may have nested clip paths of their own (this method walks down a collection => this may reccur), | ||
// so we can't assign one to the other's clip path property. | ||
path.clipPath = path.clipPath ? fabric.util.mergeClipPaths(clipPath, path.clipPath) : clipPath; | ||
return path; | ||
@@ -678,3 +626,3 @@ }, | ||
var objTransform = object.calcTransformMatrix(); | ||
var clipPath = object.getClipPath(); | ||
var clipPath = object.clipPath; | ||
var _this = this; | ||
@@ -684,3 +632,3 @@ path.clone(function (_path) { | ||
callback(_this.applyClipPathToPath(_path, _clipPath, objTransform)); | ||
}); | ||
}, ['absolutePositioned', 'inverted']); | ||
}); | ||
@@ -690,4 +638,5 @@ }, | ||
/** | ||
* Adds path to existing clipPath of object | ||
* Adds path to object's eraser, walks down object's descendants if necessary | ||
* | ||
* @fires erasing:end on object | ||
* @param {fabric.Object} obj | ||
@@ -697,3 +646,2 @@ * @param {fabric.Path} path | ||
_addPathToObjectEraser: function (obj, path) { | ||
var clipObject; | ||
var _this = this; | ||
@@ -719,22 +667,10 @@ // object is collection, i.e group | ||
} | ||
if (!obj.getEraser()) { | ||
var size = obj._getNonTransformedDimensions(); | ||
var rect = new fabric.Rect({ | ||
fill: 'rgb(0,0,0)', | ||
width: size.x, | ||
height: size.y, | ||
clipPath: obj.clipPath, | ||
originX: 'center', | ||
originY: 'center' | ||
}); | ||
clipObject = new fabric.Group([rect], { | ||
eraser: true | ||
}); | ||
// prepare eraser | ||
var eraser = obj.eraser; | ||
if (!eraser) { | ||
eraser = new fabric.Eraser(); | ||
obj.eraser = eraser; | ||
} | ||
else { | ||
clipObject = obj.clipPath; | ||
} | ||
// clone and add path | ||
path.clone(function (path) { | ||
path.globalCompositeOperation = 'destination-out'; | ||
// http://fabricjs.com/using-transformations | ||
@@ -748,7 +684,4 @@ var desiredTransform = fabric.util.multiplyTransformMatrices( | ||
fabric.util.applyTransformToObject(path, desiredTransform); | ||
clipObject.addWithUpdate(path); | ||
obj.set({ | ||
clipPath: clipObject, | ||
dirty: true | ||
}); | ||
eraser.addWithUpdate(path); | ||
obj.set('dirty', true); | ||
obj.fire('erasing:end', { | ||
@@ -818,2 +751,3 @@ path: path | ||
path.setCoords(); | ||
// commense event sequence | ||
canvas.fire('before:path:created', { path: path }); | ||
@@ -832,2 +766,3 @@ | ||
}); | ||
// fire erasing:end | ||
canvas.fire('erasing:end', { | ||
@@ -842,3 +777,2 @@ path: path, | ||
canvas.requestRenderAll(); | ||
path.setCoords(); | ||
this._resetShadow(); | ||
@@ -845,0 +779,0 @@ |
@@ -869,3 +869,9 @@ (function() { | ||
else if (copiedStyle) { | ||
this.styles[cursorLoc.lineIndex + i][0] = copiedStyle[0]; | ||
// this test is required in order to close #6841 | ||
// when a pasted buffer begins with a newline then | ||
// this.styles[cursorLoc.lineIndex + i] and copiedStyle[0] | ||
// may be undefined for some reason | ||
if (this.styles[cursorLoc.lineIndex + i] && copiedStyle[0]) { | ||
this.styles[cursorLoc.lineIndex + i][0] = copiedStyle[0]; | ||
} | ||
} | ||
@@ -872,0 +878,0 @@ copiedStyle = copiedStyle && copiedStyle.slice(addedLines[i] + 1); |
@@ -48,3 +48,3 @@ (function() { | ||
* includes padding. Used of object detection. | ||
* set and refreshed with setCoords and calcCoords. | ||
* set and refreshed with setCoords. | ||
* @memberOf fabric.Object.prototype | ||
@@ -433,17 +433,2 @@ */ | ||
/** | ||
* Calculates and returns the .coords of an object. | ||
* unused by the library, only for the end dev. | ||
* @return {Object} Object with tl, tr, br, bl .... | ||
* @chainable | ||
* @deprecated | ||
*/ | ||
calcCoords: function(absolute) { | ||
// this is a compatibility function to avoid removing calcCoords now. | ||
if (absolute) { | ||
return this.calcACoords(); | ||
} | ||
return this.calcOCoords(); | ||
}, | ||
calcLineCoords: function() { | ||
@@ -523,3 +508,4 @@ var vpt = this.getViewportTransform(), | ||
* lineCoords are used to quickly find object during pointer events. | ||
* See {@link https://github.com/kangax/fabric.js/wiki/When-to-call-setCoords|When-to-call-setCoords} | ||
* See {@link https://github.com/fabricjs/fabric.js/wiki/When-to-call-setCoords} and {@link http://fabricjs.com/fabric-gotchas} | ||
* | ||
* @param {Boolean} [skipCorners] skip calculation of oCoords. | ||
@@ -624,22 +610,5 @@ * @return {fabric.Object} thisArg | ||
* @private | ||
* @deprecated since 3.4.0, please use fabric.util._calcDimensionsTransformMatrix | ||
* not including or including flipX, flipY to emulate the flipping boolean | ||
* @return {Object} .x width dimension | ||
* @return {Object} .y height dimension | ||
*/ | ||
_calcDimensionsTransformMatrix: function(skewX, skewY, flipping) { | ||
return util.calcDimensionsMatrix({ | ||
skewX: skewX, | ||
skewY: skewY, | ||
scaleX: this.scaleX * (flipping && this.flipX ? -1 : 1), | ||
scaleY: this.scaleY * (flipping && this.flipY ? -1 : 1) | ||
}); | ||
}, | ||
/* | ||
* Calculate object dimensions from its properties | ||
* @private | ||
* @return {Object} .x width dimension | ||
* @return {Object} .y height dimension | ||
*/ | ||
_getNonTransformedDimensions: function() { | ||
@@ -646,0 +615,0 @@ var strokeWidth = this.strokeWidth, |
@@ -21,4 +21,3 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { | ||
straighten: function() { | ||
this.rotate(this._getAngleValueForStraighten()); | ||
return this; | ||
return this.rotate(this._getAngleValueForStraighten()); | ||
}, | ||
@@ -32,3 +31,2 @@ | ||
* @return {fabric.Object} thisArg | ||
* @chainable | ||
*/ | ||
@@ -43,3 +41,4 @@ fxStraighten: function(callbacks) { | ||
fabric.util.animate({ | ||
return fabric.util.animate({ | ||
target: this, | ||
startValue: this.get('angle'), | ||
@@ -57,4 +56,2 @@ endValue: this._getAngleValueForStraighten(), | ||
}); | ||
return this; | ||
} | ||
@@ -81,10 +78,8 @@ }); | ||
* @return {fabric.Canvas} thisArg | ||
* @chainable | ||
*/ | ||
fxStraightenObject: function (object) { | ||
object.fxStraighten({ | ||
return object.fxStraighten({ | ||
onChange: this.requestRenderAllBound | ||
}); | ||
return this; | ||
} | ||
}); |
@@ -1004,18 +1004,22 @@ (function(global) { | ||
} | ||
rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g); | ||
rules = rules.map(function(rule) { return rule.trim(); }); | ||
// recovers all the rule in this form `body { style code... }` | ||
// rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g); | ||
rules = styleContents.split('}'); | ||
// remove empty rules. | ||
rules = rules.filter(function(rule) { return rule.trim(); }); | ||
// at this point we have hopefully an array of rules `body { style code... ` | ||
// eslint-disable-next-line no-loop-func | ||
rules.forEach(function(rule) { | ||
var match = rule.match(/([\s\S]*?)\s*\{([^}]*)\}/), | ||
ruleObj = { }, declaration = match[2].trim(), | ||
propertyValuePairs = declaration.replace(/;$/, '').split(/\s*;\s*/); | ||
var match = rule.split('{'), | ||
ruleObj = { }, declaration = match[1].trim(), | ||
propertyValuePairs = declaration.split(';').filter(function(pair) { return pair.trim(); }); | ||
for (i = 0, len = propertyValuePairs.length; i < len; i++) { | ||
var pair = propertyValuePairs[i].split(/\s*:\s*/), | ||
property = pair[0], | ||
value = pair[1]; | ||
var pair = propertyValuePairs[i].split(':'), | ||
property = pair[0].trim(), | ||
value = pair[1].trim(); | ||
ruleObj[property] = value; | ||
} | ||
rule = match[1]; | ||
rule = match[0].trim(); | ||
rule.split(',').forEach(function(_rule) { | ||
@@ -1022,0 +1026,0 @@ _rule = _rule.replace(/^svg/i, '').trim(); |
@@ -6,3 +6,3 @@ (function(global) { | ||
var fabric = global.fabric || (global.fabric = { }), | ||
pi = Math.PI; | ||
degreesToRadians = fabric.util.degreesToRadians; | ||
@@ -37,6 +37,5 @@ if (fabric.Circle) { | ||
/** | ||
* Start angle of the circle, moving clockwise | ||
* deprecated type, this should be in degree, this was an oversight. | ||
* degrees of start of the circle. | ||
* probably will change to degrees in next major version | ||
* @type Number | ||
* @type Number 0 - 359 | ||
* @default 0 | ||
@@ -48,8 +47,7 @@ */ | ||
* End angle of the circle | ||
* deprecated type, this should be in degree, this was an oversight. | ||
* probably will change to degrees in next major version | ||
* @type Number | ||
* @default 2Pi | ||
* @type Number 1 - 360 | ||
* @default 360 | ||
*/ | ||
endAngle: pi * 2, | ||
endAngle: 360, | ||
@@ -92,3 +90,3 @@ cacheProperties: fabric.Object.prototype.cacheProperties.concat('radius', 'startAngle', 'endAngle'), | ||
var svgString, x = 0, y = 0, | ||
angle = (this.endAngle - this.startAngle) % ( 2 * pi); | ||
angle = (this.endAngle - this.startAngle) % 360; | ||
@@ -104,10 +102,13 @@ if (angle === 0) { | ||
else { | ||
var startX = fabric.util.cos(this.startAngle) * this.radius, | ||
startY = fabric.util.sin(this.startAngle) * this.radius, | ||
endX = fabric.util.cos(this.endAngle) * this.radius, | ||
endY = fabric.util.sin(this.endAngle) * this.radius, | ||
largeFlag = angle > pi ? '1' : '0'; | ||
var start = degreesToRadians(this.startAngle), | ||
end = degreesToRadians(this.endAngle), | ||
radius = this.radius, | ||
startX = fabric.util.cos(start) * radius, | ||
startY = fabric.util.sin(start) * radius, | ||
endX = fabric.util.cos(end) * radius, | ||
endY = fabric.util.sin(end) * radius, | ||
largeFlag = angle > 180 ? '1' : '0'; | ||
svgString = [ | ||
'<path d="M ' + startX + ' ' + startY, | ||
' A ' + this.radius + ' ' + this.radius, | ||
' A ' + radius + ' ' + radius, | ||
' 0 ', +largeFlag + ' 1', ' ' + endX + ' ' + endY, | ||
@@ -131,4 +132,6 @@ '" ', 'COMMON_PARTS', ' />\n' | ||
this.radius, | ||
this.startAngle, | ||
this.endAngle, false); | ||
degreesToRadians(this.startAngle), | ||
degreesToRadians(this.endAngle), | ||
false | ||
); | ||
this._renderPaintInOrder(ctx); | ||
@@ -135,0 +138,0 @@ }, |
@@ -346,3 +346,3 @@ (function(global) { | ||
} | ||
this._drawClipPath(ctx); | ||
this._drawClipPath(ctx, this.clipPath); | ||
}, | ||
@@ -394,21 +394,2 @@ | ||
/** | ||
* Realises the transform from this group onto the supplied object | ||
* i.e. it tells you what would happen if the supplied object was in | ||
* the group, and then the group was destroyed. It mutates the supplied | ||
* object. | ||
* Warning: this method is not useful anymore, it has been kept to no break the api. | ||
* is not used in the fabricJS codebase | ||
* this method will be reduced to using the utility. | ||
* @private | ||
* @deprecated | ||
* @param {fabric.Object} object | ||
* @param {Array} parentMatrix parent transformation | ||
* @return {fabric.Object} transformedObject | ||
*/ | ||
realizeTransform: function(object, parentMatrix) { | ||
fabric.util.addTransformToObject(object, parentMatrix); | ||
return object; | ||
}, | ||
/** | ||
* Destroys a group (restoring state of its objects) | ||
@@ -427,2 +408,10 @@ * @return {fabric.Group} thisArg | ||
dispose: function () { | ||
this.callSuper('dispose'); | ||
this.forEachObject(function (object) { | ||
object.dispose && object.dispose(); | ||
}); | ||
this._objects = []; | ||
}, | ||
/** | ||
@@ -591,7 +580,6 @@ * make a group an active selection, remove the group from canvas | ||
} | ||
fabric.util.enlivenObjects(objects, function(enlivenedObjects) { | ||
fabric.util.enlivenObjects([object.clipPath], function(enlivedClipPath) { | ||
var options = fabric.util.object.clone(object, true); | ||
options.clipPath = enlivedClipPath[0]; | ||
delete options.objects; | ||
fabric.util.enlivenObjects(objects, function (enlivenedObjects) { | ||
var options = fabric.util.object.clone(object, true); | ||
delete options.objects; | ||
fabric.util.enlivenObjectEnlivables(object, options, function () { | ||
callback && callback(new fabric.Group(enlivenedObjects, options, true)); | ||
@@ -598,0 +586,0 @@ }); |
@@ -205,3 +205,4 @@ (function(global) { | ||
*/ | ||
dispose: function() { | ||
dispose: function () { | ||
this.callSuper('dispose'); | ||
this.removeTexture(this.cacheKey); | ||
@@ -716,4 +717,3 @@ this.removeTexture(this.cacheKey + '_filtered'); | ||
object.resizeFilter = resizeFilters[0]; | ||
fabric.util.enlivenObjects([object.clipPath], function(enlivedProps) { | ||
object.clipPath = enlivedProps[0]; | ||
fabric.util.enlivenObjectEnlivables(object, object, function () { | ||
var image = new fabric.Image(img, object); | ||
@@ -720,0 +720,0 @@ callback(image, false); |
@@ -9,2 +9,3 @@ (function(global) { | ||
extend = fabric.util.object.extend, | ||
clone = fabric.util.object.clone, | ||
_toString = Object.prototype.toString, | ||
@@ -51,9 +52,15 @@ toFixed = fabric.util.toFixed; | ||
*/ | ||
initialize: function(path, options) { | ||
options = options || { }; | ||
initialize: function (path, options) { | ||
options = clone(options || {}); | ||
delete options.path; | ||
this.callSuper('initialize', options); | ||
if (!path) { | ||
path = []; | ||
} | ||
this._setPath(path || [], options); | ||
}, | ||
/** | ||
* @private | ||
* @param {Array|String} path Path data (sequence of coordinates and corresponding "command" tokens) | ||
* @param {Object} [options] Options object | ||
*/ | ||
_setPath: function (path, options) { | ||
var fromArray = _toString.call(path) === '[object Array]'; | ||
@@ -65,6 +72,3 @@ | ||
if (!this.path) { | ||
return; | ||
} | ||
fabric.Polyline.prototype._setPositionDimensions.call(this, options); | ||
fabric.Polyline.prototype._setPositionDimensions.call(this, options || {}); | ||
}, | ||
@@ -71,0 +75,0 @@ |
@@ -5,3 +5,4 @@ (function(global) { | ||
var fabric = global.fabric || (global.fabric = { }); | ||
var fabric = global.fabric || (global.fabric = {}), | ||
projectStrokeOnPoints = fabric.util.projectStrokeOnPoints; | ||
@@ -30,2 +31,9 @@ if (fabric.Polygon) { | ||
* @private | ||
*/ | ||
_projectStrokeOnPoints: function () { | ||
return projectStrokeOnPoints(this.points, this); | ||
}, | ||
/** | ||
* @private | ||
* @param {CanvasRenderingContext2D} ctx Context to render on | ||
@@ -32,0 +40,0 @@ */ |
@@ -9,3 +9,4 @@ (function(global) { | ||
max = fabric.util.array.max, | ||
toFixed = fabric.util.toFixed; | ||
toFixed = fabric.util.toFixed, | ||
projectStrokeOnPoints = fabric.util.projectStrokeOnPoints; | ||
@@ -39,2 +40,13 @@ if (fabric.Polyline) { | ||
/** | ||
* WARNING: Feature in progress | ||
* Calculate the exact bounding box taking in account strokeWidth on acute angles | ||
* this will be turned to true by default on fabric 6.0 | ||
* maybe will be left in as an optimization since calculations may be slow | ||
* @deprecated | ||
* @type Boolean | ||
* @default false | ||
*/ | ||
exactBoundingBox: false, | ||
cacheProperties: fabric.Object.prototype.cacheProperties.concat('points'), | ||
@@ -68,9 +80,21 @@ | ||
/** | ||
* @private | ||
*/ | ||
_projectStrokeOnPoints: function () { | ||
return projectStrokeOnPoints(this.points, this, true); | ||
}, | ||
_setPositionDimensions: function(options) { | ||
var calcDim = this._calcDimensions(options), correctLeftTop; | ||
this.width = calcDim.width; | ||
this.height = calcDim.height; | ||
var calcDim = this._calcDimensions(options), correctLeftTop, | ||
correctSize = this.exactBoundingBox ? this.strokeWidth : 0; | ||
this.width = calcDim.width - correctSize; | ||
this.height = calcDim.height - correctSize; | ||
if (!options.fromSVG) { | ||
correctLeftTop = this.translateToGivenOrigin( | ||
{ x: calcDim.left - this.strokeWidth / 2, y: calcDim.top - this.strokeWidth / 2 }, | ||
{ | ||
// this looks bad, but is one way to keep it optional for now. | ||
x: calcDim.left - this.strokeWidth / 2 + correctSize / 2, | ||
y: calcDim.top - this.strokeWidth / 2 + correctSize / 2 | ||
}, | ||
'left', | ||
@@ -89,4 +113,4 @@ 'top', | ||
this.pathOffset = { | ||
x: calcDim.left + this.width / 2, | ||
y: calcDim.top + this.height / 2 | ||
x: calcDim.left + this.width / 2 + correctSize / 2, | ||
y: calcDim.top + this.height / 2 + correctSize / 2 | ||
}; | ||
@@ -107,3 +131,3 @@ }, | ||
var points = this.points, | ||
var points = this.exactBoundingBox ? this._projectStrokeOnPoints() : this.points, | ||
minX = min(points, 'x') || 0, | ||
@@ -120,3 +144,3 @@ minY = min(points, 'y') || 0, | ||
width: width, | ||
height: height | ||
height: height, | ||
}; | ||
@@ -123,0 +147,0 @@ }, |
@@ -16,3 +16,3 @@ (function(global) { | ||
' textAlign fontStyle lineHeight textBackgroundColor charSpacing styles' + | ||
' direction path pathStartOffset pathSide').split(' '); | ||
' direction path pathStartOffset pathSide pathAlign').split(' '); | ||
@@ -46,3 +46,4 @@ /** | ||
'pathStartOffset', | ||
'pathSide' | ||
'pathSide', | ||
'pathAlign' | ||
], | ||
@@ -245,2 +246,12 @@ | ||
/** | ||
* How text is aligned to the path. This property determines | ||
* the perpendicular position of each character relative to the path. | ||
* (one of "baseline", "center", "ascender", "descender") | ||
* This feature is in BETA, and its behavior may change | ||
* @type String | ||
* @default | ||
*/ | ||
pathAlign: 'baseline', | ||
/** | ||
* @private | ||
@@ -388,2 +399,4 @@ */ | ||
* if created it gets stored for reuse | ||
* this is for internal use, please do not use it | ||
* @private | ||
* @param {String} text Text string | ||
@@ -560,3 +573,16 @@ * @param {Object} [options] Options object | ||
_setTextStyles: function(ctx, charStyle, forMeasuring) { | ||
ctx.textBaseline = 'alphabetic'; | ||
ctx.textBaseline = 'alphabetical'; | ||
if (this.path) { | ||
switch (this.pathAlign) { | ||
case 'center': | ||
ctx.textBaseline = 'middle'; | ||
break; | ||
case 'ascender': | ||
ctx.textBaseline = 'top'; | ||
break; | ||
case 'descender': | ||
ctx.textBaseline = 'bottom'; | ||
break; | ||
} | ||
} | ||
ctx.font = this._getFontDeclaration(charStyle, forMeasuring); | ||
@@ -1026,5 +1052,9 @@ }, | ||
isLtr = this.direction === 'ltr', sign = this.direction === 'ltr' ? 1 : -1, | ||
drawingLeft; | ||
drawingLeft, currentDirection = ctx.canvas.getAttribute('dir'); | ||
ctx.save(); | ||
if (currentDirection !== this.direction) { | ||
ctx.canvas.setAttribute('dir', isLtr ? 'ltr' : 'rtl'); | ||
ctx.direction = isLtr ? 'ltr' : 'rtl'; | ||
ctx.textAlign = isLtr ? 'left' : 'right'; | ||
} | ||
top -= lineHeight * this._fontSizeFraction / this.lineHeight; | ||
@@ -1034,5 +1064,2 @@ if (shortCut) { | ||
// drawingLeft = isLtr ? left : left - this.getLineWidth(lineIndex); | ||
ctx.canvas.setAttribute('dir', isLtr ? 'ltr' : 'rtl'); | ||
ctx.direction = isLtr ? 'ltr' : 'rtl'; | ||
ctx.textAlign = isLtr ? 'left' : 'right'; | ||
this._renderChar(method, ctx, lineIndex, 0, line.join(''), left, top, lineHeight); | ||
@@ -1074,5 +1101,2 @@ ctx.restore(); | ||
drawingLeft = left; | ||
ctx.canvas.setAttribute('dir', isLtr ? 'ltr' : 'rtl'); | ||
ctx.direction = isLtr ? 'ltr' : 'rtl'; | ||
ctx.textAlign = isLtr ? 'left' : 'right'; | ||
this._renderChar(method, ctx, lineIndex, i, charsToRender, drawingLeft, top, lineHeight); | ||
@@ -1328,15 +1352,8 @@ } | ||
getLineWidth: function(lineIndex) { | ||
if (this.__lineWidths[lineIndex]) { | ||
if (this.__lineWidths[lineIndex] !== undefined) { | ||
return this.__lineWidths[lineIndex]; | ||
} | ||
var width, line = this._textLines[lineIndex], lineInfo; | ||
if (line === '') { | ||
width = 0; | ||
} | ||
else { | ||
lineInfo = this.measureLine(lineIndex); | ||
width = lineInfo.width; | ||
} | ||
var lineInfo = this.measureLine(lineIndex); | ||
var width = lineInfo.width; | ||
this.__lineWidths[lineIndex] = width; | ||
@@ -1343,0 +1360,0 @@ return width; |
@@ -136,4 +136,8 @@ (function () { | ||
/** | ||
* The transformation (in the format of Canvas transform) which focuses the viewport | ||
* The transformation (a Canvas 2D API transform matrix) which focuses the viewport | ||
* @type Array | ||
* @example <caption>Default transform</caption> | ||
* canvas.viewportTransform = [1, 0, 0, 1, 0, 0]; | ||
* @example <caption>Scale by 70% and translate toward bottom-right by 50, without skewing</caption> | ||
* canvas.viewportTransform = [0.7, 0, 0, 0.7, 50, 50]; | ||
* @default | ||
@@ -232,3 +236,3 @@ */ | ||
_isRetinaScaling: function() { | ||
return (fabric.devicePixelRatio !== 1 && this.enableRetinaScaling); | ||
return (fabric.devicePixelRatio > 1 && this.enableRetinaScaling); | ||
}, | ||
@@ -241,3 +245,3 @@ | ||
getRetinaScaling: function() { | ||
return this._isRetinaScaling() ? fabric.devicePixelRatio : 1; | ||
return this._isRetinaScaling() ? Math.max(1, fabric.devicePixelRatio) : 1; | ||
}, | ||
@@ -609,3 +613,3 @@ | ||
if (this._isCurrentlyDrawing) { | ||
this.freeDrawingBrush && this.freeDrawingBrush._setBrushStyles(); | ||
this.freeDrawingBrush && this.freeDrawingBrush._setBrushStyles(this.contextTop); | ||
} | ||
@@ -677,4 +681,4 @@ this._initRetinaScaling(); | ||
/** | ||
* Sets viewport transform of this canvas instance | ||
* @param {Array} vpt the transform in the form of context.transform | ||
* Sets viewport transformation of this canvas instance | ||
* @param {Array} vpt a Canvas 2D API transform matrix | ||
* @return {fabric.Canvas} instance | ||
@@ -1766,2 +1770,6 @@ * @chainable true | ||
object.dispose && object.dispose(); | ||
// animation module is still optional | ||
if (fabric.runningAnimations) { | ||
fabric.runningAnimations.cancelByTarget(object); | ||
} | ||
}); | ||
@@ -1781,3 +1789,3 @@ this._objects = []; | ||
this.lowerCanvasEl.classList.remove('lower-canvas'); | ||
this.lowerCanvasEl.style = this._originalCanvasStyle; | ||
fabric.util.setStyle(this.lowerCanvasEl, this._originalCanvasStyle); | ||
delete this._originalCanvasStyle; | ||
@@ -1784,0 +1792,0 @@ // restore canvas size to original size in case retina scaling was applied |
@@ -1,3 +0,113 @@ | ||
(function() { | ||
(function () { | ||
var extend = fabric.util.object.extend, | ||
clone = fabric.util.object.clone; | ||
/** | ||
* @typedef {Object} AnimationOptions | ||
* @property {Function} [options.onChange] Callback; invoked on every value change | ||
* @property {Function} [options.onComplete] Callback; invoked when value change is completed | ||
* @property {Number} [options.startValue=0] Starting value | ||
* @property {Number} [options.endValue=100] Ending value | ||
* @property {Number} [options.byValue=100] Value to modify the property by | ||
* @property {Function} [options.easing] Easing function | ||
* @property {Number} [options.duration=500] Duration of change (in ms) | ||
* @property {Function} [options.abort] Additional function with logic. If returns true, animation aborts. | ||
* | ||
* @typedef {() => void} CancelFunction | ||
* | ||
* @typedef {Object} AnimationCurrentState | ||
* @property {number} currentValue value in range [`startValue`, `endValue`] | ||
* @property {number} completionRate value in range [0, 1] | ||
* @property {number} durationRate value in range [0, 1] | ||
* | ||
* @typedef {(AnimationOptions & AnimationCurrentState & { cancel: CancelFunction }} AnimationContext | ||
*/ | ||
/** | ||
* Array holding all running animations | ||
* @memberof fabric | ||
* @type {AnimationContext[]} | ||
*/ | ||
var RUNNING_ANIMATIONS = []; | ||
fabric.util.object.extend(RUNNING_ANIMATIONS, { | ||
/** | ||
* cancel all running animations at the next requestAnimFrame | ||
* @returns {AnimationContext[]} | ||
*/ | ||
cancelAll: function () { | ||
var animations = this.splice(0); | ||
animations.forEach(function (animation) { | ||
animation.cancel(); | ||
}); | ||
return animations; | ||
}, | ||
/** | ||
* cancel all running animations attached to canvas at the next requestAnimFrame | ||
* @param {fabric.Canvas} canvas | ||
* @returns {AnimationContext[]} | ||
*/ | ||
cancelByCanvas: function (canvas) { | ||
if (!canvas) { | ||
return []; | ||
} | ||
var cancelled = this.filter(function (animation) { | ||
return typeof animation.target === 'object' && animation.target.canvas === canvas; | ||
}); | ||
cancelled.forEach(function (animation) { | ||
animation.cancel(); | ||
}); | ||
return cancelled; | ||
}, | ||
/** | ||
* cancel all running animations for target at the next requestAnimFrame | ||
* @param {*} target | ||
* @returns {AnimationContext[]} | ||
*/ | ||
cancelByTarget: function (target) { | ||
var cancelled = this.findAnimationsByTarget(target); | ||
cancelled.forEach(function (animation) { | ||
animation.cancel(); | ||
}); | ||
return cancelled; | ||
}, | ||
/** | ||
* | ||
* @param {CancelFunction} cancelFunc the function returned by animate | ||
* @returns {number} | ||
*/ | ||
findAnimationIndex: function (cancelFunc) { | ||
return this.indexOf(this.findAnimation(cancelFunc)); | ||
}, | ||
/** | ||
* | ||
* @param {CancelFunction} cancelFunc the function returned by animate | ||
* @returns {AnimationContext | undefined} animation's options object | ||
*/ | ||
findAnimation: function (cancelFunc) { | ||
return this.find(function (animation) { | ||
return animation.cancel === cancelFunc; | ||
}); | ||
}, | ||
/** | ||
* | ||
* @param {*} target the object that is assigned to the target property of the animation context | ||
* @returns {AnimationContext[]} array of animation options object associated with target | ||
*/ | ||
findAnimationsByTarget: function (target) { | ||
if (!target) { | ||
return []; | ||
} | ||
return this.filter(function (animation) { | ||
return animation.target === target; | ||
}); | ||
} | ||
}); | ||
function noop() { | ||
@@ -14,18 +124,26 @@ return false; | ||
* @memberOf fabric.util | ||
* @param {Object} [options] Animation options | ||
* @param {Function} [options.onChange] Callback; invoked on every value change | ||
* @param {Function} [options.onComplete] Callback; invoked when value change is completed | ||
* @param {Number} [options.startValue=0] Starting value | ||
* @param {Number} [options.endValue=100] Ending value | ||
* @param {Number} [options.byValue=100] Value to modify the property by | ||
* @param {Function} [options.easing] Easing function | ||
* @param {Number} [options.duration=500] Duration of change (in ms) | ||
* @param {Function} [options.abort] Additional function with logic. If returns true, onComplete is called. | ||
* @returns {Function} abort function | ||
* @param {AnimationOptions} [options] Animation options | ||
* @returns {CancelFunction} cancel function | ||
*/ | ||
function animate(options) { | ||
var cancel = false; | ||
options || (options = {}); | ||
var cancel = false, | ||
context, | ||
removeFromRegistry = function () { | ||
var index = fabric.runningAnimations.indexOf(context); | ||
return index > -1 && fabric.runningAnimations.splice(index, 1)[0]; | ||
}; | ||
context = extend(clone(options), { | ||
cancel: function () { | ||
cancel = true; | ||
return removeFromRegistry(); | ||
}, | ||
currentValue: 'startValue' in options ? options.startValue : 0, | ||
completionRate: 0, | ||
durationRate: 0 | ||
}); | ||
fabric.runningAnimations.push(context); | ||
requestAnimFrame(function(timestamp) { | ||
options || (options = { }); | ||
var start = timestamp || +new Date(), | ||
@@ -45,4 +163,2 @@ duration = options.duration || 500, | ||
(function tick(ticktime) { | ||
// TODO: move abort call after calculation | ||
// and pass (current,valuePerc, timePerc) as arguments | ||
time = ticktime || +new Date(); | ||
@@ -53,2 +169,6 @@ var currentTime = time > finish ? duration : (time - start), | ||
valuePerc = Math.abs((current - startValue) / byValue); | ||
// update context | ||
context.currentValue = current; | ||
context.completionRate = valuePerc; | ||
context.durationRate = timePerc; | ||
if (cancel) { | ||
@@ -58,10 +178,14 @@ return; | ||
if (abort(current, valuePerc, timePerc)) { | ||
// remove this in 4.0 | ||
// does to even make sense to abort and run onComplete? | ||
onComplete(endValue, 1, 1); | ||
removeFromRegistry(); | ||
return; | ||
} | ||
if (time > finish) { | ||
// update context | ||
context.currentValue = endValue; | ||
context.completionRate = 1; | ||
context.durationRate = 1; | ||
// execute callbacks | ||
onChange(endValue, 1, 1); | ||
onComplete(endValue, 1, 1); | ||
removeFromRegistry(); | ||
return; | ||
@@ -75,5 +199,4 @@ } | ||
}); | ||
return function() { | ||
cancel = true; | ||
}; | ||
return context.cancel; | ||
} | ||
@@ -110,2 +233,3 @@ | ||
fabric.util.cancelAnimFrame = cancelAnimFrame; | ||
fabric.runningAnimations = RUNNING_ANIMATIONS; | ||
})(); |
@@ -143,2 +143,131 @@ (function(global) { | ||
/** | ||
* Creates a vetor from points represented as a point | ||
* @static | ||
* @memberOf fabric.util | ||
* | ||
* @typedef {Object} Point | ||
* @property {number} x | ||
* @property {number} y | ||
* | ||
* @param {Point} from | ||
* @param {Point} to | ||
* @returns {Point} vector | ||
*/ | ||
createVector: function (from, to) { | ||
return new fabric.Point(to.x - from.x, to.y - from.y); | ||
}, | ||
/** | ||
* Calculates angle between 2 vectors using dot product | ||
* @static | ||
* @memberOf fabric.util | ||
* @param {Point} a | ||
* @param {Point} b | ||
* @returns the angle in radian between the vectors | ||
*/ | ||
calcAngleBetweenVectors: function (a, b) { | ||
return Math.acos((a.x * b.x + a.y * b.y) / (Math.hypot(a.x, a.y) * Math.hypot(b.x, b.y))); | ||
}, | ||
/** | ||
* @static | ||
* @memberOf fabric.util | ||
* @param {Point} v | ||
* @returns {Point} vector representing the unit vector of pointing to the direction of `v` | ||
*/ | ||
getHatVector: function (v) { | ||
return new fabric.Point(v.x, v.y).multiply(1 / Math.hypot(v.x, v.y)); | ||
}, | ||
/** | ||
* @static | ||
* @memberOf fabric.util | ||
* @param {Point} A | ||
* @param {Point} B | ||
* @param {Point} C | ||
* @returns {{ vector: Point, angle: number }} vector representing the bisector of A and A's angle | ||
*/ | ||
getBisector: function (A, B, C) { | ||
var AB = fabric.util.createVector(A, B), AC = fabric.util.createVector(A, C); | ||
var alpha = fabric.util.calcAngleBetweenVectors(AB, AC); | ||
// check if alpha is relative to AB->BC | ||
var ro = fabric.util.calcAngleBetweenVectors(fabric.util.rotateVector(AB, alpha), AC); | ||
var phi = alpha * (ro === 0 ? 1 : -1) / 2; | ||
return { | ||
vector: fabric.util.getHatVector(fabric.util.rotateVector(AB, phi)), | ||
angle: alpha | ||
}; | ||
}, | ||
/** | ||
* Project stroke width on points returning 2 projections for each point as follows: | ||
* - `miter`: 2 points corresponding to the outer boundary and the inner boundary of stroke. | ||
* - `bevel`: 2 points corresponding to the bevel boundaries, tangent to the bisector. | ||
* - `round`: same as `bevel` | ||
* Used to calculate object's bounding box | ||
* @static | ||
* @memberOf fabric.util | ||
* @param {Point[]} points | ||
* @param {Object} options | ||
* @param {number} options.strokeWidth | ||
* @param {'miter'|'bevel'|'round'} options.strokeLineJoin | ||
* @param {number} options.strokeMiterLimit https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-miterlimit | ||
* @param {boolean} options.strokeUniform | ||
* @param {number} options.scaleX | ||
* @param {number} options.scaleY | ||
* @param {boolean} [openPath] whether the shape is open or not, affects the calculations of the first and last points | ||
* @returns {fabric.Point[]} array of size 2n/4n of all suspected points | ||
*/ | ||
projectStrokeOnPoints: function (points, options, openPath) { | ||
var coords = [], s = options.strokeWidth / 2, | ||
strokeUniformScalar = options.strokeUniform ? | ||
new fabric.Point(1 / options.scaleX, 1 / options.scaleY) : new fabric.Point(1, 1), | ||
getStrokeHatVector = function (v) { | ||
var scalar = s / (Math.hypot(v.x, v.y)); | ||
return new fabric.Point(v.x * scalar * strokeUniformScalar.x, v.y * scalar * strokeUniformScalar.y); | ||
}; | ||
if (points.length <= 1) {return coords;} | ||
points.forEach(function (p, index) { | ||
var A = new fabric.Point(p.x, p.y), B, C; | ||
if (index === 0) { | ||
C = points[index + 1]; | ||
B = openPath ? getStrokeHatVector(fabric.util.createVector(C, A)).addEquals(A) : points[points.length - 1]; | ||
} | ||
else if (index === points.length - 1) { | ||
B = points[index - 1]; | ||
C = openPath ? getStrokeHatVector(fabric.util.createVector(B, A)).addEquals(A) : points[0]; | ||
} | ||
else { | ||
B = points[index - 1]; | ||
C = points[index + 1]; | ||
} | ||
var bisector = fabric.util.getBisector(A, B, C), | ||
bisectorVector = bisector.vector, | ||
alpha = bisector.angle, | ||
scalar, | ||
miterVector; | ||
if (options.strokeLineJoin === 'miter') { | ||
scalar = -s / Math.sin(alpha / 2); | ||
miterVector = new fabric.Point( | ||
bisectorVector.x * scalar * strokeUniformScalar.x, | ||
bisectorVector.y * scalar * strokeUniformScalar.y | ||
); | ||
if (Math.hypot(miterVector.x, miterVector.y) / s <= options.strokeMiterLimit) { | ||
coords.push(A.add(miterVector)); | ||
coords.push(A.subtract(miterVector)); | ||
return; | ||
} | ||
} | ||
scalar = -s * Math.SQRT2; | ||
miterVector = new fabric.Point( | ||
bisectorVector.x * scalar * strokeUniformScalar.x, | ||
bisectorVector.y * scalar * strokeUniformScalar.y | ||
); | ||
coords.push(A.add(miterVector)); | ||
coords.push(A.subtract(miterVector)); | ||
}); | ||
return coords; | ||
}, | ||
/** | ||
* Apply transform t to point p | ||
@@ -456,2 +585,21 @@ * @static | ||
/** | ||
* Creates corresponding fabric instances residing in an object, e.g. `clipPath` | ||
* @see {@link fabric.Object.ENLIVEN_PROPS} | ||
* @param {Object} object | ||
* @param {Object} [context] assign enlived props to this object (pass null to skip this) | ||
* @param {(objects:fabric.Object[]) => void} callback | ||
*/ | ||
enlivenObjectEnlivables: function (object, context, callback) { | ||
var enlivenProps = fabric.Object.ENLIVEN_PROPS.filter(function (key) { return !!object[key]; }); | ||
fabric.util.enlivenObjects(enlivenProps.map(function (key) { return object[key]; }), function (enlivedProps) { | ||
var objects = {}; | ||
enlivenProps.forEach(function (key, index) { | ||
objects[key] = enlivedProps[index]; | ||
context && (context[key] = enlivedProps[index]); | ||
}); | ||
callback && callback(objects); | ||
}); | ||
}, | ||
/** | ||
* Create and wait for loading of patterns | ||
@@ -548,45 +696,2 @@ * @static | ||
/** | ||
* WARNING: THIS WAS TO SUPPORT OLD BROWSERS. deprecated. | ||
* WILL BE REMOVED IN FABRIC 5.0 | ||
* 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> | ||
* | ||
* @param {CanvasRenderingContext2D} ctx context | ||
* @param {Number} x start x coordinate | ||
* @param {Number} y start y coordinate | ||
* @param {Number} x2 end x coordinate | ||
* @param {Number} y2 end y coordinate | ||
* @param {Array} da dash array pattern | ||
* @deprecated | ||
*/ | ||
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(); | ||
}, | ||
/** | ||
* Creates canvas element | ||
@@ -718,3 +823,3 @@ * @static | ||
* @param {Number} [options.skewX] | ||
* @param {Number} [options.skewX] | ||
* @param {Number} [options.skewY] | ||
* @return {Number[]} transform matrix | ||
@@ -1073,4 +1178,47 @@ */ | ||
}; | ||
} | ||
}, | ||
/** | ||
* Merges 2 clip paths into one visually equal clip path | ||
* | ||
* **IMPORTANT**:\ | ||
* Does **NOT** clone the arguments, clone them proir if necessary. | ||
* | ||
* Creates a wrapper (group) that contains one clip path and is clipped by the other so content is kept where both overlap. | ||
* Use this method if both the clip paths may have nested clip paths of their own, so assigning one to the other's clip path property is not possible. | ||
* | ||
* In order to handle the `inverted` property we follow logic described in the following cases:\ | ||
* **(1)** both clip paths are inverted - the clip paths pass the inverted prop to the wrapper and loose it themselves.\ | ||
* **(2)** one is inverted and the other isn't - the wrapper shouldn't become inverted and the inverted clip path must clip the non inverted one to produce an identical visual effect.\ | ||
* **(3)** both clip paths are not inverted - wrapper and clip paths remain unchanged. | ||
* | ||
* @memberOf fabric.util | ||
* @param {fabric.Object} c1 | ||
* @param {fabric.Object} c2 | ||
* @returns {fabric.Object} merged clip path | ||
*/ | ||
mergeClipPaths: function (c1, c2) { | ||
var a = c1, b = c2; | ||
if (a.inverted && !b.inverted) { | ||
// case (2) | ||
a = c2; | ||
b = c1; | ||
} | ||
// `b` becomes `a`'s clip path so we transform `b` to `a` coordinate plane | ||
fabric.util.applyTransformToObject( | ||
b, | ||
fabric.util.multiplyTransformMatrices( | ||
fabric.util.invertTransform(a.calcTransformMatrix()), | ||
b.calcTransformMatrix() | ||
) | ||
); | ||
// assign the `inverted` prop to the wrapping group | ||
var inverted = a.inverted && b.inverted; | ||
if (inverted) { | ||
// case (1) | ||
a.inverted = b.inverted = false; | ||
} | ||
return new fabric.Group([a], { clipPath: b, inverted: inverted }); | ||
}, | ||
}; | ||
})(typeof exports !== 'undefined' ? exports : this); |
@@ -510,3 +510,3 @@ (function() { | ||
// the path | ||
while (tmpLen < distance && perc <= 1 && nextStep > 0.0001) { | ||
while (tmpLen < distance && nextStep > 0.0001) { | ||
p = iterator(perc); | ||
@@ -518,4 +518,4 @@ lastPerc = perc; | ||
// we discard this step and we make smaller steps. | ||
perc -= nextStep; | ||
nextStep /= 2; | ||
perc -= nextStep; | ||
} | ||
@@ -817,46 +817,2 @@ else { | ||
/** | ||
* Calculate bounding box of a elliptic-arc | ||
* @deprecated | ||
* @param {Number} fx start point of arc | ||
* @param {Number} fy | ||
* @param {Number} rx horizontal radius | ||
* @param {Number} ry vertical radius | ||
* @param {Number} rot angle of horizontal axis | ||
* @param {Number} large 1 or 0, whatever the arc is the big or the small on the 2 points | ||
* @param {Number} sweep 1 or 0, 1 clockwise or counterclockwise direction | ||
* @param {Number} tx end point of arc | ||
* @param {Number} ty | ||
*/ | ||
function getBoundsOfArc(fx, fy, rx, ry, rot, large, sweep, tx, ty) { | ||
var fromX = 0, fromY = 0, bound, bounds = [], | ||
segs = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot); | ||
for (var i = 0, len = segs.length; i < len; i++) { | ||
bound = getBoundsOfCurve(fromX, fromY, segs[i][1], segs[i][2], segs[i][3], segs[i][4], segs[i][5], segs[i][6]); | ||
bounds.push({ x: bound[0].x + fx, y: bound[0].y + fy }); | ||
bounds.push({ x: bound[1].x + fx, y: bound[1].y + fy }); | ||
fromX = segs[i][5]; | ||
fromY = segs[i][6]; | ||
} | ||
return bounds; | ||
}; | ||
/** | ||
* Draws arc | ||
* @deprecated | ||
* @param {CanvasRenderingContext2D} ctx | ||
* @param {Number} fx | ||
* @param {Number} fy | ||
* @param {Array} coords coords of the arc, without the front 'A/a' | ||
*/ | ||
function drawArc(ctx, fx, fy, coords) { | ||
coords = coords.slice(0).unshift('X'); // command A or a does not matter | ||
var beziers = fromArcToBeziers(fx, fy, coords); | ||
beziers.forEach(function(bezier) { | ||
ctx.bezierCurveTo.apply(ctx, bezier.slice(1)); | ||
}); | ||
}; | ||
/** | ||
* Join path commands to go back to svg format | ||
@@ -876,12 +832,2 @@ * @param {Array} pathData fabricJS parsed path commands | ||
fabric.util.transformPath = transformPath; | ||
/** | ||
* Typo of `fromArcToBeziers` kept for not breaking the api once corrected. | ||
* Will be removed in fabric 5.0 | ||
* @deprecated | ||
*/ | ||
fabric.util.fromArcToBeizers = fromArcToBeziers; | ||
// kept because we do not want to make breaking changes. | ||
// but useless and deprecated. | ||
fabric.util.getBoundsOfArc = getBoundsOfArc; | ||
fabric.util.drawArc = drawArc; | ||
})(); |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
2587502
0
118
59955
9
2
293