Compare commits

...

79 Commits

Author SHA1 Message Date
Anton Lavrenov
7b99aa9813 update cdn link 2024-07-16 20:20:35 -05:00
Anton Lavrenov
331bd9bb64 build for 9.3.14 2024-07-16 20:20:35 -05:00
Anton Lavrenov
72d92d8bcc update CHANGELOG with new version 2024-07-16 20:20:23 -05:00
Anton Lavrenov
459606b637 changes 2024-07-16 20:20:01 -05:00
Anton Lavrenov
3f6f9f9365 fix shadow + corner radius for images, close #1734 2024-07-16 20:19:26 -05:00
Anton Lavrenov
ac1587fac8 update docs, fix fullRule for hit graph, close #1787 2024-07-15 12:40:00 -05:00
Anton Lavrenov
00997bc225 update cdn link 2024-07-05 13:25:23 -05:00
Anton Lavrenov
5ac40635e4 build for 9.3.13 2024-07-05 13:25:23 -05:00
Anton Lavrenov
d9e6f11e42 update CHANGELOG with new version 2024-07-05 13:25:10 -05:00
Anton Lavrenov
bb5275f9d1 measureText fallback 2024-07-05 13:24:38 -05:00
Anton Lavrenov
cb69ff9223 update cdn link 2024-06-20 10:00:41 -05:00
Anton Lavrenov
e86f222253 build for 9.3.12 2024-06-20 10:00:41 -05:00
Anton Lavrenov
addbb98328 update CHANGELOG with new version 2024-06-20 10:00:27 -05:00
Anton Lavrenov
83523ecc6f fix transform on double touch, fix #1767 2024-06-20 09:59:21 -05:00
Anton Lavrenov
8be222e80e Fix svg path.getPointAtLength(). fix #1766 2024-06-12 16:12:20 -05:00
Anton Lavrenov
0a99665e98 Merge branch 'master' of github.com:konvajs/konva 2024-06-12 12:21:18 -05:00
Anton Lavrenov
d95ff79964 fix shape.getClientRect() when a parent is cached, fix #1759` 2024-06-12 12:21:10 -05:00
Anton Lavrenov
af13fc2e11 Merge pull request #1770 from tacman/patch-1
add link to konva-dev
2024-06-04 12:41:42 -05:00
Anton Lavrenov
cb0cbbfe3d Merge pull request #1772 from squoddam/fix-fill-type-gradient
Update `fill` type to accept `CanvasGradient`
2024-06-04 11:48:25 -05:00
Elazar Kopyrin
52977b5c0c Update fill and stroke GetSet type to accept CanvasGradient 2024-06-03 22:18:08 +03:00
Elazar Kopyrin
01f4bb605e Update fill type to accpet CanvasGradient 2024-06-03 20:20:05 +03:00
Tac Tacelosky
b54f3888e3 add link to konva-dev
This extension is invaluable for debugging.
2024-05-30 13:40:04 -06:00
Anton Lavrenov
68b4ea3cb6 update cdn link 2024-05-23 12:41:37 -05:00
Anton Lavrenov
ad01e860d9 build for 9.3.11 2024-05-23 12:41:37 -05:00
Anton Lavrenov
10487e99e4 update CHANGELOG with new version 2024-05-23 12:41:22 -05:00
Anton Lavrenov
f0a7924949 try to fix again 2024-05-23 12:40:59 -05:00
Anton Lavrenov
9083dfbbe7 update cdn link 2024-05-23 12:20:11 -05:00
Anton Lavrenov
f2a37285fc build for 9.3.10 2024-05-23 12:20:11 -05:00
Anton Lavrenov
a5cd67261f update CHANGELOG with new version 2024-05-23 12:20:00 -05:00
Anton Lavrenov
e30a16cb36 update cdn link 2024-05-20 19:25:36 -05:00
Anton Lavrenov
e2009f58ff build for 9.3.9 2024-05-20 19:25:36 -05:00
Anton Lavrenov
bbf945b219 update CHANGELOG with new version 2024-05-20 19:25:25 -05:00
Anton Lavrenov
60aefdfeaa changes 2024-05-20 19:25:09 -05:00
Anton Lavrenov
07bdf49bfd fix underline for experimental render 2024-05-20 19:23:37 -05:00
Anton Lavrenov
d089066a30 update cdn link 2024-05-15 21:11:56 -05:00
Anton Lavrenov
955705898d build for 9.3.8 2024-05-15 21:11:56 -05:00
Anton Lavrenov
f63e019475 update CHANGELOG with new version 2024-05-15 21:11:41 -05:00
Anton Lavrenov
ed5a11dda3 change 2024-05-15 21:11:19 -05:00
Anton Lavrenov
7d5ba3e429 rename fix apply 2024-05-15 21:10:03 -05:00
Anton Lavrenov
2587e0708f update cdn link 2024-05-15 20:36:12 -05:00
Anton Lavrenov
5143154d97 build for 9.3.7 2024-05-15 20:36:12 -05:00
Anton Lavrenov
8f2e9f8793 update CHANGELOG with new version 2024-05-15 20:36:00 -05:00
Anton Lavrenov
0ec3425d99 possible fix for all our text problems 2024-05-15 20:35:26 -05:00
Anton Lavrenov
fdd0e64aad add failing test 2024-05-15 15:36:03 -05:00
Anton Lavrenov
88861b3ec6 fix click event flow a little. close #1755 2024-05-15 14:10:24 -05:00
Anton Lavrenov
70f57d2a95 ignore node_modules in ts 2024-05-15 13:45:07 -05:00
Anton Lavrenov
6d34326084 update cdn link 2024-03-07 06:55:13 +07:00
Anton Lavrenov
03ad744ab7 build for 9.3.6 2024-03-07 06:55:12 +07:00
Anton Lavrenov
7f607b1b7e update CHANGELOG with new version 2024-03-07 06:54:58 +07:00
Anton Lavrenov
ef8710fd48 note 2024-03-07 06:54:29 +07:00
Anton Lavrenov
4f29c6d365 fix trasnformer bug 2024-03-07 06:53:45 +07:00
Anton Lavrenov
613493eb21 update cdn link 2024-03-04 05:18:52 +07:00
Anton Lavrenov
b0b8347810 build for 9.3.5 2024-03-04 05:18:52 +07:00
Anton Lavrenov
ab224d5b85 update CHANGELOG with new version 2024-03-04 05:18:38 +07:00
Anton Lavrenov
73d65cb7d3 improve transformer performance 2024-03-04 05:16:19 +07:00
Anton Lavrenov
b09ceb34f3 change events order for transformer. fix #1724 2024-03-03 11:45:55 +07:00
Anton Lavrenov
fe8b7e0fc3 update cdn link 2024-03-03 10:43:42 +07:00
Anton Lavrenov
9f8c653745 build for 9.3.4 2024-03-03 10:43:42 +07:00
Anton Lavrenov
8cdac23faa update CHANGELOG with new version 2024-03-03 10:43:27 +07:00
Anton Lavrenov
eb4e2b4fef fix cliping with zero size. close #1723 2024-03-03 10:42:25 +07:00
Anton Lavrenov
e7672858b7 update cdn link 2024-02-09 11:45:12 -05:00
Anton Lavrenov
ea92753221 build for 9.3.3 2024-02-09 11:45:12 -05:00
Anton Lavrenov
93106aba53 update CHANGELOG with new version 2024-02-09 11:45:02 -05:00
Anton Lavrenov
4da037a2fa Another fix for exporting buffered shapes 2024-02-09 11:44:32 -05:00
Anton Lavrenov
1e2c28738b update cdn link 2024-01-26 11:08:49 -05:00
Anton Lavrenov
f212a1ad69 build for 9.3.2 2024-01-26 11:08:48 -05:00
Anton Lavrenov
b37e14afff update CHANGELOG with new version 2024-01-26 11:08:36 -05:00
Anton Lavrenov
76cace8b38 fix large memory usage 2024-01-26 11:08:12 -05:00
Anton Lavrenov
abfdf7153f update cdn link 2024-01-17 19:13:36 -05:00
Anton Lavrenov
297241add4 build for 9.3.1 2024-01-17 19:13:36 -05:00
Anton Lavrenov
80fad79858 update CHANGELOG with new version 2024-01-17 19:13:18 -05:00
Anton Lavrenov
0d502baccd fix buffer export 2024-01-17 19:12:49 -05:00
Anton Lavrenov
a1660e1ccb fix filters work. close #1697 2024-01-05 10:55:28 -05:00
Anton Lavrenov
48ae639b70 Merge pull request #1695 from CPatchane/fix/support_ctx_roundRect
Add support for ctx.roundRect
2024-01-03 07:23:00 -05:00
CPatchane
f6ccabb34a Correctly type radii param 2024-01-02 21:13:32 +01:00
CPatchane
49e6e3e824 Remove the existence check 2024-01-02 21:13:15 +01:00
CPatchane
467147b4c7 Add support for ctx.roundRect
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/roundRect
2023-12-28 14:50:32 +01:00
Anton Lavrenov
a8efcd554a type fixes. close #1692 2023-12-25 11:10:37 -05:00
Anton Lavrenov
813cd4f780 add missing types, close #1693 2023-12-25 10:26:28 -05:00
29 changed files with 894 additions and 173 deletions

