UNPKG

fabric

Version:

Object model for HTML5 canvas, and SVG-to-canvas parser. Backed by jsdom and node-canvas.

1 lines 12.9 kB
{"version":3,"file":"scale.mjs","names":[],"sources":["../../../src/controls/scale.ts"],"sourcesContent":["import type {\n ControlCursorCallback,\n TPointerEvent,\n Transform,\n TransformActionHandler,\n} from '../EventTypeDefs';\nimport type { FabricObject } from '../shapes/Object/FabricObject';\nimport type { TAxis } from '../typedefs';\nimport type { Canvas } from '../canvas/Canvas';\nimport {\n findCornerQuadrant,\n getLocalPoint,\n invertOrigin,\n isLocked,\n isTransformCentered,\n NOT_ALLOWED_CURSOR,\n} from './util';\nimport { wrapWithFireEvent } from './wrapWithFireEvent';\nimport { wrapWithFixedAnchor } from './wrapWithFixedAnchor';\nimport { SCALE_X, SCALE_Y, SCALING } from '../constants';\n\ntype ScaleTransform = Transform & {\n gestureScale?: number;\n signX?: number;\n signY?: number;\n};\n\ntype ScaleBy = TAxis | 'equally' | '' | undefined;\n\n/**\n * Inspect event and fabricObject properties to understand if the scaling action\n * @param {Event} eventData from the user action\n * @param {FabricObject} fabricObject the fabric object about to scale\n * @return {Boolean} true if scale is proportional\n */\nexport function scaleIsProportional(\n eventData: TPointerEvent,\n fabricObject: FabricObject,\n): boolean {\n const canvas = fabricObject.canvas as Canvas,\n uniformIsToggled = eventData[canvas.uniScaleKey!];\n return (\n (canvas.uniformScaling && !uniformIsToggled) ||\n (!canvas.uniformScaling && uniformIsToggled)\n );\n}\n\n/**\n * Inspect fabricObject to understand if the current scaling action is allowed\n * @param {FabricObject} fabricObject the fabric object about to scale\n * @param {String} by 'x' or 'y' or ''\n * @param {Boolean} scaleProportionally true if we are trying to scale proportionally\n * @return {Boolean} true if scaling is not allowed at current conditions\n */\nexport function scalingIsForbidden(\n fabricObject: FabricObject,\n by: ScaleBy,\n scaleProportionally: boolean,\n) {\n const lockX = isLocked(fabricObject, 'lockScalingX'),\n lockY = isLocked(fabricObject, 'lockScalingY');\n if (lockX && lockY) {\n return true;\n }\n if (!by && (lockX || lockY) && scaleProportionally) {\n return true;\n }\n if (lockX && by === 'x') {\n return true;\n }\n if (lockY && by === 'y') {\n return true;\n }\n // code crashes because of a division by 0 if a 0 sized object is scaled\n // forbid to prevent scaling to happen. ISSUE-9475\n const { width, height, strokeWidth } = fabricObject;\n if (width === 0 && strokeWidth === 0 && by !== 'y') {\n return true;\n }\n if (height === 0 && strokeWidth === 0 && by !== 'x') {\n return true;\n }\n return false;\n}\n\nconst scaleMap = ['e', 'se', 's', 'sw', 'w', 'nw', 'n', 'ne', 'e'];\n\n/**\n * return the correct cursor style for the scale action\n * @param {Event} eventData the javascript event that is causing the scale\n * @param {Control} control the control that is interested in the action\n * @param {FabricObject} fabricObject the fabric object that is interested in the action\n * @return {String} a valid css string for the cursor\n */\nexport const scaleCursorStyleHandler: ControlCursorCallback = (\n eventData,\n control,\n fabricObject,\n coord,\n) => {\n const scaleProportionally = scaleIsProportional(eventData, fabricObject),\n by =\n control.x !== 0 && control.y === 0\n ? 'x'\n : control.x === 0 && control.y !== 0\n ? 'y'\n : '';\n if (scalingIsForbidden(fabricObject, by, scaleProportionally)) {\n return NOT_ALLOWED_CURSOR;\n }\n const n = findCornerQuadrant(fabricObject, control, coord);\n return `${scaleMap[n]}-resize`;\n};\n\n/**\n * Basic scaling logic, reused with different constrain for scaling X,Y, freely or equally.\n * Needs to be wrapped with `wrapWithFixedAnchor` to be effective\n * @param {Event} eventData javascript event that is doing the transform\n * @param {Object} transform javascript object containing a series of information around the current transform\n * @param {number} x current mouse x position, canvas normalized\n * @param {number} y current mouse y position, canvas normalized\n * @param {Object} options additional information for scaling\n * @param {String} options.by 'x', 'y', 'equally' or '' to indicate type of scaling\n * @return {Boolean} true if some change happened\n * @private\n */\nfunction scaleObject(\n eventData: TPointerEvent,\n transform: ScaleTransform,\n x: number,\n y: number,\n options: { by?: ScaleBy } = {},\n) {\n const target = transform.target,\n by = options.by,\n scaleProportionally = scaleIsProportional(eventData, target),\n forbidScaling = scalingIsForbidden(target, by, scaleProportionally);\n let newPoint, scaleX, scaleY, dim, signX, signY;\n\n if (forbidScaling) {\n return false;\n }\n if (transform.gestureScale) {\n scaleX = transform.scaleX * transform.gestureScale;\n scaleY = transform.scaleY * transform.gestureScale;\n } else {\n newPoint = getLocalPoint(\n transform,\n transform.originX,\n transform.originY,\n x,\n y,\n );\n // use of sign: We use sign to detect change of direction of an action. sign usually change when\n // we cross the origin point with the mouse. So a scale flip for example. There is an issue when scaling\n // by center and scaling using one middle control ( default: mr, mt, ml, mb), the mouse movement can easily\n // cross many time the origin point and flip the object. so we need a way to filter out the noise.\n // This ternary here should be ok to filter out X scaling when we want Y only and vice versa.\n signX = by !== 'y' ? Math.sign(newPoint.x || transform.signX || 1) : 1;\n signY = by !== 'x' ? Math.sign(newPoint.y || transform.signY || 1) : 1;\n if (!transform.signX) {\n transform.signX = signX;\n }\n if (!transform.signY) {\n transform.signY = signY;\n }\n\n if (\n isLocked(target, 'lockScalingFlip') &&\n (transform.signX !== signX || transform.signY !== signY)\n ) {\n return false;\n }\n\n dim = target._getTransformedDimensions();\n // missing detection of flip and logic to switch the origin\n if (scaleProportionally && !by) {\n // uniform scaling\n const distance = Math.abs(newPoint.x) + Math.abs(newPoint.y),\n { original } = transform,\n originalDistance =\n Math.abs((dim.x * original.scaleX) / target.scaleX) +\n Math.abs((dim.y * original.scaleY) / target.scaleY),\n scale = distance / originalDistance;\n scaleX = original.scaleX * scale;\n scaleY = original.scaleY * scale;\n } else {\n scaleX = Math.abs((newPoint.x * target.scaleX) / dim.x);\n scaleY = Math.abs((newPoint.y * target.scaleY) / dim.y);\n }\n // if we are scaling by center, we need to double the scale\n if (isTransformCentered(transform)) {\n scaleX *= 2;\n scaleY *= 2;\n }\n if (transform.signX !== signX && by !== 'y') {\n transform.originX = invertOrigin(transform.originX);\n scaleX *= -1;\n transform.signX = signX;\n }\n if (transform.signY !== signY && by !== 'x') {\n transform.originY = invertOrigin(transform.originY);\n scaleY *= -1;\n transform.signY = signY;\n }\n }\n // minScale is taken care of in the setter.\n const oldScaleX = target.scaleX,\n oldScaleY = target.scaleY;\n if (!by) {\n !isLocked(target, 'lockScalingX') && target.set(SCALE_X, scaleX);\n !isLocked(target, 'lockScalingY') && target.set(SCALE_Y, scaleY);\n } else {\n // forbidden cases already handled on top here.\n by === 'x' && target.set(SCALE_X, scaleX);\n by === 'y' && target.set(SCALE_Y, scaleY);\n }\n return oldScaleX !== target.scaleX || oldScaleY !== target.scaleY;\n}\n\n/**\n * Generic scaling logic, to scale from corners either equally or freely.\n * Needs to be wrapped with `wrapWithFixedAnchor` to be effective\n * @param {Event} eventData javascript event that is doing the transform\n * @param {Object} transform javascript object containing a series of information around the current transform\n * @param {number} x current mouse x position, canvas normalized\n * @param {number} y current mouse y position, canvas normalized\n * @return {Boolean} true if some change happened\n */\nexport const scaleObjectFromCorner: TransformActionHandler = (\n eventData,\n transform,\n x,\n y,\n) => {\n return scaleObject(eventData, transform, x, y);\n};\n\n/**\n * Scaling logic for the X axis.\n * Needs to be wrapped with `wrapWithFixedAnchor` to be effective\n * @param {Event} eventData javascript event that is doing the transform\n * @param {Object} transform javascript object containing a series of information around the current transform\n * @param {number} x current mouse x position, canvas normalized\n * @param {number} y current mouse y position, canvas normalized\n * @return {Boolean} true if some change happened\n */\nconst scaleObjectX: TransformActionHandler = (eventData, transform, x, y) => {\n return scaleObject(eventData, transform, x, y, { by: 'x' });\n};\n\n/**\n * Scaling logic for the Y axis.\n * Needs to be wrapped with `wrapWithFixedAnchor` to be effective\n * @param {Event} eventData javascript event that is doing the transform\n * @param {Object} transform javascript object containing a series of information around the current transform\n * @param {number} x current mouse x position, canvas normalized\n * @param {number} y current mouse y position, canvas normalized\n * @return {Boolean} true if some change happened\n */\nconst scaleObjectY: TransformActionHandler = (eventData, transform, x, y) => {\n return scaleObject(eventData, transform, x, y, { by: 'y' });\n};\n\nexport const scalingEqually = wrapWithFireEvent(\n SCALING,\n wrapWithFixedAnchor(scaleObjectFromCorner),\n);\n\nexport const scalingX = wrapWithFireEvent(\n SCALING,\n wrapWithFixedAnchor(scaleObjectX),\n);\n\nexport const scalingY = wrapWithFireEvent(\n SCALING,\n wrapWithFixedAnchor(scaleObjectY),\n);\n"],"mappings":"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"}