@nasser-sw/fabric 7.0.1-beta3 → 7.0.1-beta5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/0 +0 -0
- package/dist/index.js +345 -162
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/index.min.mjs +1 -1
- package/dist/index.min.mjs.map +1 -1
- package/dist/index.mjs +345 -162
- package/dist/index.mjs.map +1 -1
- package/dist/index.node.cjs +345 -162
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.mjs +345 -162
- package/dist/index.node.mjs.map +1 -1
- package/dist/package.json.min.mjs +1 -1
- package/dist/package.json.mjs +1 -1
- package/dist/src/shapes/Line.d.ts +32 -86
- package/dist/src/shapes/Line.d.ts.map +1 -1
- package/dist/src/shapes/Line.min.mjs +1 -1
- package/dist/src/shapes/Line.min.mjs.map +1 -1
- package/dist/src/shapes/Line.mjs +345 -161
- package/dist/src/shapes/Line.mjs.map +1 -1
- package/dist-extensions/src/shapes/CustomLine.d.ts +10 -0
- package/dist-extensions/src/shapes/CustomLine.d.ts.map +1 -0
- package/dist-extensions/src/shapes/Line.d.ts +32 -86
- package/dist-extensions/src/shapes/Line.d.ts.map +1 -1
- package/fabric-test-editor.html +157 -8
- package/fabric-test2.html +513 -0
- package/fabric.ts +182 -182
- package/package.json +1 -1
- package/src/shapes/Line.ts +397 -164
- package/debug/konva/CHANGELOG.md +0 -1474
- package/debug/konva/LICENSE +0 -22
- package/debug/konva/README.md +0 -205
- package/debug/konva/gulpfile.mjs +0 -110
- package/debug/konva/package.json +0 -139
- package/debug/konva/release.sh +0 -65
- package/debug/konva/resources/doc-includes/ContainerParams.txt +0 -6
- package/debug/konva/resources/doc-includes/NodeParams.txt +0 -20
- package/debug/konva/resources/doc-includes/ShapeParams.txt +0 -53
- package/debug/konva/resources/jsdoc.conf.json +0 -28
- package/debug/konva/rollup.config.mjs +0 -32
- package/debug/konva/src/Animation.ts +0 -237
- package/debug/konva/src/BezierFunctions.ts +0 -826
- package/debug/konva/src/Canvas.ts +0 -193
- package/debug/konva/src/Container.ts +0 -649
- package/debug/konva/src/Context.ts +0 -1017
- package/debug/konva/src/Core.ts +0 -5
- package/debug/konva/src/DragAndDrop.ts +0 -173
- package/debug/konva/src/Factory.ts +0 -246
- package/debug/konva/src/FastLayer.ts +0 -29
- package/debug/konva/src/Global.ts +0 -210
- package/debug/konva/src/Group.ts +0 -31
- package/debug/konva/src/Layer.ts +0 -546
- package/debug/konva/src/Node.ts +0 -3477
- package/debug/konva/src/PointerEvents.ts +0 -67
- package/debug/konva/src/Shape.ts +0 -2081
- package/debug/konva/src/Stage.ts +0 -1000
- package/debug/konva/src/Tween.ts +0 -811
- package/debug/konva/src/Util.ts +0 -1123
- package/debug/konva/src/Validators.ts +0 -210
- package/debug/konva/src/_CoreInternals.ts +0 -85
- package/debug/konva/src/_FullInternals.ts +0 -171
- package/debug/konva/src/canvas-backend.ts +0 -36
- package/debug/konva/src/filters/Blur.ts +0 -388
- package/debug/konva/src/filters/Brighten.ts +0 -48
- package/debug/konva/src/filters/Brightness.ts +0 -30
- package/debug/konva/src/filters/Contrast.ts +0 -75
- package/debug/konva/src/filters/Emboss.ts +0 -207
- package/debug/konva/src/filters/Enhance.ts +0 -154
- package/debug/konva/src/filters/Grayscale.ts +0 -25
- package/debug/konva/src/filters/HSL.ts +0 -108
- package/debug/konva/src/filters/HSV.ts +0 -106
- package/debug/konva/src/filters/Invert.ts +0 -23
- package/debug/konva/src/filters/Kaleidoscope.ts +0 -274
- package/debug/konva/src/filters/Mask.ts +0 -220
- package/debug/konva/src/filters/Noise.ts +0 -44
- package/debug/konva/src/filters/Pixelate.ts +0 -107
- package/debug/konva/src/filters/Posterize.ts +0 -46
- package/debug/konva/src/filters/RGB.ts +0 -82
- package/debug/konva/src/filters/RGBA.ts +0 -103
- package/debug/konva/src/filters/Sepia.ts +0 -27
- package/debug/konva/src/filters/Solarize.ts +0 -29
- package/debug/konva/src/filters/Threshold.ts +0 -44
- package/debug/konva/src/index.ts +0 -3
- package/debug/konva/src/shapes/Arc.ts +0 -176
- package/debug/konva/src/shapes/Arrow.ts +0 -231
- package/debug/konva/src/shapes/Circle.ts +0 -76
- package/debug/konva/src/shapes/Ellipse.ts +0 -121
- package/debug/konva/src/shapes/Image.ts +0 -319
- package/debug/konva/src/shapes/Label.ts +0 -386
- package/debug/konva/src/shapes/Line.ts +0 -364
- package/debug/konva/src/shapes/Path.ts +0 -1013
- package/debug/konva/src/shapes/Rect.ts +0 -79
- package/debug/konva/src/shapes/RegularPolygon.ts +0 -167
- package/debug/konva/src/shapes/Ring.ts +0 -94
- package/debug/konva/src/shapes/Sprite.ts +0 -370
- package/debug/konva/src/shapes/Star.ts +0 -125
- package/debug/konva/src/shapes/Text.ts +0 -1065
- package/debug/konva/src/shapes/TextPath.ts +0 -583
- package/debug/konva/src/shapes/Transformer.ts +0 -1889
- package/debug/konva/src/shapes/Wedge.ts +0 -129
- package/debug/konva/src/skia-backend.ts +0 -35
- package/debug/konva/src/types.ts +0 -84
- package/debug/konva/tsconfig.json +0 -31
- package/debug/konva/tsconfig.test.json +0 -7
package/src/shapes/Line.ts
CHANGED
|
@@ -10,12 +10,12 @@ import type { ObjectEvents } from '../EventTypeDefs';
|
|
|
10
10
|
import { makeBoundingBoxFromPoints } from '../util';
|
|
11
11
|
import { CENTER, LEFT, TOP } from '../constants';
|
|
12
12
|
import type { CSSRules } from '../parser/typedefs';
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
import { Control } from '../controls/Control';
|
|
14
|
+
import type { TPointerEvent, Transform } from '../EventTypeDefs';
|
|
15
15
|
|
|
16
16
|
const coordProps = ['x1', 'x2', 'y1', 'y2'] as const;
|
|
17
17
|
|
|
18
|
-
interface
|
|
18
|
+
interface UniqueLineCoords {
|
|
19
19
|
x1: number;
|
|
20
20
|
x2: number;
|
|
21
21
|
y1: number;
|
|
@@ -24,149 +24,402 @@ interface UniqueLineProps {
|
|
|
24
24
|
|
|
25
25
|
export interface SerializedLineProps
|
|
26
26
|
extends SerializedObjectProps,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
* A Class to draw a line
|
|
31
|
-
* A bunch of methods will be added to Polyline to handle the line case
|
|
32
|
-
* The line class is very strange to work with, is all special, it hardly aligns
|
|
33
|
-
* to what a developer want everytime there is an angle
|
|
34
|
-
* @deprecated
|
|
35
|
-
*/
|
|
36
|
-
export class Line<
|
|
27
|
+
UniqueLineCoords {}
|
|
28
|
+
|
|
29
|
+
export class Line<
|
|
37
30
|
Props extends TOptions<FabricObjectProps> = Partial<FabricObjectProps>,
|
|
38
31
|
SProps extends SerializedLineProps = SerializedLineProps,
|
|
39
|
-
EventSpec extends ObjectEvents = ObjectEvents
|
|
32
|
+
EventSpec extends ObjectEvents = ObjectEvents
|
|
40
33
|
>
|
|
41
34
|
extends FabricObject<Props, SProps, EventSpec>
|
|
42
|
-
implements
|
|
35
|
+
implements UniqueLineCoords
|
|
43
36
|
{
|
|
44
|
-
/**
|
|
45
|
-
* x value or first line edge
|
|
46
|
-
* @type number
|
|
47
|
-
*/
|
|
48
37
|
declare x1: number;
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* y value or first line edge
|
|
52
|
-
* @type number
|
|
53
|
-
*/
|
|
54
38
|
declare y1: number;
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* x value or second line edge
|
|
58
|
-
* @type number
|
|
59
|
-
*/
|
|
60
39
|
declare x2: number;
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* y value or second line edge
|
|
64
|
-
* @type number
|
|
65
|
-
*/
|
|
66
40
|
declare y2: number;
|
|
67
41
|
|
|
68
|
-
|
|
42
|
+
hitStrokeWidth: number | 'auto' = 'auto';
|
|
69
43
|
|
|
44
|
+
private _updatingEndpoints = false;
|
|
45
|
+
private _useEndpointCoords = true;
|
|
46
|
+
|
|
47
|
+
static type = 'Line';
|
|
70
48
|
static cacheProperties = [...cacheProperties, ...coordProps];
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
* @param {Array} [points] Array of points
|
|
74
|
-
* @param {Object} [options] Options object
|
|
75
|
-
* @return {Line} thisArg
|
|
76
|
-
*/
|
|
77
|
-
constructor([x1, y1, x2, y2] = [0, 0, 0, 0], options: Partial<Props> = {}) {
|
|
49
|
+
|
|
50
|
+
constructor([x1, y1, x2, y2] = [0, 0, 100, 0], options: Partial<Props & {hitStrokeWidth?: number | 'auto'}> = {}) {
|
|
78
51
|
super();
|
|
79
|
-
Object.assign(this, Line.ownDefaults);
|
|
80
52
|
this.setOptions(options);
|
|
81
53
|
this.x1 = x1;
|
|
82
54
|
this.x2 = x2;
|
|
83
55
|
this.y1 = y1;
|
|
84
56
|
this.y2 = y2;
|
|
57
|
+
|
|
58
|
+
if (options.hitStrokeWidth !== undefined) {
|
|
59
|
+
this.hitStrokeWidth = options.hitStrokeWidth;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
this.hasBorders = false;
|
|
63
|
+
this.hasControls = true;
|
|
64
|
+
this.selectable = true;
|
|
65
|
+
this.hoverCursor = 'move';
|
|
66
|
+
this.perPixelTargetFind = false;
|
|
67
|
+
this.strokeLineCap = 'butt';
|
|
68
|
+
|
|
85
69
|
this._setWidthHeight();
|
|
86
70
|
const { left, top } = options;
|
|
87
71
|
typeof left === 'number' && this.set(LEFT, left);
|
|
88
72
|
typeof top === 'number' && this.set(TOP, top);
|
|
73
|
+
this._setupLineControls();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
_setupLineControls() {
|
|
77
|
+
this.controls = {
|
|
78
|
+
p1: new Control({
|
|
79
|
+
x: 0,
|
|
80
|
+
y: 0,
|
|
81
|
+
cursorStyle: 'move',
|
|
82
|
+
actionHandler: this._endpointActionHandler.bind(this),
|
|
83
|
+
positionHandler: this._p1PositionHandler.bind(this),
|
|
84
|
+
render: this._renderEndpointControl.bind(this),
|
|
85
|
+
sizeX: 12,
|
|
86
|
+
sizeY: 12,
|
|
87
|
+
}),
|
|
88
|
+
p2: new Control({
|
|
89
|
+
x: 0,
|
|
90
|
+
y: 0,
|
|
91
|
+
cursorStyle: 'move',
|
|
92
|
+
actionHandler: this._endpointActionHandler.bind(this),
|
|
93
|
+
positionHandler: this._p2PositionHandler.bind(this),
|
|
94
|
+
render: this._renderEndpointControl.bind(this),
|
|
95
|
+
sizeX: 12,
|
|
96
|
+
sizeY: 12,
|
|
97
|
+
}),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
_p1PositionHandler() {
|
|
102
|
+
const vpt = this.canvas?.viewportTransform || [1, 0, 0, 1, 0, 0];
|
|
103
|
+
return new Point(this.x1, this.y1).transform(vpt);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
_p2PositionHandler() {
|
|
107
|
+
const vpt = this.canvas?.viewportTransform || [1, 0, 0, 1, 0, 0];
|
|
108
|
+
return new Point(this.x2, this.y2).transform(vpt);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
_renderEndpointControl(
|
|
112
|
+
ctx: CanvasRenderingContext2D,
|
|
113
|
+
left: number,
|
|
114
|
+
top: number
|
|
115
|
+
) {
|
|
116
|
+
const size = 12;
|
|
117
|
+
ctx.save();
|
|
118
|
+
ctx.fillStyle = '#007bff';
|
|
119
|
+
ctx.strokeStyle = '#ffffff';
|
|
120
|
+
ctx.lineWidth = 2;
|
|
121
|
+
ctx.beginPath();
|
|
122
|
+
ctx.arc(left, top, size / 2, 0, 2 * Math.PI);
|
|
123
|
+
ctx.fill();
|
|
124
|
+
ctx.stroke();
|
|
125
|
+
ctx.restore();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
drawBorders(ctx: CanvasRenderingContext2D, styleOverride: any = {}) {
|
|
129
|
+
if (this._useEndpointCoords) {
|
|
130
|
+
this._drawLineBorders(ctx, styleOverride);
|
|
131
|
+
return this;
|
|
132
|
+
}
|
|
133
|
+
return super.drawBorders(ctx, styleOverride, {});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
_drawLineBorders(ctx: CanvasRenderingContext2D, styleOverride: any = {}) {
|
|
137
|
+
const vpt = this.canvas?.viewportTransform || [1, 0, 0, 1, 0, 0];
|
|
138
|
+
ctx.save();
|
|
139
|
+
ctx.setTransform(vpt[0], vpt[1], vpt[2], vpt[3], vpt[4], vpt[5]);
|
|
140
|
+
ctx.strokeStyle =
|
|
141
|
+
styleOverride.borderColor || this.borderColor || 'rgba(100, 200, 200, 0.5)';
|
|
142
|
+
ctx.lineWidth = (this.strokeWidth || 1) + 5;
|
|
143
|
+
ctx.lineCap = this.strokeLineCap || 'butt';
|
|
144
|
+
ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
|
|
145
|
+
ctx.beginPath();
|
|
146
|
+
ctx.moveTo(this.x1, this.y1);
|
|
147
|
+
ctx.lineTo(this.x2, this.y2);
|
|
148
|
+
ctx.stroke();
|
|
149
|
+
ctx.restore();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
_renderControls(ctx: CanvasRenderingContext2D, styleOverride: any = {}) {
|
|
153
|
+
ctx.save();
|
|
154
|
+
ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
|
|
155
|
+
this.drawControls(ctx, styleOverride);
|
|
156
|
+
ctx.restore();
|
|
89
157
|
}
|
|
90
158
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
159
|
+
getBoundingRect() {
|
|
160
|
+
if (this._useEndpointCoords) {
|
|
161
|
+
const { x1, y1, x2, y2 } = this;
|
|
162
|
+
const effectiveStrokeWidth =
|
|
163
|
+
this.hitStrokeWidth === 'auto'
|
|
164
|
+
? this.strokeWidth
|
|
165
|
+
: this.hitStrokeWidth;
|
|
166
|
+
const padding = Math.max(effectiveStrokeWidth / 2 + 5, 10);
|
|
167
|
+
return {
|
|
168
|
+
left: Math.min(x1, x2) - padding,
|
|
169
|
+
top: Math.min(y1, y2) - padding,
|
|
170
|
+
width: Math.abs(x2 - x1) + padding * 2 || padding * 2,
|
|
171
|
+
height: Math.abs(y2 - y1) + padding * 2 || padding * 2,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
return super.getBoundingRect();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
setCoords() {
|
|
178
|
+
if (this._useEndpointCoords) {
|
|
179
|
+
// Set the object's center to the geometric center of the line
|
|
180
|
+
const center = this._findCenterFromElement();
|
|
181
|
+
this.left = center.x;
|
|
182
|
+
this.top = center.y;
|
|
183
|
+
|
|
184
|
+
// Set width and height for hit detection and bounding box
|
|
185
|
+
const effectiveStrokeWidth =
|
|
186
|
+
this.hitStrokeWidth === 'auto'
|
|
187
|
+
? this.strokeWidth
|
|
188
|
+
: this.hitStrokeWidth;
|
|
189
|
+
const hitPadding = Math.max(effectiveStrokeWidth / 2 + 5, 10);
|
|
190
|
+
this.width = Math.abs(this.x2 - this.x1) + hitPadding * 2;
|
|
191
|
+
this.height = Math.abs(this.y2 - this.y1) + hitPadding * 2;
|
|
192
|
+
}
|
|
193
|
+
super.setCoords();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
getCoords(): [Point, Point, Point, Point] {
|
|
197
|
+
if (this._useEndpointCoords) {
|
|
198
|
+
const deltaX = this.x2 - this.x1;
|
|
199
|
+
const deltaY = this.y2 - this.y1;
|
|
200
|
+
const length = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
|
201
|
+
|
|
202
|
+
if (length === 0) {
|
|
203
|
+
return super.getCoords() as [Point, Point, Point, Point];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const effectiveStrokeWidth = this.hitStrokeWidth === 'auto'
|
|
207
|
+
? this.strokeWidth
|
|
208
|
+
: this.hitStrokeWidth;
|
|
209
|
+
const halfWidth = Math.max(effectiveStrokeWidth / 2 + 2, 5);
|
|
210
|
+
|
|
211
|
+
// Unit vector perpendicular to line
|
|
212
|
+
const perpX = -deltaY / length;
|
|
213
|
+
const perpY = deltaX / length;
|
|
214
|
+
|
|
215
|
+
// Four corners of oriented rectangle
|
|
216
|
+
return [
|
|
217
|
+
new Point(this.x1 + perpX * halfWidth, this.y1 + perpY * halfWidth),
|
|
218
|
+
new Point(this.x2 + perpX * halfWidth, this.y2 + perpY * halfWidth),
|
|
219
|
+
new Point(this.x2 - perpX * halfWidth, this.y2 - perpY * halfWidth),
|
|
220
|
+
new Point(this.x1 - perpX * halfWidth, this.y1 - perpY * halfWidth),
|
|
221
|
+
];
|
|
222
|
+
}
|
|
223
|
+
return super.getCoords() as [Point, Point, Point, Point];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
containsPoint(point: Point): boolean {
|
|
227
|
+
if (this._useEndpointCoords) {
|
|
228
|
+
if (this.canvas?.getActiveObject() === this) {
|
|
229
|
+
return super.containsPoint(point);
|
|
230
|
+
}
|
|
231
|
+
const distance = this._distanceToLineSegment(point.x, point.y);
|
|
232
|
+
const effectiveStrokeWidth = this.hitStrokeWidth === 'auto'
|
|
233
|
+
? this.strokeWidth
|
|
234
|
+
: this.hitStrokeWidth || 1;
|
|
235
|
+
|
|
236
|
+
const tolerance = Math.max(effectiveStrokeWidth / 2 + 2, 5);
|
|
237
|
+
return distance <= tolerance;
|
|
238
|
+
}
|
|
239
|
+
return super.containsPoint(point);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
_distanceToLineSegment(px: number, py: number): number {
|
|
243
|
+
const x1 = this.x1, y1 = this.y1, x2 = this.x2, y2 = this.y2;
|
|
244
|
+
|
|
245
|
+
const pd2 = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2);
|
|
246
|
+
if (pd2 === 0) {
|
|
247
|
+
return Math.sqrt((px - x1) * (px - x1) + (py - y1) * (py - y1));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const u = ((px - x1) * (x2 - x1) + (py - y1) * (y2 - y1)) / pd2;
|
|
251
|
+
|
|
252
|
+
let closestX: number, closestY: number;
|
|
253
|
+
if (u < 0) {
|
|
254
|
+
closestX = x1;
|
|
255
|
+
closestY = y1;
|
|
256
|
+
} else if (u > 1) {
|
|
257
|
+
closestX = x2;
|
|
258
|
+
closestY = y2;
|
|
259
|
+
} else {
|
|
260
|
+
closestX = x1 + u * (x2 - x1);
|
|
261
|
+
closestY = y1 + u * (y2 - y1);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return Math.sqrt((px - closestX) * (px - closestX) + (py - closestY) * (py - closestY));
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
_endpointActionHandler(
|
|
268
|
+
eventData: TPointerEvent,
|
|
269
|
+
transformData: Transform,
|
|
270
|
+
x: number,
|
|
271
|
+
y: number
|
|
272
|
+
) {
|
|
273
|
+
const controlKey = transformData.corner;
|
|
274
|
+
const pointer = new Point(x, y);
|
|
275
|
+
let newX = pointer.x;
|
|
276
|
+
let newY = pointer.y;
|
|
277
|
+
|
|
278
|
+
if (eventData.shiftKey) {
|
|
279
|
+
const otherControl = controlKey === 'p1' ? 'p2' : 'p1';
|
|
280
|
+
const otherX = this[otherControl === 'p1' ? 'x1' : 'x2'];
|
|
281
|
+
const otherY = this[otherControl === 'p1' ? 'y1' : 'y2'];
|
|
282
|
+
const snapped = this._snapToAngle(otherX, otherY, newX, newY);
|
|
283
|
+
newX = snapped.x;
|
|
284
|
+
newY = snapped.y;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (this._useEndpointCoords) {
|
|
288
|
+
if (controlKey === 'p1') {
|
|
289
|
+
this.x1 = newX;
|
|
290
|
+
this.y1 = newY;
|
|
291
|
+
} else if (controlKey === 'p2') {
|
|
292
|
+
this.x2 = newX;
|
|
293
|
+
this.y2 = newY;
|
|
294
|
+
}
|
|
295
|
+
this.dirty = true;
|
|
296
|
+
this.setCoords();
|
|
297
|
+
this.canvas?.requestRenderAll();
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Fallback for old system
|
|
302
|
+
this._updatingEndpoints = true;
|
|
303
|
+
if (controlKey === 'p1') {
|
|
304
|
+
this.x1 = newX;
|
|
305
|
+
this.y1 = newY;
|
|
306
|
+
} else if (controlKey === 'p2') {
|
|
307
|
+
this.x2 = newX;
|
|
308
|
+
this.y2 = newY;
|
|
309
|
+
}
|
|
310
|
+
this._setWidthHeight();
|
|
311
|
+
this.dirty = true;
|
|
312
|
+
this._updatingEndpoints = false;
|
|
313
|
+
this.canvas?.requestRenderAll();
|
|
314
|
+
this.fire('modified', { transform: transformData, target: this, e: eventData });
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
_snapToAngle(
|
|
319
|
+
fromX: number,
|
|
320
|
+
fromY: number,
|
|
321
|
+
toX: number,
|
|
322
|
+
toY: number
|
|
323
|
+
): { x: number; y: number } {
|
|
324
|
+
const deltaX = toX - fromX;
|
|
325
|
+
const deltaY = toY - fromY;
|
|
326
|
+
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
|
327
|
+
if (distance === 0) return { x: toX, y: toY };
|
|
328
|
+
let angle = Math.atan2(deltaY, deltaX) * (180 / Math.PI);
|
|
329
|
+
const snapIncrement = 15;
|
|
330
|
+
const snappedAngle = Math.round(angle / snapIncrement) * snapIncrement;
|
|
331
|
+
const snappedRadians = snappedAngle * (Math.PI / 180);
|
|
332
|
+
return {
|
|
333
|
+
x: fromX + Math.cos(snappedRadians) * distance,
|
|
334
|
+
y: fromY + Math.sin(snappedRadians) * distance,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
_setWidthHeight(skipReposition = false) {
|
|
339
|
+
this.width = Math.abs(this.x2 - this.x1) || 1;
|
|
340
|
+
this.height = Math.abs(this.y2 - this.y1) || 1;
|
|
341
|
+
if (!skipReposition && !this._updatingEndpoints) {
|
|
342
|
+
const { left, top, width, height } = makeBoundingBoxFromPoints([
|
|
343
|
+
{ x: this.x1, y: this.y1 },
|
|
344
|
+
{ x: this.x2, y: this.y2 },
|
|
345
|
+
]);
|
|
346
|
+
this.setPositionByOrigin(
|
|
347
|
+
new Point(left + width / 2, top + height / 2),
|
|
348
|
+
CENTER,
|
|
349
|
+
CENTER
|
|
350
|
+
);
|
|
351
|
+
}
|
|
105
352
|
}
|
|
106
353
|
|
|
107
|
-
/**
|
|
108
|
-
* @private
|
|
109
|
-
* @param {String} key
|
|
110
|
-
* @param {*} value
|
|
111
|
-
*/
|
|
112
354
|
_set(key: string, value: any) {
|
|
355
|
+
const oldLeft = this.left;
|
|
356
|
+
const oldTop = this.top;
|
|
113
357
|
super._set(key, value);
|
|
114
|
-
if (coordProps.includes(key as keyof
|
|
115
|
-
// this doesn't make sense very much, since setting x1 when top or left
|
|
116
|
-
// are already set, is just going to show a strange result since the
|
|
117
|
-
// line will move way more than the developer expect.
|
|
118
|
-
// in fabric5 it worked only when the line didn't have extra transformations,
|
|
119
|
-
// in fabric6 too. With extra transform they behave bad in different ways.
|
|
120
|
-
// This needs probably a good rework or a tutorial if you have to create a dynamic line
|
|
358
|
+
if (coordProps.includes(key as keyof UniqueLineCoords)) {
|
|
121
359
|
this._setWidthHeight();
|
|
360
|
+
this.dirty = true;
|
|
361
|
+
}
|
|
362
|
+
if ((key === 'left' || key === 'top') && this.canvas && !this._updatingEndpoints) {
|
|
363
|
+
const deltaX = this.left - oldLeft;
|
|
364
|
+
const deltaY = this.top - oldTop;
|
|
365
|
+
if (deltaX !== 0 || deltaY !== 0) {
|
|
366
|
+
this._updatingEndpoints = true;
|
|
367
|
+
this.x1 += deltaX;
|
|
368
|
+
this.y1 += deltaY;
|
|
369
|
+
this.x2 += deltaX;
|
|
370
|
+
this.y2 += deltaY;
|
|
371
|
+
this._updatingEndpoints = false;
|
|
372
|
+
}
|
|
122
373
|
}
|
|
123
374
|
return this;
|
|
124
375
|
}
|
|
125
376
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
377
|
+
render(ctx: CanvasRenderingContext2D) {
|
|
378
|
+
if (this._useEndpointCoords) {
|
|
379
|
+
this._renderDirectly(ctx);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
super.render(ctx);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
_renderDirectly(ctx: CanvasRenderingContext2D) {
|
|
386
|
+
if (!this.visible) return;
|
|
387
|
+
ctx.save();
|
|
388
|
+
const vpt = this.canvas?.viewportTransform || [1, 0, 0, 1, 0, 0];
|
|
389
|
+
ctx.transform(vpt[0], vpt[1], vpt[2], vpt[3], vpt[4], vpt[5]);
|
|
390
|
+
ctx.globalAlpha = this.opacity;
|
|
391
|
+
ctx.strokeStyle = this.stroke?.toString() || '#000';
|
|
392
|
+
ctx.lineWidth = this.strokeWidth;
|
|
393
|
+
ctx.lineCap = this.strokeLineCap || 'butt';
|
|
131
394
|
ctx.beginPath();
|
|
395
|
+
ctx.moveTo(this.x1, this.y1);
|
|
396
|
+
ctx.lineTo(this.x2, this.y2);
|
|
397
|
+
ctx.stroke();
|
|
398
|
+
ctx.restore();
|
|
399
|
+
}
|
|
132
400
|
|
|
401
|
+
_render(ctx: CanvasRenderingContext2D) {
|
|
402
|
+
if (this._useEndpointCoords) return;
|
|
403
|
+
ctx.beginPath();
|
|
133
404
|
const p = this.calcLinePoints();
|
|
134
405
|
ctx.moveTo(p.x1, p.y1);
|
|
135
406
|
ctx.lineTo(p.x2, p.y2);
|
|
136
|
-
|
|
137
407
|
ctx.lineWidth = this.strokeWidth;
|
|
138
|
-
|
|
139
|
-
// TODO: test this
|
|
140
|
-
// make sure setting "fill" changes color of a line
|
|
141
|
-
// (by copying fillStyle to strokeStyle, since line is stroked, not filled)
|
|
142
408
|
const origStrokeStyle = ctx.strokeStyle;
|
|
143
409
|
if (isFiller(this.stroke)) {
|
|
144
410
|
ctx.strokeStyle = this.stroke.toLive(ctx)!;
|
|
145
|
-
} else {
|
|
146
|
-
ctx.strokeStyle = this.stroke ?? ctx.fillStyle;
|
|
147
411
|
}
|
|
148
412
|
this.stroke && this._renderStroke(ctx);
|
|
149
413
|
ctx.strokeStyle = origStrokeStyle;
|
|
150
414
|
}
|
|
151
415
|
|
|
152
|
-
/**
|
|
153
|
-
* This function is an helper for svg import. it returns the center of the object in the svg
|
|
154
|
-
* untransformed coordinates
|
|
155
|
-
* @private
|
|
156
|
-
* @return {Point} center point from element coordinates
|
|
157
|
-
*/
|
|
158
416
|
_findCenterFromElement(): Point {
|
|
159
417
|
return new Point((this.x1 + this.x2) / 2, (this.y1 + this.y2) / 2);
|
|
160
418
|
}
|
|
161
419
|
|
|
162
|
-
|
|
163
|
-
* Returns object representation of an instance
|
|
164
|
-
* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
|
|
165
|
-
* @return {Object} object representation of an instance
|
|
166
|
-
*/
|
|
167
|
-
toObject<
|
|
420
|
+
toObject<
|
|
168
421
|
T extends Omit<Props & TClassProperties<this>, keyof SProps>,
|
|
169
|
-
K extends keyof T = never
|
|
422
|
+
K extends keyof T = never
|
|
170
423
|
>(propertiesToInclude: K[] = []): Pick<T, K> & SProps {
|
|
171
424
|
return {
|
|
172
425
|
...super.toObject(propertiesToInclude),
|
|
@@ -174,79 +427,71 @@ export class Line<
|
|
|
174
427
|
};
|
|
175
428
|
}
|
|
176
429
|
|
|
177
|
-
/*
|
|
178
|
-
* Calculate object dimensions from its properties
|
|
179
|
-
* @private
|
|
180
|
-
*/
|
|
181
430
|
_getNonTransformedDimensions(): Point {
|
|
182
431
|
const dim = super._getNonTransformedDimensions();
|
|
183
|
-
if (this.strokeLineCap === '
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
if (this.height === 0) {
|
|
188
|
-
dim.x -= this.strokeWidth;
|
|
189
|
-
}
|
|
432
|
+
if (this.strokeLineCap === 'round') {
|
|
433
|
+
dim.x += this.strokeWidth;
|
|
434
|
+
dim.y += this.strokeWidth;
|
|
190
435
|
}
|
|
191
436
|
return dim;
|
|
192
437
|
}
|
|
193
438
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
439
|
+
calcLinePoints(): UniqueLineCoords {
|
|
440
|
+
if (this._updatingEndpoints) {
|
|
441
|
+
const centerX = (this.x1 + this.x2) / 2;
|
|
442
|
+
const centerY = (this.y1 + this.y2) / 2;
|
|
443
|
+
return {
|
|
444
|
+
x1: this.x1 - centerX,
|
|
445
|
+
y1: this.y1 - centerY,
|
|
446
|
+
x2: this.x2 - centerX,
|
|
447
|
+
y2: this.y2 - centerY,
|
|
448
|
+
};
|
|
449
|
+
}
|
|
202
450
|
const { x1: _x1, x2: _x2, y1: _y1, y2: _y2, width, height } = this;
|
|
203
|
-
const xMult = _x1 <= _x2 ? -1 : 1
|
|
204
|
-
|
|
205
|
-
x1 = (xMult * width) / 2,
|
|
206
|
-
y1 = (yMult * height) / 2,
|
|
207
|
-
x2 = (xMult * -width) / 2,
|
|
208
|
-
y2 = (yMult * -height) / 2;
|
|
209
|
-
|
|
451
|
+
const xMult = _x1 <= _x2 ? -1 : 1;
|
|
452
|
+
const yMult = _y1 <= _y2 ? -1 : 1;
|
|
210
453
|
return {
|
|
211
|
-
x1,
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
y2,
|
|
454
|
+
x1: (xMult * width) / 2,
|
|
455
|
+
y1: (yMult * height) / 2,
|
|
456
|
+
x2: (xMult * -width) / 2,
|
|
457
|
+
y2: (yMult * -height) / 2,
|
|
215
458
|
};
|
|
216
459
|
}
|
|
217
460
|
|
|
218
|
-
/* _FROM_SVG_START_ */
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Returns svg representation of an instance
|
|
222
|
-
* @return {Array} an array of strings with the specific svg representation
|
|
223
|
-
* of the instance
|
|
224
|
-
*/
|
|
225
461
|
_toSVG() {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
462
|
+
if (this._useEndpointCoords) {
|
|
463
|
+
// Use absolute coordinates to bypass all Fabric.js transforms
|
|
464
|
+
return [
|
|
465
|
+
`<line stroke="${this.stroke}" stroke-width="${this.strokeWidth}" stroke-linecap="${this.strokeLineCap}" `,
|
|
466
|
+
`x1="${this.x1}" y1="${this.y1}" x2="${this.x2}" y2="${this.y2}" />\n`,
|
|
467
|
+
];
|
|
468
|
+
} else {
|
|
469
|
+
// Use standard calcLinePoints for legacy mode
|
|
470
|
+
const { x1, x2, y1, y2 } = this.calcLinePoints();
|
|
471
|
+
return [
|
|
472
|
+
'<line ',
|
|
473
|
+
'COMMON_PARTS',
|
|
474
|
+
`x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" />\n`,
|
|
475
|
+
];
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
toSVG(reviver?: (markup: string) => string): string {
|
|
480
|
+
if (this._useEndpointCoords) {
|
|
481
|
+
// Override toSVG to prevent Fabric.js from adding transform wrapper
|
|
482
|
+
const markup = this._toSVG().join('');
|
|
483
|
+
return reviver ? reviver(markup) : markup;
|
|
484
|
+
}
|
|
485
|
+
// Use default behavior for legacy mode
|
|
486
|
+
return super.toSVG(reviver);
|
|
232
487
|
}
|
|
233
488
|
|
|
234
|
-
/**
|
|
235
|
-
* List of attribute names to account for when parsing SVG element (used by {@link Line.fromElement})
|
|
236
|
-
* @see http://www.w3.org/TR/SVG/shapes.html#LineElement
|
|
237
|
-
*/
|
|
238
489
|
static ATTRIBUTE_NAMES = SHARED_ATTRIBUTES.concat(coordProps);
|
|
239
490
|
|
|
240
|
-
/**
|
|
241
|
-
* Returns Line instance from an SVG element
|
|
242
|
-
* @param {HTMLElement} element Element to parse
|
|
243
|
-
* @param {Object} [options] Options object
|
|
244
|
-
* @param {Function} [callback] callback function invoked after parsing
|
|
245
|
-
*/
|
|
246
491
|
static async fromElement(
|
|
247
492
|
element: HTMLElement,
|
|
248
493
|
options?: Abortable,
|
|
249
|
-
cssRules?: CSSRules
|
|
494
|
+
cssRules?: CSSRules
|
|
250
495
|
) {
|
|
251
496
|
const {
|
|
252
497
|
x1 = 0,
|
|
@@ -258,14 +503,7 @@ export class Line<
|
|
|
258
503
|
return new this([x1, y1, x2, y2], parsedAttributes);
|
|
259
504
|
}
|
|
260
505
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* Returns Line instance from an object representation
|
|
265
|
-
* @param {Object} object Object to create an instance from
|
|
266
|
-
* @returns {Promise<Line>}
|
|
267
|
-
*/
|
|
268
|
-
static fromObject<T extends TOptions<SerializedLineProps>>({
|
|
506
|
+
static fromObject<T extends TOptions<SerializedLineProps>>({
|
|
269
507
|
x1,
|
|
270
508
|
y1,
|
|
271
509
|
x2,
|
|
@@ -273,16 +511,11 @@ export class Line<
|
|
|
273
511
|
...object
|
|
274
512
|
}: T) {
|
|
275
513
|
return this._fromObject<Line>(
|
|
276
|
-
{
|
|
277
|
-
|
|
278
|
-
points: [x1, y1, x2, y2],
|
|
279
|
-
},
|
|
280
|
-
{
|
|
281
|
-
extraParam: 'points',
|
|
282
|
-
},
|
|
514
|
+
{ ...object, points: [x1, y1, x2, y2] },
|
|
515
|
+
{ extraParam: 'points' }
|
|
283
516
|
);
|
|
284
517
|
}
|
|
285
518
|
}
|
|
286
519
|
|
|
287
520
|
classRegistry.setClass(Line);
|
|
288
|
-
classRegistry.setSVGClass(Line);
|
|
521
|
+
classRegistry.setSVGClass(Line);
|