mirror of
https://github.com/Hopiu/fabric.js.git
synced 2026-05-18 10:31:08 +00:00
418 lines
13 KiB
JavaScript
418 lines
13 KiB
JavaScript
(function(){
|
|
|
|
var getPointer = fabric.util.getPointer,
|
|
degreesToRadians = fabric.util.degreesToRadians;
|
|
|
|
fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {
|
|
|
|
/**
|
|
* Determines which one of the four corners has been clicked
|
|
* @private
|
|
* @param {Event} e Event object
|
|
* @param {Object} offset Canvas offset
|
|
* @return {String|Boolean} corner code (tl, tr, bl, br, etc.), or false if nothing is found
|
|
*/
|
|
_findTargetCorner: function(e, offset) {
|
|
if (!this.hasControls || !this.active) return false;
|
|
|
|
var pointer = this.canvas.getPointer(e, true),
|
|
ex = pointer.x,
|
|
ey = pointer.y,
|
|
xPoints,
|
|
lines;
|
|
|
|
for (var i in this.oCoords) {
|
|
|
|
if (i === 'mtr' && !this.hasRotatingPoint) {
|
|
continue;
|
|
}
|
|
|
|
if (this.get('lockUniScaling') && (i === 'mt' || i === 'mr' || i === 'mb' || i === 'ml')) {
|
|
continue;
|
|
}
|
|
|
|
lines = this._getImageLines(this.oCoords[i].corner);
|
|
|
|
// debugging
|
|
|
|
// canvas.contextTop.fillRect(lines.bottomline.d.x, lines.bottomline.d.y, 2, 2);
|
|
// canvas.contextTop.fillRect(lines.bottomline.o.x, lines.bottomline.o.y, 2, 2);
|
|
|
|
// canvas.contextTop.fillRect(lines.leftline.d.x, lines.leftline.d.y, 2, 2);
|
|
// canvas.contextTop.fillRect(lines.leftline.o.x, lines.leftline.o.y, 2, 2);
|
|
|
|
// canvas.contextTop.fillRect(lines.topline.d.x, lines.topline.d.y, 2, 2);
|
|
// canvas.contextTop.fillRect(lines.topline.o.x, lines.topline.o.y, 2, 2);
|
|
|
|
// canvas.contextTop.fillRect(lines.rightline.d.x, lines.rightline.d.y, 2, 2);
|
|
// canvas.contextTop.fillRect(lines.rightline.o.x, lines.rightline.o.y, 2, 2);
|
|
|
|
xPoints = this._findCrossPoints({x: ex, y: ey}, lines);
|
|
if (xPoints !== 0 && xPoints % 2 === 1) {
|
|
this.__corner = i;
|
|
return i;
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
|
|
/**
|
|
* Sets the coordinates of the draggable boxes in the corners of
|
|
* the image used to scale/rotate it.
|
|
* @private
|
|
*/
|
|
_setCornerCoords: function() {
|
|
var coords = this.oCoords,
|
|
theta = degreesToRadians(this.angle),
|
|
newTheta = degreesToRadians(45 - this.angle),
|
|
cornerHypotenuse = Math.sqrt(2 * Math.pow(this.cornerSize, 2)) / 2,
|
|
cosHalfOffset = cornerHypotenuse * Math.cos(newTheta),
|
|
sinHalfOffset = cornerHypotenuse * Math.sin(newTheta),
|
|
sinTh = Math.sin(theta),
|
|
cosTh = Math.cos(theta);
|
|
|
|
coords.tl.corner = {
|
|
tl: {
|
|
x: coords.tl.x - sinHalfOffset,
|
|
y: coords.tl.y - cosHalfOffset
|
|
},
|
|
tr: {
|
|
x: coords.tl.x + cosHalfOffset,
|
|
y: coords.tl.y - sinHalfOffset
|
|
},
|
|
bl: {
|
|
x: coords.tl.x - cosHalfOffset,
|
|
y: coords.tl.y + sinHalfOffset
|
|
},
|
|
br: {
|
|
x: coords.tl.x + sinHalfOffset,
|
|
y: coords.tl.y + cosHalfOffset
|
|
}
|
|
};
|
|
|
|
coords.tr.corner = {
|
|
tl: {
|
|
x: coords.tr.x - sinHalfOffset,
|
|
y: coords.tr.y - cosHalfOffset
|
|
},
|
|
tr: {
|
|
x: coords.tr.x + cosHalfOffset,
|
|
y: coords.tr.y - sinHalfOffset
|
|
},
|
|
br: {
|
|
x: coords.tr.x + sinHalfOffset,
|
|
y: coords.tr.y + cosHalfOffset
|
|
},
|
|
bl: {
|
|
x: coords.tr.x - cosHalfOffset,
|
|
y: coords.tr.y + sinHalfOffset
|
|
}
|
|
};
|
|
|
|
coords.bl.corner = {
|
|
tl: {
|
|
x: coords.bl.x - sinHalfOffset,
|
|
y: coords.bl.y - cosHalfOffset
|
|
},
|
|
bl: {
|
|
x: coords.bl.x - cosHalfOffset,
|
|
y: coords.bl.y + sinHalfOffset
|
|
},
|
|
br: {
|
|
x: coords.bl.x + sinHalfOffset,
|
|
y: coords.bl.y + cosHalfOffset
|
|
},
|
|
tr: {
|
|
x: coords.bl.x + cosHalfOffset,
|
|
y: coords.bl.y - sinHalfOffset
|
|
}
|
|
};
|
|
|
|
coords.br.corner = {
|
|
tr: {
|
|
x: coords.br.x + cosHalfOffset,
|
|
y: coords.br.y - sinHalfOffset
|
|
},
|
|
bl: {
|
|
x: coords.br.x - cosHalfOffset,
|
|
y: coords.br.y + sinHalfOffset
|
|
},
|
|
br: {
|
|
x: coords.br.x + sinHalfOffset,
|
|
y: coords.br.y + cosHalfOffset
|
|
},
|
|
tl: {
|
|
x: coords.br.x - sinHalfOffset,
|
|
y: coords.br.y - cosHalfOffset
|
|
}
|
|
};
|
|
|
|
coords.ml.corner = {
|
|
tl: {
|
|
x: coords.ml.x - sinHalfOffset,
|
|
y: coords.ml.y - cosHalfOffset
|
|
},
|
|
tr: {
|
|
x: coords.ml.x + cosHalfOffset,
|
|
y: coords.ml.y - sinHalfOffset
|
|
},
|
|
bl: {
|
|
x: coords.ml.x - cosHalfOffset,
|
|
y: coords.ml.y + sinHalfOffset
|
|
},
|
|
br: {
|
|
x: coords.ml.x + sinHalfOffset,
|
|
y: coords.ml.y + cosHalfOffset
|
|
}
|
|
};
|
|
|
|
coords.mt.corner = {
|
|
tl: {
|
|
x: coords.mt.x - sinHalfOffset,
|
|
y: coords.mt.y - cosHalfOffset
|
|
},
|
|
tr: {
|
|
x: coords.mt.x + cosHalfOffset,
|
|
y: coords.mt.y - sinHalfOffset
|
|
},
|
|
bl: {
|
|
x: coords.mt.x - cosHalfOffset,
|
|
y: coords.mt.y + sinHalfOffset
|
|
},
|
|
br: {
|
|
x: coords.mt.x + sinHalfOffset,
|
|
y: coords.mt.y + cosHalfOffset
|
|
}
|
|
};
|
|
|
|
coords.mr.corner = {
|
|
tl: {
|
|
x: coords.mr.x - sinHalfOffset,
|
|
y: coords.mr.y - cosHalfOffset
|
|
},
|
|
tr: {
|
|
x: coords.mr.x + cosHalfOffset,
|
|
y: coords.mr.y - sinHalfOffset
|
|
},
|
|
bl: {
|
|
x: coords.mr.x - cosHalfOffset,
|
|
y: coords.mr.y + sinHalfOffset
|
|
},
|
|
br: {
|
|
x: coords.mr.x + sinHalfOffset,
|
|
y: coords.mr.y + cosHalfOffset
|
|
}
|
|
};
|
|
|
|
coords.mb.corner = {
|
|
tl: {
|
|
x: coords.mb.x - sinHalfOffset,
|
|
y: coords.mb.y - cosHalfOffset
|
|
},
|
|
tr: {
|
|
x: coords.mb.x + cosHalfOffset,
|
|
y: coords.mb.y - sinHalfOffset
|
|
},
|
|
bl: {
|
|
x: coords.mb.x - cosHalfOffset,
|
|
y: coords.mb.y + sinHalfOffset
|
|
},
|
|
br: {
|
|
x: coords.mb.x + sinHalfOffset,
|
|
y: coords.mb.y + cosHalfOffset
|
|
}
|
|
};
|
|
|
|
coords.mtr.corner = {
|
|
tl: {
|
|
x: coords.mtr.x - sinHalfOffset + (sinTh * this.rotatingPointOffset),
|
|
y: coords.mtr.y - cosHalfOffset - (cosTh * this.rotatingPointOffset)
|
|
},
|
|
tr: {
|
|
x: coords.mtr.x + cosHalfOffset + (sinTh * this.rotatingPointOffset),
|
|
y: coords.mtr.y - sinHalfOffset - (cosTh * this.rotatingPointOffset)
|
|
},
|
|
bl: {
|
|
x: coords.mtr.x - cosHalfOffset + (sinTh * this.rotatingPointOffset),
|
|
y: coords.mtr.y + sinHalfOffset - (cosTh * this.rotatingPointOffset)
|
|
},
|
|
br: {
|
|
x: coords.mtr.x + sinHalfOffset + (sinTh * this.rotatingPointOffset),
|
|
y: coords.mtr.y + cosHalfOffset - (cosTh * this.rotatingPointOffset)
|
|
}
|
|
};
|
|
},
|
|
/**
|
|
* Draws borders of an object's bounding box.
|
|
* Requires public properties: width, height
|
|
* Requires public options: padding, borderColor
|
|
* @param {CanvasRenderingContext2D} ctx Context to draw on
|
|
* @return {fabric.Object} thisArg
|
|
* @chainable
|
|
*/
|
|
drawBorders: function(ctx) {
|
|
if (!this.hasBorders) return this;
|
|
|
|
var padding = this.padding,
|
|
padding2 = padding * 2,
|
|
strokeWidth = ~~(this.strokeWidth / 2) * 2; // Round down to even number
|
|
|
|
ctx.save();
|
|
|
|
ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
|
|
ctx.strokeStyle = this.borderColor;
|
|
|
|
var scaleX = 1 / this._constrainScale(this.scaleX),
|
|
scaleY = 1 / this._constrainScale(this.scaleY);
|
|
|
|
ctx.lineWidth = 1 / this.borderScaleFactor;
|
|
|
|
var vpt = this.canvas.viewportTransform,
|
|
wh = fabric.util.transformPoint(new fabric.Point(this.getWidth(), this.getHeight()), vpt, true),
|
|
sxy = fabric.util.transformPoint(new fabric.Point(scaleX, scaleY), vpt, true),
|
|
w = wh.x,
|
|
h = wh.y,
|
|
sx= sxy.x,
|
|
sy= sxy.y;
|
|
if (this.group) {
|
|
w = w * this.group.scaleX;
|
|
h = h * this.group.scaleY;
|
|
}
|
|
|
|
ctx.strokeRect(
|
|
~~(-(w / 2) - padding - strokeWidth / 2 * sx) - 0.5, // offset needed to make lines look sharper
|
|
~~(-(h / 2) - padding - strokeWidth / 2 * sy) - 0.5,
|
|
~~(w + padding2 + strokeWidth * sx) + 1, // double offset needed to make lines look sharper
|
|
~~(h + padding2 + strokeWidth * sy) + 1
|
|
);
|
|
|
|
if (this.hasRotatingPoint && !this.get('lockRotation') && this.hasControls) {
|
|
|
|
var rotateHeight = (
|
|
this.flipY
|
|
? h + (strokeWidth * sx) + (padding * 2)
|
|
: -h - (strokeWidth * sy) - (padding * 2)
|
|
) / 2;
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(0, rotateHeight);
|
|
ctx.lineTo(0, rotateHeight + (this.flipY ? this.rotatingPointOffset : -this.rotatingPointOffset));
|
|
ctx.closePath();
|
|
ctx.stroke();
|
|
}
|
|
|
|
ctx.restore();
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Draws corners of an object's bounding box.
|
|
* Requires public properties: width, height
|
|
* Requires public options: cornerSize, padding
|
|
* @param {CanvasRenderingContext2D} ctx Context to draw on
|
|
* @return {fabric.Object} thisArg
|
|
* @chainable
|
|
*/
|
|
drawControls: function(ctx) {
|
|
if (!this.hasControls) return this;
|
|
|
|
var size = this.cornerSize,
|
|
size2 = size / 2,
|
|
strokeWidth2 = ~~(this.strokeWidth / 2), // half strokeWidth rounded down
|
|
wh = fabric.util.transformPoint(new fabric.Point(this.getWidth(), this.getHeight()), this.canvas.viewportTransform, true),
|
|
width = wh.x,
|
|
height = wh.y,
|
|
left = -(width / 2),
|
|
top = -(height / 2),
|
|
_left,
|
|
_top,
|
|
padding = this.padding,
|
|
scaleOffset = size2,
|
|
scaleOffsetSize = size2 - size,
|
|
methodName = this.transparentCorners ? 'strokeRect' : 'fillRect',
|
|
transparent = this.transparentCorners,
|
|
isVML = typeof G_vmlCanvasManager !== 'undefined';
|
|
|
|
ctx.save();
|
|
|
|
ctx.lineWidth = 1;
|
|
|
|
ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
|
|
ctx.strokeStyle = ctx.fillStyle = this.cornerColor;
|
|
|
|
// top-left
|
|
_left = left - scaleOffset - strokeWidth2 - padding;
|
|
_top = top - scaleOffset - strokeWidth2 - padding;
|
|
|
|
isVML || transparent || ctx.clearRect(_left, _top, size, size);
|
|
ctx[methodName](_left, _top, size, size);
|
|
|
|
// top-right
|
|
_left = left + width - scaleOffset + strokeWidth2 + padding;
|
|
_top = top - scaleOffset - strokeWidth2 - padding;
|
|
|
|
isVML || transparent || ctx.clearRect(_left, _top, size, size);
|
|
ctx[methodName](_left, _top, size, size);
|
|
|
|
// bottom-left
|
|
_left = left - scaleOffset - strokeWidth2 - padding;
|
|
_top = top + height + scaleOffsetSize + strokeWidth2 + padding;
|
|
|
|
isVML || transparent || ctx.clearRect(_left, _top, size, size);
|
|
ctx[methodName](_left, _top, size, size);
|
|
|
|
// bottom-right
|
|
_left = left + width + scaleOffsetSize + strokeWidth2 + padding;
|
|
_top = top + height + scaleOffsetSize + strokeWidth2 + padding;
|
|
|
|
isVML || transparent || ctx.clearRect(_left, _top, size, size);
|
|
ctx[methodName](_left, _top, size, size);
|
|
|
|
if (!this.get('lockUniScaling')) {
|
|
// middle-top
|
|
_left = left + width/2 - scaleOffset;
|
|
_top = top - scaleOffset - strokeWidth2 - padding;
|
|
|
|
isVML || transparent || ctx.clearRect(_left, _top, size, size);
|
|
ctx[methodName](_left, _top, size, size);
|
|
|
|
// middle-bottom
|
|
_left = left + width/2 - scaleOffset;
|
|
_top = top + height + scaleOffsetSize + strokeWidth2 + padding;
|
|
|
|
isVML || transparent || ctx.clearRect(_left, _top, size, size);
|
|
ctx[methodName](_left, _top, size, size);
|
|
|
|
// middle-right
|
|
_left = left + width + scaleOffsetSize + strokeWidth2 + padding;
|
|
_top = top + height/2 - scaleOffset;
|
|
|
|
isVML || transparent || ctx.clearRect(_left, _top, size, size);
|
|
ctx[methodName](_left, _top, size, size);
|
|
|
|
// middle-left
|
|
_left = left - scaleOffset - strokeWidth2 - padding;
|
|
_top = top + height/2 - scaleOffset;
|
|
|
|
isVML || transparent || ctx.clearRect(_left, _top, size, size);
|
|
ctx[methodName](_left, _top, size, size);
|
|
}
|
|
|
|
// middle-top-rotate
|
|
if (this.hasRotatingPoint) {
|
|
|
|
_left = left + width/2 - scaleOffset;
|
|
_top = this.flipY ?
|
|
(top + height + (this.rotatingPointOffset) - size2 + strokeWidth2 + padding)
|
|
: (top - (this.rotatingPointOffset) - size2 - strokeWidth2 - padding);
|
|
|
|
isVML || transparent || ctx.clearRect(_left, _top, size, size);
|
|
ctx[methodName](_left, _top, size, size);
|
|
}
|
|
|
|
ctx.restore();
|
|
|
|
return this;
|
|
}
|
|
});
|
|
})();
|