View File

@@ -3,6 +3,61 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
### 9.3.14 (2024-07-16)
- Fix shadow + corner radius for images
- Support `fillRule` for `Konva.Shape` on hit graph
### 9.3.13 (2024-07-05)
- Fallback for `Konva.Text.measureSize()` when 2d context doesn't return full data
### 9.3.12 (2024-06-20)
- Fix stopped transforming when it was triggered by multi-touch
- Fix svg `path.getPointAtLength()` calculations in some cases
- Fix `shape.getClientRect()` when any of parents is cached
### 9.3.11 (2024-05-23)
- Fix chrome clear canvas issue
- Typescript fixes
### 9.3.9 (2024-05-20)
- Fix underline and line-through for `Konva.Text` when `Konva._fixTextRendering = true`
### 9.3.8 (2024-05-15)
- Fix click events fires on stage
- Temporary `Konva._fixTextRendering = true` flag to fix inconsistent text
### 9.3.6 (2024-03-04)
- Fix transformer bug to enable hit graph back
### 9.3.5 (2024-03-04)
- `tranformer` event will be triggered AFTER all data of transformer is updated
- Improve performance of transformer
### 9.3.4 (2024-03-03)
- Fix clipping with zero size
### 9.3.3 (2024-02-09)
- Another fix for exporting buffered shapes
### 9.3.2 (2024-01-26)
- Fix large memory usage on node export
### 9.3.1 (2024-01-17)
- Fix Pixelate filter work/fix caching size
- Fix node export when large buffer canvas is used
### 9.3.0 (2023-12-20)
- New attribute `rotateLineVisible` for `Konva.Transformer` to show/hide rotate line

View File

@@ -64,6 +64,10 @@ Konva works in all modern mobile and desktop browsers. A browser need to be capa
At the current moment `Konva` doesn't work in IE11 directly. To make it work you just need to provide some polyfills such as `Array.prototype.find`, `String.prototype.trimLeft`, `String.prototype.trimRight`, `Array.from`.
# Debugging
The Chrome inspector simply shows the canvas element. To see the Konva objects and their details, install the konva-dev extension at https://github.com/konvajs/konva-devtool.
# Loading and installing Konva
Konva supports UMD loading. So you can use all possible variants to load the framework into your project:

View File

