UNPKG

fabric-pure-browser

Version:

Fabric.js package with no node-specific dependencies (node-canvas, jsdom). The project is published once a day (in case if a new version appears) from 'master' branch of https://github.com/fabricjs/fabric.js repository. You can keep original imports in

741 lines (696 loc) 30 kB
(function(global) { 'use strict'; var fabric = global.fabric || (global.fabric = { }), scaleMap = ['e', 'se', 's', 'sw', 'w', 'nw', 'n', 'ne', 'e'], skewMap = ['ns', 'nesw', 'ew', 'nwse'], controls = {}, LEFT = 'left', TOP = 'top', RIGHT = 'right', BOTTOM = 'bottom', CENTER = 'center', opposite = { top: BOTTOM, bottom: TOP, left: RIGHT, right: LEFT, center: CENTER, }, radiansToDegrees = fabric.util.radiansToDegrees, sign = (Math.sign || function(x) { return ((x > 0) - (x < 0)) || +x; }); /** * Combine control position and object angle to find the control direction compared * to the object center. * @param {fabric.Object} fabricObject the fabric object for which we are rendering controls * @param {fabric.Control} control the control class * @return {Number} 0 - 7 a quadrant number */ function findCornerQuadrant(fabricObject, control) { var cornerAngle = fabricObject.angle + radiansToDegrees(Math.atan2(control.y, control.x)) + 360; return Math.round((cornerAngle % 360) / 45); } function fireEvent(eventName, options) { var target = options.transform.target, canvas = target.canvas, canvasOptions = fabric.util.object.clone(options); canvasOptions.target = target; canvas && canvas.fire('object:' + eventName, canvasOptions); target.fire(eventName, options); } /** * Inspect event and fabricObject properties to understand if the scaling action * @param {Event} eventData from the user action * @param {fabric.Object} fabricObject the fabric object about to scale * @return {Boolean} true if scale is proportional */ function scaleIsProportional(eventData, fabricObject) { var canvas = fabricObject.canvas, uniScaleKey = canvas.uniScaleKey, uniformIsToggled = eventData[uniScaleKey]; return (canvas.uniformScaling && !uniformIsToggled) || (!canvas.uniformScaling && uniformIsToggled); } /** * Checks if transform is centered * @param {Object} transform transform data * @return {Boolean} true if transform is centered */ function isTransformCentered(transform) { return transform.originX === CENTER && transform.originY === CENTER; } /** * Inspect fabricObject to understand if the current scaling action is allowed * @param {fabric.Object} fabricObject the fabric object about to scale * @param {String} by 'x' or 'y' or '' * @param {Boolean} scaleProportionally true if we are trying to scale proportionally * @return {Boolean} true if scaling is not allowed at current conditions */ function scalingIsForbidden(fabricObject, by, scaleProportionally) { var lockX = fabricObject.lockScalingX, lockY = fabricObject.lockScalingY; if (lockX && lockY) { return true; } if (!by && (lockX || lockY) && scaleProportionally) { return true; } if (lockX && by === 'x') { return true; } if (lockY && by === 'y') { return true; } return false; } /** * return the correct cursor style for the scale action * @param {Event} eventData the javascript event that is causing the scale * @param {fabric.Control} control the control that is interested in the action * @param {fabric.Object} fabricObject the fabric object that is interested in the action * @return {String} a valid css string for the cursor */ function scaleCursorStyleHandler(eventData, control, fabricObject) { var notAllowed = 'not-allowed', scaleProportionally = scaleIsProportional(eventData, fabricObject), by = ''; if (control.x !== 0 && control.y === 0) { by = 'x'; } else if (control.x === 0 && control.y !== 0) { by = 'y'; } if (scalingIsForbidden(fabricObject, by, scaleProportionally)) { return notAllowed; } var n = findCornerQuadrant(fabricObject, control); return scaleMap[n] + '-resize'; } /** * return the correct cursor style for the skew action * @param {Event} eventData the javascript event that is causing the scale * @param {fabric.Control} control the control that is interested in the action * @param {fabric.Object} fabricObject the fabric object that is interested in the action * @return {String} a valid css string for the cursor */ function skewCursorStyleHandler(eventData, control, fabricObject) { var notAllowed = 'not-allowed'; if (control.x !== 0 && fabricObject.lockSkewingY) { return notAllowed; } if (control.y !== 0 && fabricObject.lockSkewingX) { return notAllowed; } var n = findCornerQuadrant(fabricObject, control) % 4; return skewMap[n] + '-resize'; } /** * Combine skew and scale style handlers to cover fabric standard use case * @param {Event} eventData the javascript event that is causing the scale * @param {fabric.Control} control the control that is interested in the action * @param {fabric.Object} fabricObject the fabric object that is interested in the action * @return {String} a valid css string for the cursor */ function scaleSkewCursorStyleHandler(eventData, control, fabricObject) { if (eventData[fabricObject.canvas.altActionKey]) { return controls.skewCursorStyleHandler(eventData, control, fabricObject); } return controls.scaleCursorStyleHandler(eventData, control, fabricObject); } /** * Inspect event, control and fabricObject to return the correct action name * @param {Event} eventData the javascript event that is causing the scale * @param {fabric.Control} control the control that is interested in the action * @param {fabric.Object} fabricObject the fabric object that is interested in the action * @return {String} an action name */ function scaleOrSkewActionName(eventData, control, fabricObject) { var isAlternative = eventData[fabricObject.canvas.altActionKey]; if (control.x === 0) { // then is scaleY or skewX return isAlternative ? 'skewX' : 'scaleY'; } if (control.y === 0) { // then is scaleY or skewX return isAlternative ? 'skewY' : 'scaleX'; } } /** * Find the correct style for the control that is used for rotation. * this function is very simple and it just take care of not-allowed or standard cursor * @param {Event} eventData the javascript event that is causing the scale * @param {fabric.Control} control the control that is interested in the action * @param {fabric.Object} fabricObject the fabric object that is interested in the action * @return {String} a valid css string for the cursor */ function rotationStyleHandler(eventData, control, fabricObject) { if (fabricObject.lockRotation) { return 'not-allowed'; } return control.cursorStyle; } function commonEventInfo(eventData, transform, x, y) { return { e: eventData, transform: transform, pointer: { x: x, y: y, } }; } /** * Wrap an action handler with saving/restoring object position on the transform. * this is the code that permits to objects to keep their position while transforming. * @param {Function} actionHandler the function to wrap * @return {Function} a function with an action handler signature */ function wrapWithFixedAnchor(actionHandler) { return function(eventData, transform, x, y) { var target = transform.target, centerPoint = target.getCenterPoint(), constraint = target.translateToOriginPoint(centerPoint, transform.originX, transform.originY), actionPerformed = actionHandler(eventData, transform, x, y); target.setPositionByOrigin(constraint, transform.originX, transform.originY); return actionPerformed; }; } /** * Wrap an action handler with firing an event if the action is performed * @param {Function} actionHandler the function to wrap * @return {Function} a function with an action handler signature */ function wrapWithFireEvent(eventName, actionHandler) { return function(eventData, transform, x, y) { var actionPerformed = actionHandler(eventData, transform, x, y); if (actionPerformed) { fireEvent(eventName, commonEventInfo(eventData, transform, x, y)); } return actionPerformed; }; } /** * Transforms a point described by x and y in a distance from the top left corner of the object * bounding box. * @param {Object} transform * @param {String} originX * @param {String} originY * @param {number} x * @param {number} y * @return {Fabric.Point} the normalized point */ function getLocalPoint(transform, originX, originY, x, y) { var target = transform.target, control = target.controls[transform.corner], zoom = target.canvas.getZoom(), padding = target.padding / zoom, localPoint = target.toLocalPoint(new fabric.Point(x, y), originX, originY); if (localPoint.x >= padding) { localPoint.x -= padding; } if (localPoint.x <= -padding) { localPoint.x += padding; } if (localPoint.y >= padding) { localPoint.y -= padding; } if (localPoint.y <= padding) { localPoint.y += padding; } localPoint.x -= control.offsetX; localPoint.y -= control.offsetY; return localPoint; } /** * Detect if the fabric object is flipped on one side. * @param {fabric.Object} target * @return {Boolean} true if one flip, but not two. */ function targetHasOneFlip(target) { return target.flipX !== target.flipY; } /** * Utility function to compensate the scale factor when skew is applied on both axes * @private */ function compensateScaleForSkew(target, oppositeSkew, scaleToCompensate, axis, reference) { if (target[oppositeSkew] !== 0) { var newDim = target._getTransformedDimensions()[axis]; var newValue = reference / newDim * target[scaleToCompensate]; target.set(scaleToCompensate, newValue); } } /** * Action handler for skewing on the X axis * @private */ function skewObjectX(eventData, transform, x, y) { var target = transform.target, // find how big the object would be, if there was no skewX. takes in account scaling dimNoSkew = target._getTransformedDimensions(0, target.skewY), localPoint = getLocalPoint(transform, transform.originX, transform.originY, x, y), // the mouse is in the center of the object, and we want it to stay there. // so the object will grow twice as much as the mouse. // this makes the skew growth to localPoint * 2 - dimNoSkew. totalSkewSize = Math.abs(localPoint.x * 2) - dimNoSkew.x, currentSkew = target.skewX, newSkew; if (totalSkewSize < 2) { // let's make it easy to go back to position 0. newSkew = 0; } else { newSkew = radiansToDegrees( Math.atan2((totalSkewSize / target.scaleX), (dimNoSkew.y / target.scaleY)) ); // now we have to find the sign of the skew. // it mostly depend on the origin of transformation. if (transform.originX === LEFT && transform.originY === BOTTOM) { newSkew = -newSkew; } if (transform.originX === RIGHT && transform.originY === TOP) { newSkew = -newSkew; } if (targetHasOneFlip(target)) { newSkew = -newSkew; } } var hasSkewed = currentSkew !== newSkew; if (hasSkewed) { var dimBeforeSkewing = target._getTransformedDimensions().y; target.set('skewX', newSkew); compensateScaleForSkew(target, 'skewY', 'scaleY', 'y', dimBeforeSkewing); } return hasSkewed; } /** * Action handler for skewing on the Y axis * @private */ function skewObjectY(eventData, transform, x, y) { var target = transform.target, // find how big the object would be, if there was no skewX. takes in account scaling dimNoSkew = target._getTransformedDimensions(target.skewX, 0), localPoint = getLocalPoint(transform, transform.originX, transform.originY, x, y), // the mouse is in the center of the object, and we want it to stay there. // so the object will grow twice as much as the mouse. // this makes the skew growth to localPoint * 2 - dimNoSkew. totalSkewSize = Math.abs(localPoint.y * 2) - dimNoSkew.y, currentSkew = target.skewY, newSkew; if (totalSkewSize < 2) { // let's make it easy to go back to position 0. newSkew = 0; } else { newSkew = radiansToDegrees( Math.atan2((totalSkewSize / target.scaleY), (dimNoSkew.x / target.scaleX)) ); // now we have to find the sign of the skew. // it mostly depend on the origin of transformation. if (transform.originX === LEFT && transform.originY === BOTTOM) { newSkew = -newSkew; } if (transform.originX === RIGHT && transform.originY === TOP) { newSkew = -newSkew; } if (targetHasOneFlip(target)) { newSkew = -newSkew; } } var hasSkewed = currentSkew !== newSkew; if (hasSkewed) { var dimBeforeSkewing = target._getTransformedDimensions().x; target.set('skewY', newSkew); compensateScaleForSkew(target, 'skewX', 'scaleX', 'x', dimBeforeSkewing); } return hasSkewed; } /** * Wrapped Action handler for skewing on the Y axis, takes care of the * skew direction and determine the correct transform origin for the anchor point * @param {Event} eventData javascript event that is doing the transform * @param {Object} transform javascript object containing a series of information around the current transform * @param {number} x current mouse x position, canvas normalized * @param {number} y current mouse y position, canvas normalized * @return {Boolean} true if some change happened */ function skewHandlerX(eventData, transform, x, y) { // step1 figure out and change transform origin. // if skewX > 0 and originY bottom we anchor on right // if skewX > 0 and originY top we anchor on left // if skewX < 0 and originY bottom we anchor on left // if skewX < 0 and originY top we anchor on right // if skewX is 0, we look for mouse position to understand where are we going. var target = transform.target, currentSkew = target.skewX, originX, originY = transform.originY; if (target.lockSkewingX) { return false; } if (currentSkew === 0) { var localPointFromCenter = getLocalPoint(transform, CENTER, CENTER, x, y); if (localPointFromCenter.x > 0) { // we are pulling right, anchor left; originX = LEFT; } else { // we are pulling right, anchor right originX = RIGHT; } } else { if (currentSkew > 0) { originX = originY === TOP ? LEFT : RIGHT; } if (currentSkew < 0) { originX = originY === TOP ? RIGHT : LEFT; } // is the object flipped on one side only? swap the origin. if (targetHasOneFlip(target)) { originX = originX === LEFT ? RIGHT : LEFT; } } // once we have the origin, we find the anchor point transform.originX = originX; var finalHandler = wrapWithFireEvent('skewing', wrapWithFixedAnchor(skewObjectX)); return finalHandler(eventData, transform, x, y); } /** * Wrapped Action handler for skewing on the Y axis, takes care of the * skew direction and determine the correct transform origin for the anchor point * @param {Event} eventData javascript event that is doing the transform * @param {Object} transform javascript object containing a series of information around the current transform * @param {number} x current mouse x position, canvas normalized * @param {number} y current mouse y position, canvas normalized * @return {Boolean} true if some change happened */ function skewHandlerY(eventData, transform, x, y) { // step1 figure out and change transform origin. // if skewY > 0 and originX left we anchor on top // if skewY > 0 and originX right we anchor on bottom // if skewY < 0 and originX left we anchor on bottom // if skewY < 0 and originX right we anchor on top // if skewY is 0, we look for mouse position to understand where are we going. var target = transform.target, currentSkew = target.skewY, originY, originX = transform.originX; if (target.lockSkewingY) { return false; } if (currentSkew === 0) { var localPointFromCenter = getLocalPoint(transform, CENTER, CENTER, x, y); if (localPointFromCenter.y > 0) { // we are pulling down, anchor up; originY = TOP; } else { // we are pulling up, anchor down originY = BOTTOM; } } else { if (currentSkew > 0) { originY = originX === LEFT ? TOP : BOTTOM; } if (currentSkew < 0) { originY = originX === LEFT ? BOTTOM : TOP; } // is the object flipped on one side only? swap the origin. if (targetHasOneFlip(target)) { originY = originY === TOP ? BOTTOM : TOP; } } // once we have the origin, we find the anchor point transform.originY = originY; var finalHandler = wrapWithFireEvent('skewing', wrapWithFixedAnchor(skewObjectY)); return finalHandler(eventData, transform, x, y); } /** * Action handler for rotation and snapping, without anchor point. * Needs to be wrapped with `wrapWithFixedAnchor` to be effective * @param {Event} eventData javascript event that is doing the transform * @param {Object} transform javascript object containing a series of information around the current transform * @param {number} x current mouse x position, canvas normalized * @param {number} y current mouse y position, canvas normalized * @return {Boolean} true if some change happened * @private */ function rotationWithSnapping(eventData, transform, x, y) { var t = transform, target = t.target, pivotPoint = target.translateToOriginPoint(target.getCenterPoint(), t.originX, t.originY); if (target.lockRotation) { return false; } var lastAngle = Math.atan2(t.ey - pivotPoint.y, t.ex - pivotPoint.x), curAngle = Math.atan2(y - pivotPoint.y, x - pivotPoint.x), angle = radiansToDegrees(curAngle - lastAngle + t.theta), hasRotated = true; if (target.snapAngle > 0) { var snapAngle = target.snapAngle, snapThreshold = target.snapThreshold || snapAngle, rightAngleLocked = Math.ceil(angle / snapAngle) * snapAngle, leftAngleLocked = Math.floor(angle / snapAngle) * snapAngle; if (Math.abs(angle - leftAngleLocked) < snapThreshold) { angle = leftAngleLocked; } else if (Math.abs(angle - rightAngleLocked) < snapThreshold) { angle = rightAngleLocked; } } // normalize angle to positive value if (angle < 0) { angle = 360 + angle; } angle %= 360; hasRotated = target.angle !== angle; target.angle = angle; return hasRotated; } /** * Basic scaling logic, reused with different constrain for scaling X,Y, freely or equally. * Needs to be wrapped with `wrapWithFixedAnchor` to be effective * @param {Event} eventData javascript event that is doing the transform * @param {Object} transform javascript object containing a series of information around the current transform * @param {number} x current mouse x position, canvas normalized * @param {number} y current mouse y position, canvas normalized * @param {Object} options additional information for scaling * @param {String} options.by 'x', 'y', 'equally' or '' to indicate type of scaling * @return {Boolean} true if some change happened * @private */ function scaleObject(eventData, transform, x, y, options) { options = options || {}; var target = transform.target, lockScalingX = target.lockScalingX, lockScalingY = target.lockScalingY, by = options.by, newPoint, scaleX, scaleY, dim, scaleProportionally = scaleIsProportional(eventData, target), forbidScaling = scalingIsForbidden(target, by, scaleProportionally), signX, signY, gestureScale = transform.gestureScale; if (forbidScaling) { return false; } if (gestureScale) { scaleX = transform.scaleX * gestureScale; scaleY = transform.scaleY * gestureScale; } else { newPoint = getLocalPoint(transform, transform.originX, transform.originY, x, y); // use of sign: We use sign to detect change of direction of an action. sign usually change when // we cross the origin point with the mouse. So a scale flip for example. There is an issue when scaling // by center and scaling using one middle control ( default: mr, mt, ml, mb), the mouse movement can easily // cross many time the origin point and flip the object. so we need a way to filter out the noise. // This ternary here should be ok to filter out X scaling when we want Y only and vice versa. signX = by !== 'y' ? sign(newPoint.x) : 1; signY = by !== 'x' ? sign(newPoint.y) : 1; if (!transform.signX) { transform.signX = signX; } if (!transform.signY) { transform.signY = signY; } if (target.lockScalingFlip && (transform.signX !== signX || transform.signY !== signY) ) { return false; } dim = target._getTransformedDimensions(); // missing detection of flip and logic to switch the origin if (scaleProportionally && !by) { // uniform scaling var distance = Math.abs(newPoint.x) + Math.abs(newPoint.y), original = transform.original, originalDistance = Math.abs(dim.x * original.scaleX / target.scaleX) + Math.abs(dim.y * original.scaleY / target.scaleY), scale = distance / originalDistance; scaleX = original.scaleX * scale; scaleY = original.scaleY * scale; } else { scaleX = Math.abs(newPoint.x * target.scaleX / dim.x); scaleY = Math.abs(newPoint.y * target.scaleY / dim.y); } // if we are scaling by center, we need to double the scale if (isTransformCentered(transform)) { scaleX *= 2; scaleY *= 2; } if (transform.signX !== signX && by !== 'y') { transform.originX = opposite[transform.originX]; scaleX *= -1; transform.signX = signX; } if (transform.signY !== signY && by !== 'x') { transform.originY = opposite[transform.originY]; scaleY *= -1; transform.signY = signY; } } // minScale is taken are in the setter. var oldScaleX = target.scaleX, oldScaleY = target.scaleY; if (!by) { !lockScalingX && target.set('scaleX', scaleX); !lockScalingY && target.set('scaleY', scaleY); } else { // forbidden cases already handled on top here. by === 'x' && target.set('scaleX', scaleX); by === 'y' && target.set('scaleY', scaleY); } return oldScaleX !== target.scaleX || oldScaleY !== target.scaleY; } /** * Generic scaling logic, to scale from corners either equally or freely. * Needs to be wrapped with `wrapWithFixedAnchor` to be effective * @param {Event} eventData javascript event that is doing the transform * @param {Object} transform javascript object containing a series of information around the current transform * @param {number} x current mouse x position, canvas normalized * @param {number} y current mouse y position, canvas normalized * @return {Boolean} true if some change happened */ function scaleObjectFromCorner(eventData, transform, x, y) { return scaleObject(eventData, transform, x, y); } /** * Scaling logic for the X axis. * Needs to be wrapped with `wrapWithFixedAnchor` to be effective * @param {Event} eventData javascript event that is doing the transform * @param {Object} transform javascript object containing a series of information around the current transform * @param {number} x current mouse x position, canvas normalized * @param {number} y current mouse y position, canvas normalized * @return {Boolean} true if some change happened */ function scaleObjectX(eventData, transform, x, y) { return scaleObject(eventData, transform, x, y , { by: 'x' }); } /** * Scaling logic for the Y axis. * Needs to be wrapped with `wrapWithFixedAnchor` to be effective * @param {Event} eventData javascript event that is doing the transform * @param {Object} transform javascript object containing a series of information around the current transform * @param {number} x current mouse x position, canvas normalized * @param {number} y current mouse y position, canvas normalized * @return {Boolean} true if some change happened */ function scaleObjectY(eventData, transform, x, y) { return scaleObject(eventData, transform, x, y , { by: 'y' }); } /** * Composed action handler to either scale Y or skew X * Needs to be wrapped with `wrapWithFixedAnchor` to be effective * @param {Event} eventData javascript event that is doing the transform * @param {Object} transform javascript object containing a series of information around the current transform * @param {number} x current mouse x position, canvas normalized * @param {number} y current mouse y position, canvas normalized * @return {Boolean} true if some change happened */ function scalingYOrSkewingX(eventData, transform, x, y) { // ok some safety needed here. if (eventData[transform.target.canvas.altActionKey]) { return controls.skewHandlerX(eventData, transform, x, y); } return controls.scalingY(eventData, transform, x, y); } /** * Composed action handler to either scale X or skew Y * Needs to be wrapped with `wrapWithFixedAnchor` to be effective * @param {Event} eventData javascript event that is doing the transform * @param {Object} transform javascript object containing a series of information around the current transform * @param {number} x current mouse x position, canvas normalized * @param {number} y current mouse y position, canvas normalized * @return {Boolean} true if some change happened */ function scalingXOrSkewingY(eventData, transform, x, y) { // ok some safety needed here. if (eventData[transform.target.canvas.altActionKey]) { return controls.skewHandlerY(eventData, transform, x, y); } return controls.scalingX(eventData, transform, x, y); } /** * Action handler to change textbox width * Needs to be wrapped with `wrapWithFixedAnchor` to be effective * @param {Event} eventData javascript event that is doing the transform * @param {Object} transform javascript object containing a series of information around the current transform * @param {number} x current mouse x position, canvas normalized * @param {number} y current mouse y position, canvas normalized * @return {Boolean} true if some change happened */ function changeWidth(eventData, transform, x, y) { var target = transform.target, localPoint = getLocalPoint(transform, transform.originX, transform.originY, x, y), strokePadding = target.strokeWidth / (target.strokeUniform ? target.scaleX : 1), multiplier = isTransformCentered(transform) ? 2 : 1, oldWidth = target.width, newWidth = Math.abs(localPoint.x * multiplier / target.scaleX) - strokePadding; target.set('width', Math.max(newWidth, 0)); return oldWidth !== newWidth; } /** * Action handler * @private * @param {Event} eventData javascript event that is doing the transform * @param {Object} transform javascript object containing a series of information around the current transform * @param {number} x current mouse x position, canvas normalized * @param {number} y current mouse y position, canvas normalized * @return {Boolean} true if the translation occurred */ function dragHandler(eventData, transform, x, y) { var target = transform.target, newLeft = x - transform.offsetX, newTop = y - transform.offsetY, moveX = !target.get('lockMovementX') && target.left !== newLeft, moveY = !target.get('lockMovementY') && target.top !== newTop; moveX && target.set('left', newLeft); moveY && target.set('top', newTop); if (moveX || moveY) { fireEvent('moving', commonEventInfo(eventData, transform, x, y)); } return moveX || moveY; } controls.scaleCursorStyleHandler = scaleCursorStyleHandler; controls.skewCursorStyleHandler = skewCursorStyleHandler; controls.scaleSkewCursorStyleHandler = scaleSkewCursorStyleHandler; controls.rotationWithSnapping = wrapWithFireEvent('rotating', wrapWithFixedAnchor(rotationWithSnapping)); controls.scalingEqually = wrapWithFireEvent('scaling', wrapWithFixedAnchor( scaleObjectFromCorner)); controls.scalingX = wrapWithFireEvent('scaling', wrapWithFixedAnchor(scaleObjectX)); controls.scalingY = wrapWithFireEvent('scaling', wrapWithFixedAnchor(scaleObjectY)); controls.scalingYOrSkewingX = scalingYOrSkewingX; controls.scalingXOrSkewingY = scalingXOrSkewingY; controls.changeWidth = wrapWithFireEvent('resizing', wrapWithFixedAnchor(changeWidth)); controls.skewHandlerX = skewHandlerX; controls.skewHandlerY = skewHandlerY; controls.dragHandler = dragHandler; controls.scaleOrSkewActionName = scaleOrSkewActionName; controls.rotationStyleHandler = rotationStyleHandler; controls.fireEvent = fireEvent; controls.wrapWithFixedAnchor = wrapWithFixedAnchor; controls.wrapWithFireEvent = wrapWithFireEvent; controls.getLocalPoint = getLocalPoint; fabric.controlsUtils = controls; })(typeof exports !== 'undefined' ? exports : this);