mirror of
https://github.com/konvajs/konva.git
synced 2025-04-05 20:48:28 +08:00
Merge branch 'transform'
This commit is contained in:
commit
1d5ed8eb39
@ -5,6 +5,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## Not released:
|
||||
|
||||
* **BREAKING!** `transformer.boundBoxFunc` works in absolute coordinates of whole transformer.
|
||||
* Many `Konva.Transformer` fixes. Now it works correctly when you transform several rotated shapes.
|
||||
|
||||
## 5.0.3 - 2020-05-01
|
||||
|
||||
* Fixes for `boundBoxFunc` of `Konva.Transformer`.
|
||||
@ -21,7 +24,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## 5.0.0 - 2020-04-21
|
||||
|
||||
* **New `Konva.Transformer` implementation!**. Old API should work. But I mark this release is `major` (breaking) just for smooth updates. Changes:
|
||||
* **New `Konva.Transformer` implementation!**. Old API should work. But I marked this release is `major` (breaking) just for smooth updates. Changes:
|
||||
* Support of transforming multiple nodes at once: `tr.nodes([shape1, shape2])`.
|
||||
* `tr.node()`, `tr.setNode()`, `tr.attachTo()` methods are deprecated. Use `tr.nodes(array)` instead
|
||||
* Fixes for center scaling
|
||||
|
252
konva.js
252
konva.js
@ -8,7 +8,7 @@
|
||||
* Konva JavaScript Framework v5.0.3
|
||||
* http://konvajs.org/
|
||||
* Licensed under the MIT
|
||||
* Date: Fri May 01 2020
|
||||
* Date: Wed May 06 2020
|
||||
*
|
||||
* Original work Copyright (C) 2011 - 2013 by Eric Rowell (KineticJS)
|
||||
* Modified work Copyright (C) 2014 - present by Anton Lavrenov (Konva)
|
||||
@ -499,7 +499,7 @@
|
||||
result.rotation = b > 0 ? Math.acos(a / r) : -Math.acos(a / r);
|
||||
result.scaleX = r;
|
||||
result.scaleY = delta / r;
|
||||
result.skewX = Math.atan((a * c + b * d) / (r * r));
|
||||
result.skewX = (a * c + b * d) / delta;
|
||||
result.skewY = 0;
|
||||
}
|
||||
else if (c != 0 || d != 0) {
|
||||
@ -509,11 +509,24 @@
|
||||
result.scaleX = delta / s;
|
||||
result.scaleY = s;
|
||||
result.skewX = 0;
|
||||
result.skewY = Math.atan((a * c + b * d) / (s * s));
|
||||
result.skewY = (a * c + b * d) / delta;
|
||||
}
|
||||
result.rotation = Util._getRotation(result.rotation);
|
||||
return result;
|
||||
};
|
||||
Transform.prototype.qrDecompose = function () {
|
||||
var angle = Math.atan2(this.m[1], this.m[0]), denom = Math.pow(this.m[0], 2) + Math.pow(this.m[1], 2), scaleX = Math.sqrt(denom), scaleY = (this.m[0] * this.m[3] - this.m[2] * this.m[1]) / scaleX, skewX = Math.atan2(this.m[0] * this.m[2] + this.m[1] * this.m[3], denom);
|
||||
var rotation = Util._getRotation(angle);
|
||||
return {
|
||||
rotation: rotation,
|
||||
scaleX: scaleX,
|
||||
scaleY: scaleY,
|
||||
skewX: skewX / (Math.PI / 180),
|
||||
skewY: 0,
|
||||
x: this.m[4],
|
||||
y: this.m[5]
|
||||
};
|
||||
};
|
||||
return Transform;
|
||||
}());
|
||||
// CONSTANTS
|
||||
@ -14607,7 +14620,7 @@
|
||||
*/
|
||||
Factory.addGetterSetter(TextPath, 'letterSpacing', 0, getNumberValidator());
|
||||
/**
|
||||
* get/set text baselineg. The default is 'middle'. Can be 'top', 'bottom', 'middle', 'alphabetic', 'hanging'
|
||||
* get/set text baseline. The default is 'middle'. Can be 'top', 'bottom', 'middle', 'alphabetic', 'hanging'
|
||||
* @name Konva.TextPath#textBaseline
|
||||
* @method
|
||||
* @param {String} textBaseline
|
||||
@ -14805,78 +14818,6 @@
|
||||
var center = getCenter(shape);
|
||||
return rotateAroundPoint(shape, deltaRad, center);
|
||||
}
|
||||
function getShapeRect(shape) {
|
||||
var angleRad = shape.rotation;
|
||||
var x1 = shape.x;
|
||||
var y1 = shape.y;
|
||||
var x2 = x1 + shape.width * Math.cos(angleRad);
|
||||
var y2 = y1 + shape.width * Math.sin(angleRad);
|
||||
var x3 = shape.x +
|
||||
shape.width * Math.cos(angleRad) +
|
||||
shape.height * Math.sin(-angleRad);
|
||||
var y3 = shape.y +
|
||||
shape.height * Math.cos(angleRad) +
|
||||
shape.width * Math.sin(angleRad);
|
||||
var x4 = shape.x + shape.height * Math.sin(-angleRad);
|
||||
var y4 = shape.y + shape.height * Math.cos(angleRad);
|
||||
var leftX = Math.min(x1, x2, x3, x4);
|
||||
var rightX = Math.max(x1, x2, x3, x4);
|
||||
var topY = Math.min(y1, y2, y3, y4);
|
||||
var bottomY = Math.max(y1, y2, y3, y4);
|
||||
return {
|
||||
x: leftX,
|
||||
y: topY,
|
||||
width: rightX - leftX,
|
||||
height: bottomY - topY
|
||||
};
|
||||
}
|
||||
function getShapesRect(shapes) {
|
||||
var x1 = 9999999999;
|
||||
var y1 = 9999999999;
|
||||
var x2 = -999999999;
|
||||
var y2 = -999999999;
|
||||
shapes.forEach(function (shape) {
|
||||
var rect = getShapeRect(shape);
|
||||
x1 = Math.min(x1, rect.x);
|
||||
y1 = Math.min(y1, rect.y);
|
||||
x2 = Math.max(x2, rect.x + rect.width);
|
||||
y2 = Math.max(y2, rect.y + rect.height);
|
||||
});
|
||||
return {
|
||||
x: x1,
|
||||
y: y1,
|
||||
width: x2 - x1,
|
||||
height: y2 - y1,
|
||||
rotation: 0
|
||||
};
|
||||
}
|
||||
function transformShape(shape, oldSelection, newSelection, keepOffset) {
|
||||
if (keepOffset === void 0) { keepOffset = 1; }
|
||||
var offset = rotateAroundPoint(shape, -oldSelection.rotation, {
|
||||
x: oldSelection.x,
|
||||
y: oldSelection.y
|
||||
});
|
||||
var offsetX = offset.x - oldSelection.x;
|
||||
var offsetY = offset.y - oldSelection.y;
|
||||
var angle = oldSelection.rotation;
|
||||
var scaleX = shape.width ? newSelection.width / oldSelection.width : 1;
|
||||
var scaleY = shape.height ? newSelection.height / oldSelection.height : 1;
|
||||
return {
|
||||
x: keepOffset * newSelection.x +
|
||||
offsetX * scaleX * Math.cos(angle) +
|
||||
offsetY * scaleY * Math.sin(-angle),
|
||||
y: keepOffset * newSelection.y +
|
||||
offsetX * scaleX * Math.sin(angle) +
|
||||
offsetY * scaleY * Math.cos(angle),
|
||||
width: shape.width * scaleX,
|
||||
height: shape.height * scaleY,
|
||||
rotation: shape.rotation
|
||||
};
|
||||
}
|
||||
function transformAndRotateShape(shape, oldSelection, newSelection) {
|
||||
var updated = transformShape(shape, oldSelection, newSelection);
|
||||
return rotateAroundPoint(updated, newSelection.rotation - oldSelection.rotation, newSelection);
|
||||
}
|
||||
function getSnap(snaps, newRotationRad, tol) {
|
||||
var snapped = newRotationRad;
|
||||
for (var i = 0; i < snaps.length; i++) {
|
||||
@ -15071,6 +15012,7 @@
|
||||
Transformer.prototype._getNodeRect = function () {
|
||||
return this._getCache(NODES_RECT, this.__getNodeRect);
|
||||
};
|
||||
// return absolute rotated bounding rectangle
|
||||
Transformer.prototype.__getNodeShape = function (node, rot, relative) {
|
||||
if (rot === void 0) { rot = this.rotation(); }
|
||||
var rect = node.getClientRect({
|
||||
@ -15109,14 +15051,56 @@
|
||||
rotation: 0
|
||||
};
|
||||
}
|
||||
var shapes = this.nodes().map(function (node) {
|
||||
return _this.__getNodeShape(node);
|
||||
var totalPoints = [];
|
||||
this.nodes().map(function (node) {
|
||||
var box = node.getClientRect({
|
||||
skipTransform: true,
|
||||
skipShadow: true,
|
||||
skipStroke: _this.ignoreStroke()
|
||||
});
|
||||
var points = [
|
||||
{ x: box.x, y: box.y },
|
||||
{ x: box.x + box.width, y: box.y },
|
||||
{ x: box.x + box.width, y: box.y + box.height },
|
||||
{ x: box.x, y: box.y + box.height }
|
||||
];
|
||||
var trans = node.getAbsoluteTransform();
|
||||
points.forEach(function (point) {
|
||||
var transformed = trans.point(point);
|
||||
totalPoints.push(transformed);
|
||||
});
|
||||
});
|
||||
var box = getShapesRect(shapes);
|
||||
return rotateAroundPoint(box, Konva.getAngle(this.rotation()), {
|
||||
x: 0,
|
||||
y: 0
|
||||
var tr = new Transform();
|
||||
tr.rotate(-Konva.getAngle(this.rotation()));
|
||||
var minX, minY, maxX, maxY;
|
||||
totalPoints.forEach(function (point) {
|
||||
var transformed = tr.point(point);
|
||||
if (minX === undefined) {
|
||||
minX = maxX = transformed.x;
|
||||
minY = maxY = transformed.y;
|
||||
}
|
||||
minX = Math.min(minX, transformed.x);
|
||||
minY = Math.min(minY, transformed.y);
|
||||
maxX = Math.max(maxX, transformed.x);
|
||||
maxY = Math.max(maxY, transformed.y);
|
||||
});
|
||||
tr.invert();
|
||||
var p = tr.point({ x: minX, y: minY });
|
||||
return {
|
||||
x: p.x,
|
||||
y: p.y,
|
||||
width: maxX - minX,
|
||||
height: maxY - minY,
|
||||
rotation: Konva.getAngle(this.rotation())
|
||||
};
|
||||
// const shapes = this.nodes().map(node => {
|
||||
// return this.__getNodeShape(node);
|
||||
// });
|
||||
// const box = getShapesRect(shapes);
|
||||
// return rotateAroundPoint(box, Konva.getAngle(this.rotation()), {
|
||||
// x: 0,
|
||||
// y: 0
|
||||
// });
|
||||
};
|
||||
Transformer.prototype.getX = function () {
|
||||
return this._getNodeRect().x;
|
||||
@ -15491,45 +15475,8 @@
|
||||
this._anchorDragOffset.y -= offset.y;
|
||||
newAttrs.height += this.padding() * 2;
|
||||
}
|
||||
// let's find delta transform
|
||||
// var dx = newAttrs.x - oldAttrs.x,
|
||||
// dy = newAttrs.y - oldAttrs.y,
|
||||
// angle = newAttrs.rotation - oldAttrs.rotation,
|
||||
// scaleX = newAttrs.width / oldAttrs.width,
|
||||
// scaleY = newAttrs.height / oldAttrs.height;
|
||||
this._nodes.forEach(function (node) {
|
||||
var oldRect = _this.__getNodeShape(node, 0);
|
||||
var newRect = transformAndRotateShape(oldRect, oldAttrs, newAttrs);
|
||||
_this._fitNodeInto(node, newRect, evt);
|
||||
});
|
||||
this.rotation(Util._getRotation(newAttrs.rotation));
|
||||
this._resetTransformCache();
|
||||
this.update();
|
||||
this.getLayer().batchDraw();
|
||||
};
|
||||
Transformer.prototype._fitNodeInto = function (node, newAttrs, evt) {
|
||||
var pure = node.getClientRect({
|
||||
skipTransform: true,
|
||||
skipShadow: true,
|
||||
skipStroke: this.ignoreStroke()
|
||||
});
|
||||
var parentTransform = node
|
||||
.getParent()
|
||||
.getAbsoluteTransform()
|
||||
.copy();
|
||||
parentTransform.invert();
|
||||
var invertedPoint = parentTransform.point({
|
||||
x: newAttrs.x,
|
||||
y: newAttrs.y
|
||||
});
|
||||
var absScale = node.getParent().getAbsoluteScale();
|
||||
newAttrs.x = invertedPoint.x;
|
||||
newAttrs.y = invertedPoint.y;
|
||||
newAttrs.width /= absScale.x;
|
||||
newAttrs.height /= absScale.y;
|
||||
if (this.boundBoxFunc()) {
|
||||
var oldAttrs = this.__getNodeShape(node, node.rotation(), node.getParent());
|
||||
var bounded = this.boundBoxFunc()(oldAttrs, newAttrs, node);
|
||||
var bounded = this.boundBoxFunc()(oldAttrs, newAttrs);
|
||||
if (bounded) {
|
||||
newAttrs = bounded;
|
||||
}
|
||||
@ -15537,22 +15484,51 @@
|
||||
Util.warn('boundBoxFunc returned falsy. You should return new bound rect from it!');
|
||||
}
|
||||
}
|
||||
var parentRot = Konva.getAngle(node.getParent().getAbsoluteRotation());
|
||||
node.rotation(Util._getRotation(newAttrs.rotation - parentRot));
|
||||
var absScale = node.getParent().getAbsoluteScale();
|
||||
var scaleX = pure.width ? newAttrs.width / pure.width : 1;
|
||||
var scaleY = pure.height ? newAttrs.height / pure.height : 1;
|
||||
var rotation = Konva.getAngle(node.rotation());
|
||||
var dx = pure.x * scaleX - node.offsetX() * scaleX;
|
||||
var dy = pure.y * scaleY - node.offsetY() * scaleY;
|
||||
node.setAttrs({
|
||||
scaleX: scaleX,
|
||||
scaleY: scaleY,
|
||||
x: newAttrs.x - (dx * Math.cos(rotation) + dy * Math.sin(-rotation)),
|
||||
y: newAttrs.y - (dy * Math.cos(rotation) + dx * Math.sin(rotation))
|
||||
// base size value doesn't really matter
|
||||
// we just need to think about bounding boxes as transforms
|
||||
// but how?
|
||||
// the idea is that we have a transformed rectangle with the size of "baseSize"
|
||||
var baseSize = 10000000;
|
||||
var oldTr = new Transform();
|
||||
oldTr.translate(oldAttrs.x, oldAttrs.y);
|
||||
oldTr.rotate(oldAttrs.rotation);
|
||||
oldTr.scale(oldAttrs.width / baseSize, oldAttrs.height / baseSize);
|
||||
var newTr = new Transform();
|
||||
newTr.translate(newAttrs.x, newAttrs.y);
|
||||
newTr.rotate(newAttrs.rotation);
|
||||
newTr.scale(newAttrs.width / baseSize, newAttrs.height / baseSize);
|
||||
// now lets think we had [old transform] and now we have [new transform]
|
||||
// Now, the questions is: how can we transform "parent" to go from [old transform] into [new transform]
|
||||
// in equation it will be:
|
||||
// [delta transform] * [old transform] = [new transform]
|
||||
// that means that
|
||||
// [delta transform] = [new transform] * [old transform inverted]
|
||||
var delta = newTr.multiply(oldTr.invert());
|
||||
this._nodes.forEach(function (node) {
|
||||
// for each node we have the same [delta transform]
|
||||
// the equations is
|
||||
// [delta transform] * [parent transform] * [old local transform] = [parent transform] * [new local transform]
|
||||
// and we need to find [new local transform]
|
||||
// [new local] = [parent inverted] * [delta] * [parent] * [old local]
|
||||
var parentTransform = node.getParent().getAbsoluteTransform();
|
||||
var localTransform = node.getTransform().copy();
|
||||
// skip offset:
|
||||
localTransform.translate(node.offsetX(), node.offsetY());
|
||||
var newLocalTransform = new Transform();
|
||||
newLocalTransform
|
||||
.multiply(parentTransform.copy().invert())
|
||||
.multiply(delta)
|
||||
.multiply(parentTransform)
|
||||
.multiply(localTransform);
|
||||
var attrs = newLocalTransform.decompose();
|
||||
node.setAttrs(attrs);
|
||||
_this._fire('transform', { evt: evt, target: node });
|
||||
node._fire('transform', { evt: evt, target: node });
|
||||
});
|
||||
this._fire('transform', { evt: evt, target: node });
|
||||
node._fire('transform', { evt: evt, target: node });
|
||||
this.rotation(Util._getRotation(newAttrs.rotation));
|
||||
this._resetTransformCache();
|
||||
this.update();
|
||||
this.getLayer().batchDraw();
|
||||
};
|
||||
/**
|
||||
* force update of Konva.Transformer.
|
||||
@ -16012,7 +15988,7 @@
|
||||
*/
|
||||
Factory.addGetterSetter(Transformer, 'nodes');
|
||||
/**
|
||||
* get/set bounding box function. boundBondFunc operates is local coordinates of nodes parent
|
||||
* get/set bounding box function. **IMPORTANT!** boundBondFunc operates in absolute coordinates
|
||||
* @name Konva.Transformer#boundBoxFunc
|
||||
* @method
|
||||
* @param {Function} func
|
||||
@ -16022,8 +15998,8 @@
|
||||
* var boundBoxFunc = transformer.boundBoxFunc();
|
||||
*
|
||||
* // set
|
||||
* transformer.boundBoxFunc(function(oldBox, newBox, node) {
|
||||
* // width and height of the boxes are corresponding to total width and height of a node
|
||||
* transformer.boundBoxFunc(function(oldBox, newBox) {
|
||||
* // width and height of the boxes are corresponding to total absolute width and height of all nodes cobined
|
||||
* // so it includes scale of the node.
|
||||
* if (newBox.width > 200) {
|
||||
* return oldBox;
|
||||
|
4
konva.min.js
vendored
4
konva.min.js
vendored
File diff suppressed because one or more lines are too long
@ -28,6 +28,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "4.2.0",
|
||||
"github-release-from-changelog": "^2.1.1",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-concat": "^2.6.1",
|
||||
"gulp-connect": "^5.7.0",
|
||||
@ -40,8 +41,10 @@
|
||||
"gulp-typescript": "^5.0.1",
|
||||
"gulp-uglify": "^3.0.2",
|
||||
"gulp-util": "^3.0.8",
|
||||
"install": "^0.13.0",
|
||||
"mocha": "6.2.2",
|
||||
"mocha-headless-chrome": "^2.0.3",
|
||||
"npm": "^6.14.5",
|
||||
"prettier": "^1.19.1",
|
||||
"rollup": "^1.27.0",
|
||||
"rollup-plugin-commonjs": "^10.1.0",
|
||||
|
@ -56,9 +56,6 @@ git commit -am "update cdn link" --allow-empty >/dev/null
|
||||
echo "create new git tag"
|
||||
git tag $1 >/dev/null
|
||||
|
||||
echo "generate documentation"
|
||||
npx gulp api >/dev/null
|
||||
|
||||
|
||||
|
||||
echo "archive documentation"
|
||||
|
24
src/Util.ts
24
src/Util.ts
@ -2,7 +2,6 @@ import { glob, Konva } from './Global';
|
||||
import { Node } from './Node';
|
||||
import { IRect, RGB, RGBA, Vector2d } from './types';
|
||||
|
||||
|
||||
/**
|
||||
* Collection constructor. Collection extends Array.
|
||||
* This class is used in conjunction with {@link Konva.Container#find}
|
||||
@ -344,7 +343,7 @@ export class Transform {
|
||||
result.rotation = b > 0 ? Math.acos(a / r) : -Math.acos(a / r);
|
||||
result.scaleX = r;
|
||||
result.scaleY = delta / r;
|
||||
result.skewX = Math.atan((a * c + b * d) / (r * r));
|
||||
result.skewX = (a * c + b * d) / delta;
|
||||
result.skewY = 0;
|
||||
} else if (c != 0 || d != 0) {
|
||||
var s = Math.sqrt(c * c + d * d);
|
||||
@ -353,7 +352,7 @@ export class Transform {
|
||||
result.scaleX = delta / s;
|
||||
result.scaleY = s;
|
||||
result.skewX = 0;
|
||||
result.skewY = Math.atan((a * c + b * d) / (s * s));
|
||||
result.skewY = (a * c + b * d) / delta;
|
||||
} else {
|
||||
// a = b = c = d = 0
|
||||
}
|
||||
@ -362,6 +361,25 @@ export class Transform {
|
||||
|
||||
return result;
|
||||
}
|
||||
qrDecompose() {
|
||||
var angle = Math.atan2(this.m[1], this.m[0]),
|
||||
denom = Math.pow(this.m[0], 2) + Math.pow(this.m[1], 2),
|
||||
scaleX = Math.sqrt(denom),
|
||||
scaleY = (this.m[0] * this.m[3] - this.m[2] * this.m[1]) / scaleX,
|
||||
skewX = Math.atan2(this.m[0] * this.m[2] + this.m[1] * this.m[3], denom);
|
||||
|
||||
const rotation = Util._getRotation(angle);
|
||||
|
||||
return {
|
||||
rotation,
|
||||
scaleX: scaleX,
|
||||
scaleY: scaleY,
|
||||
skewX: skewX / (Math.PI / 180),
|
||||
skewY: 0,
|
||||
x: this.m[4],
|
||||
y: this.m[5]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// CONSTANTS
|
||||
|
@ -660,7 +660,7 @@ Factory.addGetterSetter(TextPath, 'align', 'left');
|
||||
Factory.addGetterSetter(TextPath, 'letterSpacing', 0, getNumberValidator());
|
||||
|
||||
/**
|
||||
* get/set text baselineg. The default is 'middle'. Can be 'top', 'bottom', 'middle', 'alphabetic', 'hanging'
|
||||
* get/set text baseline. The default is 'middle'. Can be 'top', 'bottom', 'middle', 'alphabetic', 'hanging'
|
||||
* @name Konva.TextPath#textBaseline
|
||||
* @method
|
||||
* @param {String} textBaseline
|
||||
|
@ -175,103 +175,6 @@ function rotateAroundCenter(shape: Box, deltaRad: number) {
|
||||
return rotateAroundPoint(shape, deltaRad, center);
|
||||
}
|
||||
|
||||
function getShapeRect(shape: Box) {
|
||||
const angleRad = shape.rotation;
|
||||
const x1 = shape.x;
|
||||
const y1 = shape.y;
|
||||
const x2 = x1 + shape.width * Math.cos(angleRad);
|
||||
const y2 = y1 + shape.width * Math.sin(angleRad);
|
||||
const x3 =
|
||||
shape.x +
|
||||
shape.width * Math.cos(angleRad) +
|
||||
shape.height * Math.sin(-angleRad);
|
||||
const y3 =
|
||||
shape.y +
|
||||
shape.height * Math.cos(angleRad) +
|
||||
shape.width * Math.sin(angleRad);
|
||||
const x4 = shape.x + shape.height * Math.sin(-angleRad);
|
||||
const y4 = shape.y + shape.height * Math.cos(angleRad);
|
||||
|
||||
const leftX = Math.min(x1, x2, x3, x4);
|
||||
const rightX = Math.max(x1, x2, x3, x4);
|
||||
const topY = Math.min(y1, y2, y3, y4);
|
||||
const bottomY = Math.max(y1, y2, y3, y4);
|
||||
return {
|
||||
x: leftX,
|
||||
y: topY,
|
||||
width: rightX - leftX,
|
||||
height: bottomY - topY
|
||||
};
|
||||
}
|
||||
|
||||
function getShapesRect(shapes: Array<Box>) {
|
||||
let x1 = 9999999999;
|
||||
let y1 = 9999999999;
|
||||
let x2 = -999999999;
|
||||
let y2 = -999999999;
|
||||
shapes.forEach(shape => {
|
||||
const rect = getShapeRect(shape);
|
||||
x1 = Math.min(x1, rect.x);
|
||||
y1 = Math.min(y1, rect.y);
|
||||
x2 = Math.max(x2, rect.x + rect.width);
|
||||
y2 = Math.max(y2, rect.y + rect.height);
|
||||
});
|
||||
|
||||
return {
|
||||
x: x1,
|
||||
y: y1,
|
||||
width: x2 - x1,
|
||||
height: y2 - y1,
|
||||
rotation: 0
|
||||
};
|
||||
}
|
||||
|
||||
function transformShape(
|
||||
shape: Box,
|
||||
oldSelection: Box,
|
||||
newSelection: Box,
|
||||
keepOffset = 1
|
||||
) {
|
||||
const offset = rotateAroundPoint(shape, -oldSelection.rotation, {
|
||||
x: oldSelection.x,
|
||||
y: oldSelection.y
|
||||
});
|
||||
const offsetX = offset.x - oldSelection.x;
|
||||
const offsetY = offset.y - oldSelection.y;
|
||||
|
||||
const angle = oldSelection.rotation;
|
||||
|
||||
const scaleX = shape.width ? newSelection.width / oldSelection.width : 1;
|
||||
const scaleY = shape.height ? newSelection.height / oldSelection.height : 1;
|
||||
|
||||
return {
|
||||
x:
|
||||
keepOffset * newSelection.x +
|
||||
offsetX * scaleX * Math.cos(angle) +
|
||||
offsetY * scaleY * Math.sin(-angle),
|
||||
y:
|
||||
keepOffset * newSelection.y +
|
||||
offsetX * scaleX * Math.sin(angle) +
|
||||
offsetY * scaleY * Math.cos(angle),
|
||||
width: shape.width * scaleX,
|
||||
height: shape.height * scaleY,
|
||||
rotation: shape.rotation
|
||||
};
|
||||
}
|
||||
|
||||
function transformAndRotateShape(
|
||||
shape: Box,
|
||||
oldSelection: Box,
|
||||
newSelection: Box
|
||||
) {
|
||||
const updated = transformShape(shape, oldSelection, newSelection);
|
||||
return rotateAroundPoint(
|
||||
updated,
|
||||
newSelection.rotation - oldSelection.rotation,
|
||||
newSelection
|
||||
);
|
||||
}
|
||||
|
||||
function getSnap(snaps: Array<number>, newRotationRad: number, tol: number) {
|
||||
let snapped = newRotationRad;
|
||||
for (let i = 0; i < snaps.length; i++) {
|
||||
@ -480,7 +383,8 @@ export class Transformer extends Group {
|
||||
return this._getCache(NODES_RECT, this.__getNodeRect);
|
||||
}
|
||||
|
||||
__getNodeShape(node, rot = this.rotation(), relative?: Node) {
|
||||
// return absolute rotated bounding rectangle
|
||||
__getNodeShape(node: Node, rot = this.rotation(), relative?: Node) {
|
||||
var rect = node.getClientRect({
|
||||
skipTransform: true,
|
||||
skipShadow: true,
|
||||
@ -522,15 +426,60 @@ export class Transformer extends Group {
|
||||
};
|
||||
}
|
||||
|
||||
const shapes = this.nodes().map(node => {
|
||||
return this.__getNodeShape(node);
|
||||
const totalPoints = [];
|
||||
this.nodes().map(node => {
|
||||
const box = node.getClientRect({
|
||||
skipTransform: true,
|
||||
skipShadow: true,
|
||||
skipStroke: this.ignoreStroke()
|
||||
});
|
||||
var points = [
|
||||
{ x: box.x, y: box.y },
|
||||
{ x: box.x + box.width, y: box.y },
|
||||
{ x: box.x + box.width, y: box.y + box.height },
|
||||
{ x: box.x, y: box.y + box.height }
|
||||
];
|
||||
var trans = node.getAbsoluteTransform();
|
||||
points.forEach(function(point) {
|
||||
var transformed = trans.point(point);
|
||||
totalPoints.push(transformed);
|
||||
});
|
||||
});
|
||||
|
||||
const box = getShapesRect(shapes);
|
||||
return rotateAroundPoint(box, Konva.getAngle(this.rotation()), {
|
||||
x: 0,
|
||||
y: 0
|
||||
const tr = new Transform();
|
||||
tr.rotate(-Konva.getAngle(this.rotation()));
|
||||
|
||||
var minX: number, minY: number, maxX: number, maxY: number;
|
||||
totalPoints.forEach(function(point) {
|
||||
var transformed = tr.point(point);
|
||||
if (minX === undefined) {
|
||||
minX = maxX = transformed.x;
|
||||
minY = maxY = transformed.y;
|
||||
}
|
||||
minX = Math.min(minX, transformed.x);
|
||||
minY = Math.min(minY, transformed.y);
|
||||
maxX = Math.max(maxX, transformed.x);
|
||||
maxY = Math.max(maxY, transformed.y);
|
||||
});
|
||||
|
||||
tr.invert();
|
||||
const p = tr.point({ x: minX, y: minY });
|
||||
return {
|
||||
x: p.x,
|
||||
y: p.y,
|
||||
width: maxX - minX,
|
||||
height: maxY - minY,
|
||||
rotation: Konva.getAngle(this.rotation())
|
||||
};
|
||||
// const shapes = this.nodes().map(node => {
|
||||
// return this.__getNodeShape(node);
|
||||
// });
|
||||
|
||||
// const box = getShapesRect(shapes);
|
||||
// return rotateAroundPoint(box, Konva.getAngle(this.rotation()), {
|
||||
// x: 0,
|
||||
// y: 0
|
||||
// });
|
||||
}
|
||||
getX() {
|
||||
return this._getNodeRect().x;
|
||||
@ -1006,54 +955,9 @@ export class Transformer extends Group {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// let's find delta transform
|
||||
// var dx = newAttrs.x - oldAttrs.x,
|
||||
// dy = newAttrs.y - oldAttrs.y,
|
||||
// angle = newAttrs.rotation - oldAttrs.rotation,
|
||||
// scaleX = newAttrs.width / oldAttrs.width,
|
||||
// scaleY = newAttrs.height / oldAttrs.height;
|
||||
|
||||
this._nodes.forEach(node => {
|
||||
var oldRect = this.__getNodeShape(node, 0);
|
||||
var newRect = transformAndRotateShape(oldRect, oldAttrs, newAttrs);
|
||||
this._fitNodeInto(node, newRect, evt);
|
||||
});
|
||||
this.rotation(Util._getRotation(newAttrs.rotation));
|
||||
this._resetTransformCache();
|
||||
this.update();
|
||||
this.getLayer().batchDraw();
|
||||
}
|
||||
_fitNodeInto(node: Node, newAttrs, evt) {
|
||||
var pure = node.getClientRect({
|
||||
skipTransform: true,
|
||||
skipShadow: true,
|
||||
skipStroke: this.ignoreStroke()
|
||||
});
|
||||
|
||||
const parentTransform = node
|
||||
.getParent()
|
||||
.getAbsoluteTransform()
|
||||
.copy();
|
||||
parentTransform.invert();
|
||||
const invertedPoint = parentTransform.point({
|
||||
x: newAttrs.x,
|
||||
y: newAttrs.y
|
||||
});
|
||||
|
||||
var absScale = node.getParent().getAbsoluteScale();
|
||||
|
||||
newAttrs.x = invertedPoint.x;
|
||||
newAttrs.y = invertedPoint.y;
|
||||
newAttrs.width /= absScale.x;
|
||||
newAttrs.height /= absScale.y;
|
||||
|
||||
if (this.boundBoxFunc()) {
|
||||
const oldAttrs = this.__getNodeShape(
|
||||
node,
|
||||
node.rotation(),
|
||||
node.getParent()
|
||||
);
|
||||
const bounded = this.boundBoxFunc()(oldAttrs, newAttrs, node);
|
||||
const bounded = this.boundBoxFunc()(oldAttrs, newAttrs);
|
||||
if (bounded) {
|
||||
newAttrs = bounded;
|
||||
} else {
|
||||
@ -1063,27 +967,56 @@ export class Transformer extends Group {
|
||||
}
|
||||
}
|
||||
|
||||
const parentRot = Konva.getAngle(node.getParent().getAbsoluteRotation());
|
||||
node.rotation(Util._getRotation(newAttrs.rotation - parentRot));
|
||||
// base size value doesn't really matter
|
||||
// we just need to think about bounding boxes as transforms
|
||||
// but how?
|
||||
// the idea is that we have a transformed rectangle with the size of "baseSize"
|
||||
const baseSize = 10000000;
|
||||
const oldTr = new Transform();
|
||||
oldTr.translate(oldAttrs.x, oldAttrs.y);
|
||||
oldTr.rotate(oldAttrs.rotation);
|
||||
oldTr.scale(oldAttrs.width / baseSize, oldAttrs.height / baseSize);
|
||||
|
||||
var absScale = node.getParent().getAbsoluteScale();
|
||||
const newTr = new Transform();
|
||||
newTr.translate(newAttrs.x, newAttrs.y);
|
||||
newTr.rotate(newAttrs.rotation);
|
||||
newTr.scale(newAttrs.width / baseSize, newAttrs.height / baseSize);
|
||||
|
||||
var scaleX = pure.width ? newAttrs.width / pure.width : 1;
|
||||
var scaleY = pure.height ? newAttrs.height / pure.height : 1;
|
||||
// now lets think we had [old transform] and now we have [new transform]
|
||||
// Now, the questions is: how can we transform "parent" to go from [old transform] into [new transform]
|
||||
// in equation it will be:
|
||||
// [delta transform] * [old transform] = [new transform]
|
||||
// that means that
|
||||
// [delta transform] = [new transform] * [old transform inverted]
|
||||
const delta = newTr.multiply(oldTr.invert());
|
||||
|
||||
var rotation = Konva.getAngle(node.rotation());
|
||||
var dx = pure.x * scaleX - node.offsetX() * scaleX;
|
||||
var dy = pure.y * scaleY - node.offsetY() * scaleY;
|
||||
this._nodes.forEach(node => {
|
||||
// for each node we have the same [delta transform]
|
||||
// the equations is
|
||||
// [delta transform] * [parent transform] * [old local transform] = [parent transform] * [new local transform]
|
||||
// and we need to find [new local transform]
|
||||
// [new local] = [parent inverted] * [delta] * [parent] * [old local]
|
||||
const parentTransform = node.getParent().getAbsoluteTransform();
|
||||
const localTransform = node.getTransform().copy();
|
||||
// skip offset:
|
||||
localTransform.translate(node.offsetX(), node.offsetY());
|
||||
|
||||
node.setAttrs({
|
||||
scaleX: scaleX,
|
||||
scaleY: scaleY,
|
||||
x: newAttrs.x - (dx * Math.cos(rotation) + dy * Math.sin(-rotation)),
|
||||
y: newAttrs.y - (dy * Math.cos(rotation) + dx * Math.sin(rotation))
|
||||
const newLocalTransform = new Transform();
|
||||
newLocalTransform
|
||||
.multiply(parentTransform.copy().invert())
|
||||
.multiply(delta)
|
||||
.multiply(parentTransform)
|
||||
.multiply(localTransform);
|
||||
|
||||
const attrs = newLocalTransform.decompose();
|
||||
node.setAttrs(attrs);
|
||||
this._fire('transform', { evt: evt, target: node });
|
||||
node._fire('transform', { evt: evt, target: node });
|
||||
});
|
||||
|
||||
this._fire('transform', { evt: evt, target: node });
|
||||
node._fire('transform', { evt: evt, target: node });
|
||||
this.rotation(Util._getRotation(newAttrs.rotation));
|
||||
this._resetTransformCache();
|
||||
this.update();
|
||||
this.getLayer().batchDraw();
|
||||
}
|
||||
/**
|
||||
* force update of Konva.Transformer.
|
||||
@ -1248,10 +1181,7 @@ export class Transformer extends Group {
|
||||
keepRatio: GetSet<boolean, this>;
|
||||
centeredScaling: GetSet<boolean, this>;
|
||||
ignoreStroke: GetSet<boolean, this>;
|
||||
boundBoxFunc: GetSet<
|
||||
(oldBox: IRect, newBox: IRect, node: Node) => IRect,
|
||||
this
|
||||
>;
|
||||
boundBoxFunc: GetSet<(oldBox: IRect, newBox: IRect) => IRect, this>;
|
||||
shouldOverdrawWholeArea: GetSet<boolean, this>;
|
||||
}
|
||||
|
||||
@ -1629,7 +1559,7 @@ Factory.addGetterSetter(Transformer, 'node');
|
||||
Factory.addGetterSetter(Transformer, 'nodes');
|
||||
|
||||
/**
|
||||
* get/set bounding box function. boundBondFunc operates is local coordinates of nodes parent
|
||||
* get/set bounding box function. **IMPORTANT!** boundBondFunc operates in absolute coordinates
|
||||
* @name Konva.Transformer#boundBoxFunc
|
||||
* @method
|
||||
* @param {Function} func
|
||||
@ -1639,8 +1569,8 @@ Factory.addGetterSetter(Transformer, 'nodes');
|
||||
* var boundBoxFunc = transformer.boundBoxFunc();
|
||||
*
|
||||
* // set
|
||||
* transformer.boundBoxFunc(function(oldBox, newBox, node) {
|
||||
* // width and height of the boxes are corresponding to total width and height of a node
|
||||
* transformer.boundBoxFunc(function(oldBox, newBox) {
|
||||
* // width and height of the boxes are corresponding to total absolute width and height of all nodes cobined
|
||||
* // so it includes scale of the node.
|
||||
* if (newBox.width > 200) {
|
||||
* return oldBox;
|
||||
|
@ -241,10 +241,10 @@ afterEach(function() {
|
||||
|
||||
if (!isFailed && !isManual) {
|
||||
Konva.stages.forEach(function(stage) {
|
||||
// stage.destroy();
|
||||
stage.destroy();
|
||||
});
|
||||
if (Konva.DD._dragElements.size) {
|
||||
throw 'Why not cleaned?';
|
||||
throw 'Why drag elements are not cleaned?';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -1492,6 +1492,67 @@ suite('Shape', function() {
|
||||
assert.equal(absRect.height, 100);
|
||||
});
|
||||
|
||||
test('getClientRect with skew', function() {
|
||||
var stage = addStage();
|
||||
var layer = new Konva.Layer();
|
||||
stage.add(layer);
|
||||
|
||||
var shape = new Konva.Rect({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 200,
|
||||
height: 100,
|
||||
skewX: 0.5,
|
||||
scaleX: 2,
|
||||
fill: 'green'
|
||||
});
|
||||
layer.add(shape);
|
||||
|
||||
var back = new Konva.Rect({
|
||||
stroke: 'red'
|
||||
});
|
||||
back.setAttrs(shape.getClientRect());
|
||||
layer.add(back);
|
||||
layer.draw();
|
||||
|
||||
var absRect = shape.getClientRect();
|
||||
|
||||
assert.equal(absRect.x, 0);
|
||||
assert.equal(absRect.y, 0);
|
||||
assert.equal(absRect.width, 450);
|
||||
assert.equal(absRect.height, 100);
|
||||
});
|
||||
|
||||
test('decompose transform', function() {
|
||||
var stage = addStage();
|
||||
var layer = new Konva.Layer();
|
||||
stage.add(layer);
|
||||
|
||||
var shape = new Konva.Rect({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 200,
|
||||
height: 100,
|
||||
skewX: 0.5,
|
||||
scaleX: 2,
|
||||
scaleY: 2,
|
||||
fill: 'green'
|
||||
});
|
||||
layer.add(shape);
|
||||
layer.draw();
|
||||
|
||||
assert.equal(shape.getTransform().decompose().scaleX, 2);
|
||||
assert.equal(shape.getTransform().decompose().scaleY, 2);
|
||||
assert.equal(shape.getTransform().decompose().skewX, 0.5);
|
||||
|
||||
shape.skewX(2);
|
||||
shape.scaleX(0.5);
|
||||
|
||||
assert.equal(shape.getTransform().decompose().skewX, 2);
|
||||
assert.equal(shape.getTransform().decompose().scaleX, 0.5);
|
||||
assert.equal(shape.getTransform().decompose().scaleY, 2);
|
||||
});
|
||||
|
||||
test('shadow should respect pixel ratio', function() {
|
||||
var stage = addStage();
|
||||
var layer = new Konva.Layer();
|
||||
|
@ -315,6 +315,32 @@ suite('TextPath', function() {
|
||||
assert.equal(layer.getContext().getTrace(true), trace);
|
||||
});
|
||||
|
||||
test.skip('Text path with center align - arc', function() {
|
||||
var stage = addStage();
|
||||
var layer = new Konva.Layer();
|
||||
|
||||
var textpath = new Konva.TextPath({
|
||||
fill: '#333',
|
||||
fontSize: 20,
|
||||
text: 'Hello World',
|
||||
align: 'right',
|
||||
data: 'M 50 200 a 100 100 0 0 1 200 0'
|
||||
});
|
||||
layer.add(textpath);
|
||||
|
||||
var path = new Konva.Path({
|
||||
stroke: '#000',
|
||||
data: 'M 50 200 a 100 100 0 0 1 200 0'
|
||||
});
|
||||
layer.add(path);
|
||||
stage.add(layer);
|
||||
|
||||
var trace =
|
||||
'restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();save();translate();rotate();fillStyle;fillText();restore();restore();restore();';
|
||||
|
||||
assert.equal(layer.getContext().getTrace(true), trace);
|
||||
});
|
||||
|
||||
test('Text path with align right', function() {
|
||||
var stage = addStage();
|
||||
var layer = new Konva.Layer();
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user