@@ -1,6 +1,6 @@
{
"name": "konva",
"version": "9.3.0",
"version": "9.3.14",
"author": "Anton Lavrenov",
"files": [
"README.md",

View File

@@ -270,7 +270,7 @@ export abstract class Container<
obj.children = [];
this.getChildren().forEach((child) => {
obj.children.push(child.toObject());
obj.children!.push(child.toObject());
});
return obj;
@@ -307,6 +307,7 @@ export abstract class Container<
* canvas and redraw every shape inside the container, it should only be used for special situations
* because it performs very poorly. Please use the {@link Konva.Stage#getIntersection} method if at all possible
* because it performs much better
* nodes with listening set to false will not be detected
* @method
* @name Konva.Container#getAllIntersections
* @param {Object} pos
@@ -342,7 +343,7 @@ export abstract class Container<
});
this._requestDraw();
}
drawScene(can?: SceneCanvas, top?: Node) {
drawScene(can?: SceneCanvas, top?: Node, bufferCanvas?: SceneCanvas) {
var layer = this.getLayer()!,
canvas = can || (layer && layer.getCanvas()),
context = canvas && canvas.getContext(),
@@ -361,7 +362,7 @@ export abstract class Container<
this._drawCachedSceneCanvas(context);
context.restore();
} else {
this._drawChildren('drawScene', canvas, top);
this._drawChildren('drawScene', canvas, top, bufferCanvas);
}
return this;
}
@@ -387,12 +388,14 @@ export abstract class Container<
}
return this;
}
_drawChildren(drawMethod, canvas, top) {
_drawChildren(drawMethod, canvas, top, bufferCanvas?) {
var context = canvas && canvas.getContext(),
clipWidth = this.clipWidth(),
clipHeight = this.clipHeight(),
clipFunc = this.clipFunc(),
hasClip = (clipWidth && clipHeight) || clipFunc;
hasClip =
(typeof clipWidth === 'number' && typeof clipHeight === 'number') ||
clipFunc;
const selfCache = top === this;
@@ -408,7 +411,7 @@ export abstract class Container<
} else {
var clipX = this.clipX();
var clipY = this.clipY();
context.rect(clipX, clipY, clipWidth, clipHeight);
context.rect(clipX || 0, clipY || 0, clipWidth, clipHeight);
}
context.clip.apply(context, clipArgs);
m = transform.copy().invert().getMatrix();
@@ -426,7 +429,7 @@ export abstract class Container<
}
this.children?.forEach(function (child) {
child[drawMethod](canvas, top);
child[drawMethod](canvas, top, bufferCanvas);
});
if (hasComposition) {
context.restore();

View File

@@ -58,6 +58,7 @@ var COMMA = ',',
'putImageData',
'quadraticCurveTo',
'rect',
'roundRect',
'restore',
'rotate',
'save',
@@ -586,6 +587,20 @@ export class Context {
rect(x: number, y: number, width: number, height: number) {
this._context.rect(x, y, width, height);
}
/**
* roundRect function.
* @method
* @name Konva.Context#roundRect
*/
roundRect(
x: number,
y: number,
width: number,
height: number,
radii: number | DOMPointInit | (number | DOMPointInit)[]
) {
this._context.roundRect(x, y, width, height, radii);
}
/**
* putImageData function.
* @method

View File

@@ -90,6 +90,7 @@ export const Konva = {
_mouseDblClickPointerId: null,
_touchDblClickPointerId: null,
_pointerDblClickPointerId: null,
_fixTextRendering: false,
/**
* Global pixel ratio configuration. KonvaJS automatically detect pixel ratio of current device.
@@ -155,6 +156,9 @@ export const Konva = {
isDragging() {
return Konva['DD'].isDragging;
},
isTransforming() {
return Konva['Transformer']?.isTransforming();
},
/**
* returns whether or not a drag and drop operation is ready, but may
* not necessarily have started

View File

@@ -309,6 +309,7 @@ export class Layer extends Container<Group | Shape> {
* get visible intersection shape. This is the preferred
* method for determining if a point intersects a shape or not
* also you may pass optional selector parameter to return ancestor of intersected shape
* nodes with listening set to false will not be detected
* @method
* @name Konva.Layer#getIntersection
* @param {Object} pos
@@ -469,7 +470,10 @@ export class Layer extends Container<Group | Shape> {
}
destroy(): this {
Util.releaseCanvas(this.getNativeCanvasElement(), this.getHitCanvas()._canvas);
Util.releaseCanvas(
this.getNativeCanvasElement(),
this.getHitCanvas()._canvas
);
return super.destroy();
}

View File

@@ -60,6 +60,8 @@ export interface NodeConfig {
opacity?: number;
scale?: Vector2d;
scaleX?: number;
skewX?: number;
skewY?: number;
scaleY?: number;
rotation?: number;
rotationDeg?: number;
@@ -341,10 +343,13 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
return;
}
// let's just add 1 pixel extra,
// because using Math.floor on x, y position may shift drawing
width += offset * 2 + 1;
height += offset * 2 + 1;
// to avoid shift we need to increase size
// but we better to avoid it, for better filters flows
const extraPaddingX = Math.abs(Math.round(rect.x) - x) > 0.5 ? 1 : 0;
const extraPaddingY = Math.abs(Math.round(rect.y) - y) > 0.5 ? 1 : 0;
width += offset * 2 + extraPaddingX;
height += offset * 2 + extraPaddingY;
x -= offset;
y -= offset;
@@ -444,7 +449,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
return this._cache.has(CANVAS);
}
abstract drawScene(canvas?: Canvas, top?: Node): void;
abstract drawScene(canvas?: Canvas, top?: Node, bufferCanvas?: Canvas): void;
abstract drawHit(canvas?: Canvas, top?: Node): void;
/**
* Return client rectangle {x, y, width, height} of node. This rectangle also include all styling (strokes, shadows, etc).
@@ -911,7 +916,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* @returns {Object}
*/
getAttrs() {
return this.attrs || {};
return (this.attrs || {}) as Config & Record<string, any>;
}
/**
* set multiple attrs at once using an object literal
@@ -1024,7 +1029,10 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
}
});
var dragSkip = !skipDragCheck && !Konva.hitOnDragEnabled && layerUnderDrag;
var dragSkip =
!skipDragCheck &&
!Konva.hitOnDragEnabled &&
(layerUnderDrag || Konva.isTransforming());
return this.isListening() && this.isVisible() && !dragSkip;
}
@@ -1481,15 +1489,21 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* @returns {Object}
*/
toObject() {
var obj = {} as any,
attrs = this.getAttrs(),
var attrs = this.getAttrs() as any,
key,
val,
getter,
defaultValue,
nonPlainObject;
obj.attrs = {};
const obj: {
attrs: Config & Record<string, any>;
className: string;
children?: Array<any>;
} = {
attrs: {} as Config & Record<string, any>,
className: this.getClassName(),
};
for (key in attrs) {
val = attrs[key];
@@ -1507,12 +1521,11 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
// restore attr value
attrs[key] = val;
if (defaultValue !== val) {
obj.attrs[key] = val;
(obj.attrs as any)[key] = val;
}
}
obj.className = this.getClassName();
return Util._prepareToStringify(obj);
return Util._prepareToStringify(obj) as typeof obj;
}
/**
* convert Node into a JSON string. Returns a JSON string.
@@ -1922,6 +1935,15 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
}),
context = canvas.getContext();
const bufferCanvas = new SceneCanvas({
// width and height already multiplied by pixelRatio
// so we need to revert that
// also increase size by x nd y offset to make sure content fits canvas
width: canvas.width / canvas.pixelRatio + Math.abs(x),
height: canvas.height / canvas.pixelRatio + Math.abs(y),
pixelRatio: canvas.pixelRatio,
});
if (config.imageSmoothingEnabled === false) {
context._context.imageSmoothingEnabled = false;
}
@@ -1931,7 +1953,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
context.translate(-1 * x, -1 * y);
}
this.drawScene(canvas);
this.drawScene(canvas, undefined, bufferCanvas);
context.restore();
return canvas;
@@ -2088,10 +2110,14 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
try {
const callback = config?.callback;
if (callback) delete config.callback;
this.toCanvas(config).toBlob((blob) => {
resolve(blob);
callback?.(blob);
}, config?.mimeType, config?.quality);
this.toCanvas(config).toBlob(
(blob) => {
resolve(blob);
callback?.(blob);
},
config?.mimeType,
config?.quality
);
} catch (err) {
reject(err);
}
@@ -3129,6 +3155,8 @@ addGetterSetter(Node, 'listening', true, getBooleanValidator());
/**
* get/set listening attr. If you need to determine if a node is listening or not
* by taking into account its parents, use the isListening() method
* nodes with listening set to false will not be detected in hit graph
* so they will be ignored in container.getIntersection() method
* @name Konva.Node#listening
* @method
* @param {Boolean} listening Can be true, or false. The default is true.

View File

@@ -26,7 +26,7 @@ export type LineJoin = 'round' | 'bevel' | 'miter';
export type LineCap = 'butt' | 'round' | 'square';
export interface ShapeConfig extends NodeConfig {
fill?: string;
fill?: string | CanvasGradient;
fillPatternImage?: HTMLImageElement;
fillPatternX?: number;
fillPatternY?: number;
@@ -128,8 +128,13 @@ function _fillFunc(this: Node, context) {
function _strokeFunc(context) {
context.stroke();
}
function _fillFuncHit(context) {
context.fill();
function _fillFuncHit(this: Node, context) {
const fillRule = this.attrs.fillRule;
if (fillRule) {
context.fill(fillRule);
} else {
context.fill();
}
}
function _strokeFuncHit(context) {
context.stroke();
@@ -469,10 +474,6 @@ export class Shape<
// so they use that method with forced fill
// it probably will be simpler, then copy/paste the code
// buffer canvas is available only inside the stage
if (!this.getStage()) {
return false;
}
// force skip buffer canvas
const perfectDrawEnabled = this.attrs.perfectDrawEnabled ?? true;
if (!perfectDrawEnabled) {
@@ -532,9 +533,22 @@ export class Shape<
};
}
getClientRect(config: ShapeGetClientRectConfig = {}) {
// if we have a cached parent, it will use cached transform matrix
// but we don't want to that
let hasCachedParent = false;
let parent = this.getParent();
while (parent) {
if (parent.isCached()) {
hasCachedParent = true;
break;
}
parent = parent.getParent();
}
const skipTransform = config.skipTransform;
const relativeTo = config.relativeTo;
// force relative to stage if we have a cached parent
const relativeTo =
config.relativeTo || (hasCachedParent && this.getStage()) || undefined;
const fillRect = this.getSelfRect();
@@ -573,7 +587,7 @@ export class Shape<
}
return rect;
}
drawScene(can?: SceneCanvas, top?: Node) {
drawScene(can?: SceneCanvas, top?: Node, bufferCanvas?: SceneCanvas) {
// basically there are 3 drawing modes
// 1 - simple drawing when nothing is cached.
// 2 - when we are caching current
@@ -586,7 +600,6 @@ export class Shape<
drawFunc = this.getSceneFunc(),
hasShadow = this.hasShadow(),
stage,
bufferCanvas,
bufferContext;
var skipBuffer = canvas.isCache;
@@ -614,8 +627,8 @@ export class Shape<
// if buffer canvas is needed
if (this._useBufferCanvas() && !skipBuffer) {
stage = this.getStage();
bufferCanvas = stage.bufferCanvas;
bufferContext = bufferCanvas.getContext();
const bc = bufferCanvas || stage.bufferCanvas;
bufferContext = bc.getContext();
bufferContext.clear();
bufferContext.save();
bufferContext._applyLineJoin(this);
@@ -626,20 +639,14 @@ export class Shape<
drawFunc.call(this, bufferContext, this);
bufferContext.restore();
var ratio = bufferCanvas.pixelRatio;
var ratio = bc.pixelRatio;
if (hasShadow) {
context._applyShadow(this);
}
context._applyOpacity(this);
context._applyGlobalCompositeOperation(this);
context.drawImage(
bufferCanvas._canvas,
0,
0,
bufferCanvas.width / ratio,
bufferCanvas.height / ratio
);
context.drawImage(bc._canvas, 0, 0, bc.width / ratio, bc.height / ratio);
} else {
context._applyLineJoin(this);
@@ -777,7 +784,7 @@ export class Shape<
dash: GetSet<number[], this>;
dashEnabled: GetSet<boolean, this>;
dashOffset: GetSet<number, this>;
fill: GetSet<string, this>;
fill: GetSet<string | CanvasGradient, this>;
fillEnabled: GetSet<boolean, this>;
fillLinearGradientColorStops: GetSet<Array<number | string>, this>;
fillLinearGradientStartPoint: GetSet<Vector2d, this>;
@@ -826,7 +833,7 @@ export class Shape<
shadowOffsetY: GetSet<number, this>;
shadowOpacity: GetSet<number, this>;
shadowBlur: GetSet<number, this>;
stroke: GetSet<string, this>;
stroke: GetSet<string | CanvasGradient, this>;
strokeEnabled: GetSet<boolean, this>;
fillAfterStrokeEnabled: GetSet<boolean, this>;
strokeScaleEnabled: GetSet<boolean, this>;

View File

@@ -353,6 +353,7 @@ export class Stage extends Container<Layer> {
/**
* get visible intersection shape. This is the preferred
* method for determining if a point intersects a shape or not
* nodes with listening set to false will not be detected
* @method
* @name Konva.Stage#getIntersection
* @param {Object} pos
@@ -507,7 +508,8 @@ export class Stage extends Container<Layer> {
this.setPointersPositions(evt);
var targetShape = this._getTargetShape(eventType);
var eventsEnabled = !DD.isDragging || Konva.hitOnDragEnabled;
var eventsEnabled =
!(Konva.isDragging() || Konva.isTransforming()) || Konva.hitOnDragEnabled;
if (targetShape && eventsEnabled) {
targetShape._fireAndBubble(events.pointerout, { evt: evt });
targetShape._fireAndBubble(events.pointerleave, { evt: evt });
@@ -550,6 +552,7 @@ export class Stage extends Container<Layer> {
// no shape detected? do nothing
if (!shape || !shape.isListening()) {
this[eventType + 'ClickStartShape'] = undefined;
return;
}
@@ -590,12 +593,13 @@ export class Stage extends Container<Layer> {
if (!events) {
return;
}
if (DD.isDragging && DD.node!.preventDefault() && evt.cancelable) {
if (Konva.isDragging() && DD.node!.preventDefault() && evt.cancelable) {
evt.preventDefault();
}
this.setPointersPositions(evt);
var eventsEnabled = !DD.isDragging || Konva.hitOnDragEnabled;
var eventsEnabled =
!(Konva.isDragging() || Konva.isTransforming()) || Konva.hitOnDragEnabled;
if (!eventsEnabled) {
return;
}
@@ -962,3 +966,15 @@ _registerNode(Stage);
* stage.container(container);
*/
Factory.addGetterSetter(Stage, 'container');
// chrome is clearing canvas in inactive browser window, causing layer content to be erased
// so let's redraw layers as soon as window becomes active
// TODO: any other way to solve this issue?
// TODO: should we remove it if chrome fixes the issue?
if (Konva.isBrowser) {
document.addEventListener('visibilitychange', () => {
stages.forEach((stage) => {
stage.batchDraw();
});
});
}

View File

@@ -933,7 +933,7 @@ export const Util = {
});
return newStart;
},
_prepareToStringify(obj) {
_prepareToStringify<T>(obj: any): T | null {
var desc;
obj.visitedByCircularReferenceRemoval = true;

View File

@@ -65,6 +65,11 @@ export class Image extends Shape<ImageConfig> {
}
}
_useBufferCanvas() {
const hasCornerRadius = !!this.cornerRadius();
const hasShadow = this.hasShadow();
if (hasCornerRadius && hasShadow) {
return true;
}
return super._useBufferCanvas(true);
}
_sceneFunc(context: Context) {

View File

@@ -320,61 +320,38 @@ export class Path extends Shape<PathConfig> {
}
static getPointOnLine(dist, P1x, P1y, P2x, P2y, fromX?, fromY?) {
if (fromX === undefined) {
fromX = P1x;
}
if (fromY === undefined) {
fromY = P1y;
}
fromX = fromX ?? P1x;
fromY = fromY ?? P1y;
var m = (P2y - P1y) / (P2x - P1x + 0.00000001);
var run = Math.sqrt((dist * dist) / (1 + m * m));
if (P2x < P1x) {
run *= -1;
const len = this.getLineLength(P1x, P1y, P2x, P2y);
if (len < 1e-10) {
return { x: P1x, y: P1y };
}
var rise = m * run;
var pt;
if (P2x === P1x) {
// vertical line
pt = {
x: fromX,
y: fromY + rise,
};
} else if ((fromY - P1y) / (fromX - P1x + 0.00000001) === m) {
pt = {
x: fromX + run,
y: fromY + rise,
};
} else {
var ix, iy;
var len = this.getLineLength(P1x, P1y, P2x, P2y);
// if (len < 0.00000001) {
// return {
// x: P1x,
// y: P1y,
// };
// }
var u = (fromX - P1x) * (P2x - P1x) + (fromY - P1y) * (P2y - P1y);
u = u / (len * len);
ix = P1x + u * (P2x - P1x);
iy = P1y + u * (P2y - P1y);
var pRise = this.getLineLength(fromX, fromY, ix, iy);
var pRun = Math.sqrt(dist * dist - pRise * pRise);
run = Math.sqrt((pRun * pRun) / (1 + m * m));
if (P2x < P1x) {
run *= -1;
}
rise = m * run;
pt = {
x: ix + run,
y: iy + rise,
};
// Vertical line
return { x: fromX, y: fromY + (P2y > P1y ? dist : -dist) };
}
return pt;
const m = (P2y - P1y) / (P2x - P1x);
const run = Math.sqrt((dist * dist) / (1 + m * m)) * (P2x < P1x ? -1 : 1);
const rise = m * run;
if (Math.abs(fromY - P1y - m * (fromX - P1x)) < 1e-10) {
return { x: fromX + run, y: fromY + rise };
}
const u =
((fromX - P1x) * (P2x - P1x) + (fromY - P1y) * (P2y - P1y)) / (len * len);
const ix = P1x + u * (P2x - P1x);
const iy = P1y + u * (P2y - P1y);
const pRise = this.getLineLength(fromX, fromY, ix, iy);
const pRun = Math.sqrt(dist * dist - pRise * pRise);
const adjustedRun =
Math.sqrt((pRun * pRun) / (1 + m * m)) * (P2x < P1x ? -1 : 1);
const adjustedRise = m * adjustedRun;
return { x: ix + adjustedRun, y: iy + adjustedRise };
}
static getPointOnCubicBezier(pct, P1x, P1y, P2x, P2y, P3x, P3y, P4x, P4y) {

View File

@@ -201,11 +201,19 @@ export class Text extends Shape<TextConfig> {
shouldUnderline = textDecoration.indexOf('underline') !== -1,
shouldLineThrough = textDecoration.indexOf('line-through') !== -1,
n;
direction = direction === INHERIT ? context.direction : direction;
var translateY = 0;
var translateY = lineHeightPx / 2;
var baseline = MIDDLE;
if (Konva._fixTextRendering) {
var metrics = this.measureSize('M'); // Use a sample character to get the ascent
baseline = 'alphabetic';
translateY =
(metrics.fontBoundingBoxAscent - metrics.fontBoundingBoxDescent) / 2 +
lineHeightPx / 2;
}
var lineTranslateX = 0;
var lineTranslateY = 0;
@@ -216,11 +224,10 @@ export class Text extends Shape<TextConfig> {
context.setAttr('font', this._getContextFont());
context.setAttr('textBaseline', MIDDLE);
context.setAttr('textBaseline', baseline);
context.setAttr('textAlign', LEFT);
// handle vertical alignment
if (verticalAlign === MIDDLE) {
alignY = (this.getHeight() - textArrLen * lineHeightPx - padding * 2) / 2;
@@ -254,18 +261,17 @@ export class Text extends Shape<TextConfig> {
context.save();
context.beginPath();
context.moveTo(
lineTranslateX,
translateY + lineTranslateY + Math.round(fontSize / 2)
);
let yOffset = Konva._fixTextRendering
? Math.round(fontSize / 4)
: Math.round(fontSize / 2);
const x = lineTranslateX;
const y = translateY + lineTranslateY + yOffset;
context.moveTo(x, y);
spacesNumber = text.split(' ').length - 1;
oneWord = spacesNumber === 0;
lineWidth =
align === JUSTIFY && !lastLine ? totalWidth - padding * 2 : width;
context.lineTo(
lineTranslateX + Math.round(lineWidth),
translateY + lineTranslateY + Math.round(fontSize / 2)
);
context.lineTo(x + Math.round(lineWidth), y);
// I have no idea what is real ratio
// just /15 looks good enough
@@ -279,7 +285,8 @@ export class Text extends Shape<TextConfig> {
if (shouldLineThrough) {
context.save();
context.beginPath();
context.moveTo(lineTranslateX, translateY + lineTranslateY);
let yOffset = Konva._fixTextRendering ? -Math.round(fontSize / 4) : 0;
context.moveTo(lineTranslateX, translateY + lineTranslateY + yOffset);
spacesNumber = text.split(' ').length - 1;
oneWord = spacesNumber === 0;
lineWidth =
@@ -288,7 +295,7 @@ export class Text extends Shape<TextConfig> {
: width;
context.lineTo(
lineTranslateX + Math.round(lineWidth),
translateY + lineTranslateY
translateY + lineTranslateY + yOffset
);
context.lineWidth = fontSize / 15;
const gradient = this._getLinearGradient();
@@ -398,9 +405,30 @@ export class Text extends Shape<TextConfig> {
metrics = _context.measureText(text);
_context.restore();
// Scale the fallback values based on the provided fontSize compared to the sample size (100 in your new case)
const scaleFactor = fontSize / 100;
// Note, fallback values are from chrome browser with 100px font size and font-family "Arial"
return {
actualBoundingBoxAscent:
metrics.actualBoundingBoxAscent ?? 71.58203125 * scaleFactor,
actualBoundingBoxDescent: metrics.actualBoundingBoxDescent ?? 0, // Remains zero as there is no descent in the provided metrics
actualBoundingBoxLeft:
metrics.actualBoundingBoxLeft ?? -7.421875 * scaleFactor,
actualBoundingBoxRight:
metrics.actualBoundingBoxRight ?? 75.732421875 * scaleFactor,
alphabeticBaseline: metrics.alphabeticBaseline ?? 0, // Remains zero as it's typically relative to the baseline itself
emHeightAscent: metrics.emHeightAscent ?? 100 * scaleFactor,
emHeightDescent: metrics.emHeightDescent ?? -20 * scaleFactor,
fontBoundingBoxAscent: metrics.fontBoundingBoxAscent ?? 91 * scaleFactor,
fontBoundingBoxDescent:
metrics.fontBoundingBoxDescent ?? 21 * scaleFactor,
hangingBaseline:
metrics.hangingBaseline ?? 72.80000305175781 * scaleFactor,
ideographicBaseline: metrics.ideographicBaseline ?? -21 * scaleFactor,
width: metrics.width,
height: fontSize,
height: fontSize, // Typically set to the font size
};
}
_getContextFont() {
@@ -703,7 +731,6 @@ Factory.overWriteSetter(Text, 'width', getNumberOrAutoValidator());
Factory.overWriteSetter(Text, 'height', getNumberOrAutoValidator());
/**
* get/set direction
* @name Konva.Text#direction

View File

@@ -42,6 +42,12 @@ export interface TransformerConfig extends ContainerConfig {
boundBoxFunc?: (oldBox: Box, newBox: Box) => Box;
useSingleNodeRotation?: boolean;
shouldOverdrawWholeArea?: boolean;
anchorDragBoundFunc?: (
oldPos: Vector2d,
newPos: Vector2d,
evt: any
) => Vector2d;
anchorStyleFunc?: (anchor: Rect) => void;
}
var EVENTS_NAME = 'tr-konva';
@@ -197,6 +203,7 @@ function getSnap(snaps: Array<number>, newRotationRad: number, tol: number) {
return snapped;
}
let activeTransformersCount = 0;
/**
* Transformer constructor. Transformer is a special type of group that allow you transform Konva
* primitives and shapes. Transforming tool is not changing `width` and `height` properties of nodes
@@ -238,7 +245,6 @@ function getSnap(snaps: Array<number>, newRotationRad: number, tol: number) {
* });
* layer.add(transformer);
*/
export class Transformer extends Group {
_nodes: Array<Node>;
_movingAnchorName: string | null = null;
@@ -248,6 +254,10 @@ export class Transformer extends Group {
cos: number;
_cursorChange: boolean;
static isTransforming = () => {
return activeTransformersCount > 0;
};
constructor(config?: TransformerConfig) {
// call super constructor
super(config);
@@ -660,6 +670,11 @@ export class Transformer extends Group {
});
}
_handleMouseDown(e) {
// do nothing if we already transforming
// that is possible to trigger with multitouch
if (this._transforming) {
return;
}
this._movingAnchorName = e.target.name().split(' ')[0];
var attrs = this._getNodeRect();
@@ -684,6 +699,7 @@ export class Transformer extends Group {
x: pos.x - ap.x,
y: pos.y - ap.y,
};
activeTransformersCount++;
this._fire('transformstart', { evt: e.evt, target: this.getNode() });
this._nodes.forEach((target) => {
target._fire('transformstart', { evt: e.evt, target });
@@ -949,11 +965,16 @@ export class Transformer extends Group {
window.removeEventListener('touchend', this._handleMouseUp, true);
}
var node = this.getNode();
activeTransformersCount--;
this._fire('transformend', { evt: e, target: node });
// redraw layer to restore hit graph
this.getLayer()?.batchDraw();
if (node) {
this._nodes.forEach((target) => {
target._fire('transformend', { evt: e, target });
// redraw layer to restore hit graph
target.getLayer()?.batchDraw();
});
}
this._movingAnchorName = null;
@@ -1103,11 +1124,14 @@ export class Transformer extends Group {
const attrs = newLocalTransform.decompose();
node.setAttrs(attrs);
this._fire('transform', { evt: evt, target: node });
node._fire('transform', { evt: evt, target: node });
node.getLayer()?.batchDraw();
});
this.rotation(Util._getRotation(newAttrs.rotation));
// trigger transform event AFTER we update rotation
this._nodes.forEach((node) => {
this._fire('transform', { evt: evt, target: node });
node._fire('transform', { evt: evt, target: node });
});
this._resetTransformCache();
this.update();
this.getLayer()!.batchDraw();

View File

@@ -1,6 +1,5 @@
import { assert } from 'chai';
import { addStage, Konva, loadImage } from '../unit/test-utils';
import { cloneAndCompareLayer } from '../unit/test-utils';
describe('Pixelate', function () {
// ======================================================
@@ -42,4 +41,25 @@ describe('Pixelate', function () {
done();
});
});
it('make sure we have no extra transparent pixels', function (done) {
var stage = addStage();
var layer = new Konva.Layer();
stage.add(layer);
Konva.Image.fromURL(
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGUAAABmCAYAAADS6F9hAAAAAXNSR0IArs4c6QAAAXJJREFUeF7t1cEJADAMw8B2/6Fd6BT3UCYQEiZ3205HGbhFoXp8mKJ4TYoCNilKUUQDIFM/pSigARCppRQFNAAitZSigAZApJZSFNAAiNRSigIaAJFaSlFAAyBSSykKaABEailFAQ2ASC2lKKABEKmlFAU0ACK1lKKABkCkllIU0ACI1FKKAhoAkVpKUUADIFJLKQpoAERqKUUBDYBILaUooAEQqaUUBTQAIrWUooAGQKSWUhTQAIjUUooCGgCRWkpRQAMgUkspCmgARGopRQENgEgtpSigARCppRQFNAAitZSigAZApJZSFNAAiNRSigIaAJFaSlFAAyBSSykKaABEailFAQ2ASC2lKKABEKmlFAU0ACK1lKKABkCkllIU0ACI1FKKAhoAkVpKUUADIFJLKQpoAERqKUUBDYBILaUooAEQqaUUBTQAIrWUooAGQKSWUhTQAIjUUooCGgCRWkpRQAMgUkspCmgARGopRQENgEgPgGOW3jCsp3sAAAAASUVORK5CYII=',
function (image) {
layer.add(image);
image.cache();
image.filters([Konva.Filters.Pixelate]);
image.pixelSize(4);
layer.draw();
cloneAndCompareLayer(layer);
done();
}
);
});
});

View File

@@ -30,45 +30,42 @@
<script type="module">
import Konva from '../src/index.ts';
var stageWidth = window.innerWidth;
var stageHeight = window.innerHeight;
Konva._fixTextRendering = true;
var stage = new Konva.Stage({
container: 'container',
width: window.innerHeight,
height: window.innerHeight,
width: stageWidth,
height: stageHeight,
});
var layer = new Konva.Layer();
stage.add(layer);
const circle = new Konva.Circle({
x: 100,
y: 150,
radius: 50,
var text = new Konva.Text({
x: 10,
y: 10,
text: 'Simple Text',
fontSize: 100,
fontFamily: 'Calibri',
fill: 'green',
textDecoration: 'underline line-through',
draggable: true,
fillLinearGradientStartPoint: { x: -50, y: -50 },
fillLinearGradientEndPoint: { x: 50, y: 50 },
fillLinearGradientColorStops: [0, 'red', 1, 'yellow'],
});
layer.add(text);
layer.add(circle);
const tr = new Konva.Transformer({
nodes: [circle],
flipEnabled: false,
var tr = new Konva.Transformer({
nodes: [text],
});
layer.add(tr);
const dot = new Konva.Circle({
x: 100,
y: 100,
radius: 2,
fill: 'blue',
tr.on('transformstart', () => {
console.log('transform start');
});
layer.add(dot);
circle.on('transform', () => {
dot.x(circle.x());
dot.y(circle.y() - circle.radius() * circle.scaleY() - 50);
tr.on('transformend', () => {
console.log('transform end');
});
</script>
</body>

View File

@@ -22,6 +22,7 @@ describe('Context', function () {
'arc',
'arcTo',
'rect',
'roundRect',
'ellipse',
'fill',
'stroke',

View File

@@ -6,6 +6,9 @@ import {
simulateMouseDown,
simulateMouseMove,
simulateMouseUp,
simulatePointerDown,
simulatePointerMove,
simulatePointerUp,
} from './test-utils';
describe('DragAndDropEvents', function () {
@@ -219,10 +222,6 @@ describe('DragAndDropEvents', function () {
clicked = true;
});
circle.on('dblclick', function () {
//console.log('dblclick');
});
simulateMouseDown(stage, {
x: 40,
y: 40,
@@ -245,6 +244,58 @@ describe('DragAndDropEvents', function () {
}, 20);
});
// TODO: how to solve it?
// hint: every shape has pointerId that indicates which pointer is dragging it
// but "pointer" event and mouse event has different pointerId
// so we need to find a way to match them
// should we save several pointers per shape?
// doesn't sound good
// switch to pointer only event handling?
it.skip('click should not occur after drag and drop', function (done) {
var stage = addStage();
var layer = new Konva.Layer();
var circle = new Konva.Circle({
x: 40,
y: 40,
radius: 20,
strokeWidth: 4,
fill: 'green',
stroke: 'black',
draggable: true,
});
layer.add(circle);
stage.add(layer);
var clicked = false;
stage.on('pointerclick', function () {
clicked = true;
});
simulatePointerDown(stage, {
x: 40,
y: 40,
});
setTimeout(function () {
simulatePointerMove(stage, {
x: 100,
y: 100,
});
simulatePointerUp(stage, {
x: 100,
y: 100,
});
assert(!clicked, 'click event should not have been fired');
done();
}, 20);
});
// ======================================================
it('drag and drop distance', function (done) {
var stage = addStage();

View File

@@ -55,15 +55,43 @@ describe('Group', function () {
var path = new Konva.Group({
width: 100,
height: 100,
clipFunc: () => [new Path2D('M0 0v50h50Z')]
clipFunc: () => [new Path2D('M0 0v50h50Z')],
});
layer.add(path);
stage.add(layer);
const trace = layer.getContext().getTrace()
const trace = layer.getContext().getTrace();
assert.equal(trace, 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();clip([object Path2D]);transform(1,0,0,1,0,0);restore();');
assert.equal(
trace,
'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();clip([object Path2D]);transform(1,0,0,1,0,0);restore();'
);
});
it('clip group with by zero size', function () {
var stage = addStage();
var layer = new Konva.Layer();
var group = new Konva.Group({
width: 100,
height: 100,
clipWidth: 0,
clipHeight: 0,
});
layer.add(group);
stage.add(layer);
const trace = layer.getContext().getTrace();
console.log(trace);
assert.equal(
trace,
'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();rect(0,0,0,0);clip();transform(1,0,0,1,0,0);restore();'
);
});
it('clip group with a Path2D and clipRule', function () {
@@ -80,8 +108,11 @@ describe('Group', function () {
layer.add(path);
stage.add(layer);
const trace = layer.getContext().getTrace()
const trace = layer.getContext().getTrace();
assert.equal(trace, 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();clip([object Path2D],evenodd);transform(1,0,0,1,0,0);restore();');
assert.equal(
trace,
'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();clip([object Path2D],evenodd);transform(1,0,0,1,0,0);restore();'
);
});
});

View File

@@ -413,4 +413,39 @@ describe('Image', function () {
done();
});
});
it('corner radius with shadow', function (done) {
// that will trigger buffer canvas
loadImage('darth-vader.jpg', (imageObj) => {
var stage = addStage();
var layer = new Konva.Layer();
var darth = new Konva.Image({
x: 20,
y: 20,
image: imageObj,
cornerRadius: 10,
draggable: true,
stroke: 'red',
strokeWidth: 100,
strokeEnabled: false,
shadowColor: 'black',
shadowBlur: 10,
shadowOffsetX: 10,
shadowOffsetY: 10,
scaleX: 0.5,
scaleY: 0.5,
});
layer.add(darth);
stage.add(layer);
assert.equal(
layer.getContext().getTrace(true),
'clearRect();save();shadowColor;shadowBlur;shadowOffsetX;shadowOffsetY;drawImage();restore();'
);
done();
});
});
});

View File

@@ -2377,4 +2377,67 @@ describe('MouseEvents', function () {
});
assert.deepEqual(stage.getPointerPosition(), { x: 60, y: 60 });
});
it('mousedown on empty then mouseup on shape', function () {
if (isNode) {
return;
}
var stage = addStage();
var layer = new Konva.Layer();
stage.add(layer);
stage.on('mousedown mousemove mouseup click', function (e) {
console.log('state', e.type);
});
var rect = new Konva.Rect({
width: 50,
height: 50,
fill: 'red',
draggable: true,
});
layer.add(rect);
layer.draw();
var clicks = 0;
rect.on('click', function () {
console.log('click');
clicks += 1;
if (clicks === 2) {
debugger;
}
});
simulateMouseDown(stage, {
x: 40,
y: 40,
});
simulateMouseUp(stage, {
x: 40,
y: 40,
});
// trigger click
assert.equal(clicks, 1, 'clicks not triggered');
// mousedown outside
simulateMouseDown(stage, {
x: 60,
y: 6,
});
// move into rect
simulateMouseMove(stage, {
x: 40,
y: 40,
});
// mouseup inside rect
simulateMouseUp(stage, {
x: 40,
y: 40,
});
// it shouldn't trigger the click event!!!
assert.equal(clicks, 1, 'clicks not triggered');
});
});

View File

@@ -1469,7 +1469,7 @@ describe('Caching', function () {
layer.draw();
assert.equal(
circle._cache.get('canvas').filter.width,
21 * circle._cache.get('canvas').filter.pixelRatio
20 * circle._cache.get('canvas').filter.pixelRatio
);
circle.filters([]);
// TODO: should we clear cache canvas?

View File

@@ -1134,11 +1134,11 @@ describe('Path', function () {
assert.deepEqual(points, [
{ x: 300, y: 10 },
{ x: 290.2871413779118, y: 27.48314552325543 },
{ x: 280.57428275582356, y: 44.96629104651086 },
{ x: 270.86142413373534, y: 62.4494365697663 },
{ x: 261.1485655116471, y: 79.93258209302172 },
{ x: 251.43570688955887, y: 97.41572761627717 },
{ x: 290.28714137642737, y: 27.483145522430753 },
{ x: 280.57428275285474, y: 44.96629104486151 },
{ x: 270.86142412928206, y: 62.44943656729226 },
{ x: 261.1485655057094, y: 79.93258208972301 },
{ x: 251.4357068821368, y: 97.41572761215377 },
{ x: 230.89220826660141, y: 87.23996356219386 },
{ x: 207.0639321224534, y: 74.08466390481559 },
{ x: 182.87529785963875, y: 63.52674972743341 },
@@ -1168,6 +1168,47 @@ describe('Path', function () {
stage.add(layer);
});
it('get point at path with float attrs', function () {
var stage = addStage();
var layer = new Konva.Layer();
const data =
'M419.0000314094981 342.88624187900973 L419.00003140949804 577.0038889378335 L465.014001082264 577.0038889378336 Z';
var path = new Konva.Path({
stroke: 'red',
strokeWidth: 3,
data,
});
layer.add(path);
if (isBrowser) {
const SVGPath = document.createElementNS(
'http://www.w3.org/2000/svg',
'path'
) as SVGPathElement;
SVGPath.setAttribute('d', data);
for (var i = 0; i < path.getLength(); i += 1) {
var p = path.getPointAtLength(i);
var circle = new Konva.Circle({
x: p.x,
y: p.y,
radius: 2,
fill: 'black',
stroke: 'black',
});
layer.add(circle);
const position = SVGPath.getPointAtLength(i);
assert(
Math.abs(p.x - position.x) <= 1,
'error for x should be smaller than 10% for i = ' + i
);
assert(
Math.abs(p.y - position.y) <= 1,
'error for y should be smaller than 10% for i = ' + i
);
}
}
});
it('get point at path - bezier', function () {
var stage = addStage();
var layer = new Konva.Layer();
@@ -1618,8 +1659,11 @@ describe('Path', function () {
layer.add(path);
stage.add(layer);
const trace = layer.getContext().getTrace()
const trace = layer.getContext().getTrace();
assert.equal(trace, 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();moveTo(200,100);lineTo(300,100);lineTo(300,150);closePath();fillStyle=#ccc;fill(evenodd);restore();');
assert.equal(
trace,
'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();moveTo(200,100);lineTo(300,100);lineTo(300,150);closePath();fillStyle=#ccc;fill(evenodd);restore();'
);
});
});

View File

@@ -12,6 +12,7 @@ import {
compareLayers,
loadImage,
Konva,
compareCanvases,
} from './test-utils';
describe('Shape', function () {
@@ -1479,6 +1480,76 @@ describe('Shape', function () {
}
});
it('export when buffer canvas is used should handle scaling correctly', async function () {
var stage = addStage();
var layer = new Konva.Layer();
stage.add(layer);
var group = new Konva.Group();
layer.add(group);
var text = new Konva.Text({
text: 'hello',
fontSize: 300,
fill: 'green',
shadowColor: 'black',
});
group.add(text);
const canvas1 = group.toCanvas({
x: group.x(),
y: group.y(),
width: text.width(),
height: text.height(),
});
text.stroke('transparent');
const canvas2 = group.toCanvas({
x: group.x(),
y: group.y(),
width: text.width(),
height: text.height(),
});
compareCanvases(canvas2, canvas1, 255, 10);
});
it('export when buffer canvas is used should handle scaling correctly another time', async function () {
var stage = addStage();
var layer = new Konva.Layer();
stage.add(layer);
var group = new Konva.Group({
x: 400,
});
layer.add(group);
var text = new Konva.Text({
text: 'hello',
fontSize: 300,
fill: 'green',
shadowColor: 'black',
});
group.add(text);
const canvas1 = group.toCanvas({
x: group.x(),
y: group.y(),
width: text.width(),
height: text.height(),
});
text.stroke('transparent');
const canvas2 = group.toCanvas({
x: group.x(),
y: group.y(),
width: text.width(),
height: text.height(),
});
compareCanvases(canvas2, canvas1, 240, 110);
});
// ======================================================
it('optional disable shadow for stroke', function () {
var stage = addStage();
@@ -1582,6 +1653,35 @@ describe('Shape', function () {
assert.equal(rect.height, 100, 'should not effect width');
});
it('getClientRect should not use cached values', function () {
var stage = addStage();
var layer = new Konva.Layer();
var shape = new Konva.Rect({
x: 100,
y: 100,
width: 100,
height: 100,
fill: 'green',
stroke: 'black',
strokeWidth: 4,
strokeEnabled: false,
shadowOffsetX: 10,
shadowEnabled: false,
});
layer.add(shape);
stage.add(layer);
layer.cache();
layer.scaleX(2);
const rect = shape.getClientRect();
assert.equal(rect.x, 200);
});
it('getClientRect for shape in transformed parent', function () {
var stage = addStage();
var layer = new Konva.Layer();
@@ -2203,4 +2303,36 @@ describe('Shape', function () {
assert.equal(callCount, 0);
Konva.Util.warn = oldWarn;
});
it('fill rule on hit graph', function () {
var stage = addStage();
var layer = new Konva.Layer();
stage.add(layer);
var mask = new Konva.Shape({
sceneFunc: function (ctx, shape) {
ctx.beginPath();
ctx.rect(0, 0, 500, 500);
ctx.rect(100, 100, 100, 100);
ctx.closePath();
ctx.fillShape(shape);
},
draggable: true,
fill: 'red',
fillRule: 'evenodd',
});
layer.add(mask);
layer.draw();
const trace = layer.getContext().getTrace();
assert.equal(
trace,
'clearRect(0,0,578,200);clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();rect(0,0,500,500);rect(100,100,100,100);closePath();fillStyle=red;fill(evenodd);restore();'
);
const hitShape = layer.getIntersection({ x: 150, y: 150 });
assert.equal(hitShape, null);
});
});

View File

@@ -1654,7 +1654,7 @@ describe('Text', function () {
assert.equal(layer.getContext().getTrace(), trace);
});
it('sets ltr text direction', function () {
var stage = addStage();
var layer = new Konva.Layer();
@@ -1673,9 +1673,8 @@ describe('Text', function () {
assert.equal(layer.getContext().getTrace(), trace);
});
it('sets rtl text direction', function () {
it('sets rtl text direction', function () {
var stage = addStage();
var layer = new Konva.Layer();
@@ -1693,7 +1692,7 @@ describe('Text', function () {
assert.equal(layer.getContext().getTrace(), trace);
});
it('sets rtl text direction with letterSpacing', function () {
var stage = addStage();
var layer = new Konva.Layer();
@@ -1713,4 +1712,22 @@ describe('Text', function () {
assert.equal(layer.getContext().getTrace(), trace);
});
it('try fixed render', () => {
Konva._fixTextRendering = true;
var stage = addStage();
var layer = new Konva.Layer();
stage.add(layer);
var text = new Konva.Text({ text: 'hello', fontSize: 100 });
layer.add(text);
layer.draw();
Konva._fixTextRendering = false;
const trace =
'clearRect(0,0,578,200);clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);font=normal normal 100px Arial;textBaseline=alphabetic;textAlign=left;translate(0,0);save();fillStyle=black;fillText(hello,0,85);restore();restore();';
assert.equal(layer.getContext().getTrace(), trace);
});
});

View File

@@ -460,11 +460,11 @@ describe('TouchEvents', function () {
);
assert.equal(touchEnd, 1);
assert.equal(stageTouchEnd, 1);
assert.equal(stageTap, 2, 'one tap should be fired');
assert.equal(stageTap, 1, 'one tap should be fired');
assert.equal(
stageEventStack.join(' '),
'touchstart touchstart touchstart touchend tap tap',
'touchstart touchstart touchstart touchend tap',
'should fire tap after touchend'
);
@@ -481,7 +481,7 @@ describe('TouchEvents', function () {
assert.equal(touchEnd, 2);
assert.equal(touchEnd2, 1);
assert.equal(stageTouchEnd, 3);
assert.equal(stageTap, 2, 'still one tap should be fired');
assert.equal(stageTap, 1, 'still one tap should be fired');
// Don't need to check event stack here, the pointers moved so no tap is fired
});

View File

@@ -4929,4 +4929,163 @@ describe('Transformer', function () {
assertAlmostEqual(rect.scaleX(), 1);
assertAlmostEqual(rect.scaleY(), 1);
});
it('should be able to prevent rotation in transform event', function () {
var stage = addStage();
var layer = new Konva.Layer();
stage.add(layer);
var rect = new Konva.Rect({
x: 55,
y: 55,
draggable: true,
width: 100,
height: 100,
fill: 'yellow',
});
layer.add(rect);
var tr = new Konva.Transformer({
nodes: [rect],
});
layer.add(tr);
layer.draw();
tr.on('transform', function (e) {
tr.rotation(0);
});
simulateMouseDown(tr, {
x: 100,
y: 2,
});
simulateMouseMove(tr, {
x: 110,
y: 2,
});
assert.equal(tr.rotation(), 0);
simulateMouseUp(tr, { x: 110, y: 2 });
});
it('skip render on hit graph while transforming', function (done) {
var stage = addStage();
var layer = new Konva.Layer();
stage.add(layer);
var rect = new Konva.Rect({
x: 55,
y: 55,
draggable: true,
width: 100,
height: 100,
fill: 'yellow',
});
layer.add(rect);
var tr = new Konva.Transformer({
nodes: [rect],
});
layer.add(tr);
layer.draw();
simulateMouseDown(tr, {
x: 100,
y: 2,
});
simulateMouseMove(tr, {
x: 110,
y: 2,
});
let shape = layer.getIntersection({
x: 100,
y: 100,
});
assert.equal(shape, null);
simulateMouseUp(tr, { x: 110, y: 2 });
layer.draw();
shape = layer.getIntersection({
x: 100,
y: 100,
});
assert.equal(shape, rect);
// reset position
rect.setAttrs({
x: 50,
y: 50,
draggable: true,
width: 100,
height: 100,
});
tr.nodes([rect]);
layer.draw();
// now check if graph is visible back when we moved a bit
simulateMouseDown(tr, {
x: 100,
y: 2,
});
simulateMouseMove(tr, {
x: 110,
y: 2,
});
setTimeout(() => {
shape = layer.getIntersection({
x: 100,
y: 100,
});
assert.equal(shape, null);
simulateMouseUp(tr, { x: 110, y: 2 });
setTimeout(() => {
shape = layer.getIntersection({
x: 100,
y: 100,
});
assert.equal(shape, rect);
done();
}, 100);
}, 100);
});
it('enable hit graph back on transformer destroy', function (done) {
var stage = addStage();
var layer = new Konva.Layer();
stage.add(layer);
var rect = new Konva.Rect({
x: 55,
y: 55,
draggable: true,
width: 100,
height: 100,
fill: 'yellow',
});
layer.add(rect);
var tr = new Konva.Transformer({
nodes: [rect],
});
layer.add(tr);
layer.draw();
// now check if graph is visible back when we moved a bit
simulateMouseDown(tr, {
x: 100,
y: 2,
});
simulateMouseMove(tr, {
x: 110,
y: 2,
});
setTimeout(() => {
tr.destroy();
setTimeout(() => {
var shape = layer.getIntersection({
x: 100,
y: 100,
});
assert.equal(shape, rect);
done();
}, 100);
}, 100);
});
});

View File

@@ -14,9 +14,11 @@
"noImplicitAny": false,
"noImplicitThis": false,
"useUnknownInCatchVariables": false,
"skipLibCheck": true,
// probably we would never enable this one
// because it's too strict, konva generates many functions on the runtime
"strictPropertyInitialization": false,
},
"include": ["./src/**/*.ts"]
"include": ["./src/**/*.ts"],
}