fabric
Version:
Object model for HTML5 canvas, and SVG-to-canvas parser. Backed by jsdom and node-canvas.
159 lines (158 loc) • 7.61 kB
JavaScript
import { SCALING } from "../constants.mjs";
import { NOT_ALLOWED_CURSOR, findCornerQuadrant, getLocalPoint, invertOrigin, isLocked, isTransformCentered } from "./util.mjs";
import { wrapWithFireEvent } from "./wrapWithFireEvent.mjs";
import { wrapWithFixedAnchor } from "./wrapWithFixedAnchor.mjs";
//#region src/controls/scale.ts
/**
* Inspect event and fabricObject properties to understand if the scaling action
* @param {Event} eventData from the user action
* @param {FabricObject} fabricObject the fabric object about to scale
* @return {Boolean} true if scale is proportional
*/
function scaleIsProportional(eventData, fabricObject) {
const canvas = fabricObject.canvas, uniformIsToggled = eventData[canvas.uniScaleKey];
return canvas.uniformScaling && !uniformIsToggled || !canvas.uniformScaling && uniformIsToggled;
}
/**
* Inspect fabricObject to understand if the current scaling action is allowed
* @param {FabricObject} 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) {
const lockX = isLocked(fabricObject, "lockScalingX"), lockY = isLocked(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;
const { width, height, strokeWidth } = fabricObject;
if (width === 0 && strokeWidth === 0 && by !== "y") return true;
if (height === 0 && strokeWidth === 0 && by !== "x") return true;
return false;
}
const scaleMap = [
"e",
"se",
"s",
"sw",
"w",
"nw",
"n",
"ne",
"e"
];
/**
* return the correct cursor style for the scale action
* @param {Event} eventData the javascript event that is causing the scale
* @param {Control} control the control that is interested in the action
* @param {FabricObject} fabricObject the fabric object that is interested in the action
* @return {String} a valid css string for the cursor
*/
const scaleCursorStyleHandler = (eventData, control, fabricObject, coord) => {
const scaleProportionally = scaleIsProportional(eventData, fabricObject);
if (scalingIsForbidden(fabricObject, control.x !== 0 && control.y === 0 ? "x" : control.x === 0 && control.y !== 0 ? "y" : "", scaleProportionally)) return NOT_ALLOWED_CURSOR;
return `${scaleMap[findCornerQuadrant(fabricObject, control, coord)]}-resize`;
};
/**
* 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 = {}) {
const target = transform.target, by = options.by, scaleProportionally = scaleIsProportional(eventData, target), forbidScaling = scalingIsForbidden(target, by, scaleProportionally);
let newPoint, scaleX, scaleY, dim, signX, signY;
if (forbidScaling) return false;
if (transform.gestureScale) {
scaleX = transform.scaleX * transform.gestureScale;
scaleY = transform.scaleY * transform.gestureScale;
} else {
newPoint = getLocalPoint(transform, transform.originX, transform.originY, x, y);
signX = by !== "y" ? Math.sign(newPoint.x || transform.signX || 1) : 1;
signY = by !== "x" ? Math.sign(newPoint.y || transform.signY || 1) : 1;
if (!transform.signX) transform.signX = signX;
if (!transform.signY) transform.signY = signY;
if (isLocked(target, "lockScalingFlip") && (transform.signX !== signX || transform.signY !== signY)) return false;
dim = target._getTransformedDimensions();
if (scaleProportionally && !by) {
const distance = Math.abs(newPoint.x) + Math.abs(newPoint.y), { original } = transform, scale = distance / (Math.abs(dim.x * original.scaleX / target.scaleX) + Math.abs(dim.y * original.scaleY / target.scaleY));
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 (isTransformCentered(transform)) {
scaleX *= 2;
scaleY *= 2;
}
if (transform.signX !== signX && by !== "y") {
transform.originX = invertOrigin(transform.originX);
scaleX *= -1;
transform.signX = signX;
}
if (transform.signY !== signY && by !== "x") {
transform.originY = invertOrigin(transform.originY);
scaleY *= -1;
transform.signY = signY;
}
}
const oldScaleX = target.scaleX, oldScaleY = target.scaleY;
if (!by) {
!isLocked(target, "lockScalingX") && target.set("scaleX", scaleX);
!isLocked(target, "lockScalingY") && target.set("scaleY", scaleY);
} else {
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
*/
const 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
*/
const 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
*/
const scaleObjectY = (eventData, transform, x, y) => {
return scaleObject(eventData, transform, x, y, { by: "y" });
};
const scalingEqually = wrapWithFireEvent(SCALING, wrapWithFixedAnchor(scaleObjectFromCorner));
const scalingX = wrapWithFireEvent(SCALING, wrapWithFixedAnchor(scaleObjectX));
const scalingY = wrapWithFireEvent(SCALING, wrapWithFixedAnchor(scaleObjectY));
//#endregion
export { scaleCursorStyleHandler, scalingEqually, scalingX, scalingY };
//# sourceMappingURL=scale.mjs.map