Merge branch 'transform'

This commit is contained in:
Anton Lavrenov 2020-05-06 11:40:27 -05:00
commit 1d5ed8eb39
12 changed files with 744 additions and 594 deletions

View File

@ -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
View File

@ -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

File diff suppressed because one or more lines are too long

View File

@ -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",

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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?';
}
}
});

View File

@ -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();

View File

@ -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