@nasser-sw/fabric 7.0.1-beta6 → 7.0.1-beta8
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/dist/index.js +79 -12
- 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 +79 -12
- package/dist/index.mjs.map +1 -1
- package/dist/index.node.cjs +79 -12
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.mjs +79 -12
- 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 +1 -0
- 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 +79 -11
- package/dist/src/shapes/Line.mjs.map +1 -1
- package/dist-extensions/src/shapes/Line.d.ts +1 -0
- package/dist-extensions/src/shapes/Line.d.ts.map +1 -1
- package/fabric-test2.html +134 -0
- package/package.json +1 -1
- package/src/shapes/Line.ts +147 -50
package/src/shapes/Line.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { CENTER, LEFT, TOP } from '../constants';
|
|
|
12
12
|
import type { CSSRules } from '../parser/typedefs';
|
|
13
13
|
import { Control } from '../controls/Control';
|
|
14
14
|
import type { TPointerEvent, Transform } from '../EventTypeDefs';
|
|
15
|
-
import {
|
|
15
|
+
import { Gradient } from '../gradient/Gradient';
|
|
16
16
|
|
|
17
17
|
const coordProps = ['x1', 'x2', 'y1', 'y2'] as const;
|
|
18
18
|
|
|
@@ -27,10 +27,10 @@ export interface SerializedLineProps
|
|
|
27
27
|
extends SerializedObjectProps,
|
|
28
28
|
UniqueLineCoords {}
|
|
29
29
|
|
|
30
|
-
export class Line<
|
|
30
|
+
export class Line<
|
|
31
31
|
Props extends TOptions<FabricObjectProps> = Partial<FabricObjectProps>,
|
|
32
32
|
SProps extends SerializedLineProps = SerializedLineProps,
|
|
33
|
-
EventSpec extends ObjectEvents = ObjectEvents
|
|
33
|
+
EventSpec extends ObjectEvents = ObjectEvents,
|
|
34
34
|
>
|
|
35
35
|
extends FabricObject<Props, SProps, EventSpec>
|
|
36
36
|
implements UniqueLineCoords
|
|
@@ -44,11 +44,15 @@ export class Line<
|
|
|
44
44
|
|
|
45
45
|
private _updatingEndpoints = false;
|
|
46
46
|
private _useEndpointCoords = true;
|
|
47
|
+
private _exportingSVG = false;
|
|
47
48
|
|
|
48
49
|
static type = 'Line';
|
|
49
50
|
static cacheProperties = [...cacheProperties, ...coordProps];
|
|
50
51
|
|
|
51
|
-
constructor(
|
|
52
|
+
constructor(
|
|
53
|
+
[x1, y1, x2, y2] = [0, 0, 100, 0],
|
|
54
|
+
options: Partial<Props & { hitStrokeWidth?: number | 'auto' }> = {},
|
|
55
|
+
) {
|
|
52
56
|
super();
|
|
53
57
|
this.setOptions(options);
|
|
54
58
|
this.x1 = x1;
|
|
@@ -110,7 +114,7 @@ export class Line<
|
|
|
110
114
|
_renderEndpointControl(
|
|
111
115
|
ctx: CanvasRenderingContext2D,
|
|
112
116
|
left: number,
|
|
113
|
-
top: number
|
|
117
|
+
top: number,
|
|
114
118
|
) {
|
|
115
119
|
const size = 12;
|
|
116
120
|
ctx.save();
|
|
@@ -137,7 +141,9 @@ export class Line<
|
|
|
137
141
|
ctx.save();
|
|
138
142
|
ctx.setTransform(vpt[0], vpt[1], vpt[2], vpt[3], vpt[4], vpt[5]);
|
|
139
143
|
ctx.strokeStyle =
|
|
140
|
-
styleOverride.borderColor ||
|
|
144
|
+
styleOverride.borderColor ||
|
|
145
|
+
this.borderColor ||
|
|
146
|
+
'rgba(100, 200, 200, 0.5)';
|
|
141
147
|
ctx.lineWidth = (this.strokeWidth || 1) + 5;
|
|
142
148
|
ctx.lineCap = this.strokeLineCap || 'butt';
|
|
143
149
|
ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
|
|
@@ -159,9 +165,7 @@ export class Line<
|
|
|
159
165
|
if (this._useEndpointCoords) {
|
|
160
166
|
const { x1, y1, x2, y2 } = this;
|
|
161
167
|
const effectiveStrokeWidth =
|
|
162
|
-
this.hitStrokeWidth === 'auto'
|
|
163
|
-
? this.strokeWidth
|
|
164
|
-
: this.hitStrokeWidth;
|
|
168
|
+
this.hitStrokeWidth === 'auto' ? this.strokeWidth : this.hitStrokeWidth;
|
|
165
169
|
const padding = Math.max(effectiveStrokeWidth / 2 + 5, 10);
|
|
166
170
|
return {
|
|
167
171
|
left: Math.min(x1, x2) - padding,
|
|
@@ -175,19 +179,19 @@ export class Line<
|
|
|
175
179
|
|
|
176
180
|
setCoords() {
|
|
177
181
|
if (this._useEndpointCoords) {
|
|
178
|
-
// Set the object's center to the geometric center of the line
|
|
179
|
-
const center = this._findCenterFromElement();
|
|
180
|
-
this.left = center.x;
|
|
181
|
-
this.top = center.y;
|
|
182
|
-
|
|
183
182
|
// Set width and height for hit detection and bounding box
|
|
184
183
|
const effectiveStrokeWidth =
|
|
185
|
-
this.hitStrokeWidth === 'auto'
|
|
186
|
-
? this.strokeWidth
|
|
187
|
-
: this.hitStrokeWidth;
|
|
184
|
+
this.hitStrokeWidth === 'auto' ? this.strokeWidth : this.hitStrokeWidth;
|
|
188
185
|
const hitPadding = Math.max(effectiveStrokeWidth / 2 + 5, 10);
|
|
189
186
|
this.width = Math.abs(this.x2 - this.x1) + hitPadding * 2;
|
|
190
187
|
this.height = Math.abs(this.y2 - this.y1) + hitPadding * 2;
|
|
188
|
+
|
|
189
|
+
// Only update left/top if they haven't been explicitly set (e.g., during loading)
|
|
190
|
+
if (this.left === 0 && this.top === 0) {
|
|
191
|
+
const center = this._findCenterFromElement();
|
|
192
|
+
this.left = center.x;
|
|
193
|
+
this.top = center.y;
|
|
194
|
+
}
|
|
191
195
|
}
|
|
192
196
|
super.setCoords();
|
|
193
197
|
}
|
|
@@ -197,20 +201,19 @@ export class Line<
|
|
|
197
201
|
const deltaX = this.x2 - this.x1;
|
|
198
202
|
const deltaY = this.y2 - this.y1;
|
|
199
203
|
const length = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
|
200
|
-
|
|
204
|
+
|
|
201
205
|
if (length === 0) {
|
|
202
206
|
return super.getCoords() as [Point, Point, Point, Point];
|
|
203
207
|
}
|
|
204
|
-
|
|
205
|
-
const effectiveStrokeWidth =
|
|
206
|
-
? this.strokeWidth
|
|
207
|
-
: this.hitStrokeWidth;
|
|
208
|
+
|
|
209
|
+
const effectiveStrokeWidth =
|
|
210
|
+
this.hitStrokeWidth === 'auto' ? this.strokeWidth : this.hitStrokeWidth;
|
|
208
211
|
const halfWidth = Math.max(effectiveStrokeWidth / 2 + 2, 5);
|
|
209
|
-
|
|
212
|
+
|
|
210
213
|
// Unit vector perpendicular to line
|
|
211
214
|
const perpX = -deltaY / length;
|
|
212
215
|
const perpY = deltaX / length;
|
|
213
|
-
|
|
216
|
+
|
|
214
217
|
// Four corners of oriented rectangle
|
|
215
218
|
return [
|
|
216
219
|
new Point(this.x1 + perpX * halfWidth, this.y1 + perpY * halfWidth),
|
|
@@ -228,10 +231,11 @@ export class Line<
|
|
|
228
231
|
return super.containsPoint(point);
|
|
229
232
|
}
|
|
230
233
|
const distance = this._distanceToLineSegment(point.x, point.y);
|
|
231
|
-
const effectiveStrokeWidth =
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
234
|
+
const effectiveStrokeWidth =
|
|
235
|
+
this.hitStrokeWidth === 'auto'
|
|
236
|
+
? this.strokeWidth
|
|
237
|
+
: this.hitStrokeWidth || 1;
|
|
238
|
+
|
|
235
239
|
const tolerance = Math.max(effectiveStrokeWidth / 2 + 2, 5);
|
|
236
240
|
return distance <= tolerance;
|
|
237
241
|
}
|
|
@@ -239,15 +243,18 @@ export class Line<
|
|
|
239
243
|
}
|
|
240
244
|
|
|
241
245
|
_distanceToLineSegment(px: number, py: number): number {
|
|
242
|
-
const x1 = this.x1,
|
|
243
|
-
|
|
246
|
+
const x1 = this.x1,
|
|
247
|
+
y1 = this.y1,
|
|
248
|
+
x2 = this.x2,
|
|
249
|
+
y2 = this.y2;
|
|
250
|
+
|
|
244
251
|
const pd2 = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2);
|
|
245
252
|
if (pd2 === 0) {
|
|
246
253
|
return Math.sqrt((px - x1) * (px - x1) + (py - y1) * (py - y1));
|
|
247
254
|
}
|
|
248
|
-
|
|
255
|
+
|
|
249
256
|
const u = ((px - x1) * (x2 - x1) + (py - y1) * (y2 - y1)) / pd2;
|
|
250
|
-
|
|
257
|
+
|
|
251
258
|
let closestX: number, closestY: number;
|
|
252
259
|
if (u < 0) {
|
|
253
260
|
closestX = x1;
|
|
@@ -259,15 +266,17 @@ export class Line<
|
|
|
259
266
|
closestX = x1 + u * (x2 - x1);
|
|
260
267
|
closestY = y1 + u * (y2 - y1);
|
|
261
268
|
}
|
|
262
|
-
|
|
263
|
-
return Math.sqrt(
|
|
269
|
+
|
|
270
|
+
return Math.sqrt(
|
|
271
|
+
(px - closestX) * (px - closestX) + (py - closestY) * (py - closestY),
|
|
272
|
+
);
|
|
264
273
|
}
|
|
265
274
|
|
|
266
275
|
_endpointActionHandler(
|
|
267
276
|
eventData: TPointerEvent,
|
|
268
277
|
transformData: Transform,
|
|
269
278
|
x: number,
|
|
270
|
-
y: number
|
|
279
|
+
y: number,
|
|
271
280
|
) {
|
|
272
281
|
const controlKey = transformData.corner;
|
|
273
282
|
const pointer = new Point(x, y);
|
|
@@ -291,6 +300,15 @@ export class Line<
|
|
|
291
300
|
this.x2 = newX;
|
|
292
301
|
this.y2 = newY;
|
|
293
302
|
}
|
|
303
|
+
|
|
304
|
+
// Update gradient coordinates if stroke is a gradient (but not during SVG export)
|
|
305
|
+
if (this.stroke instanceof Gradient && !this._exportingSVG) {
|
|
306
|
+
this.stroke.coords.x1 = this.x1;
|
|
307
|
+
this.stroke.coords.y1 = this.y1;
|
|
308
|
+
this.stroke.coords.x2 = this.x2;
|
|
309
|
+
this.stroke.coords.y2 = this.y2;
|
|
310
|
+
}
|
|
311
|
+
|
|
294
312
|
this.dirty = true;
|
|
295
313
|
this.setCoords();
|
|
296
314
|
this.canvas?.requestRenderAll();
|
|
@@ -310,7 +328,11 @@ export class Line<
|
|
|
310
328
|
this.dirty = true;
|
|
311
329
|
this._updatingEndpoints = false;
|
|
312
330
|
this.canvas?.requestRenderAll();
|
|
313
|
-
this.fire('modified', {
|
|
331
|
+
this.fire('modified', {
|
|
332
|
+
transform: transformData,
|
|
333
|
+
target: this,
|
|
334
|
+
e: eventData,
|
|
335
|
+
});
|
|
314
336
|
return true;
|
|
315
337
|
}
|
|
316
338
|
|
|
@@ -318,7 +340,7 @@ export class Line<
|
|
|
318
340
|
fromX: number,
|
|
319
341
|
fromY: number,
|
|
320
342
|
toX: number,
|
|
321
|
-
toY: number
|
|
343
|
+
toY: number,
|
|
322
344
|
): { x: number; y: number } {
|
|
323
345
|
const deltaX = toX - fromX;
|
|
324
346
|
const deltaY = toY - fromY;
|
|
@@ -345,7 +367,7 @@ export class Line<
|
|
|
345
367
|
this.setPositionByOrigin(
|
|
346
368
|
new Point(left + width / 2, top + height / 2),
|
|
347
369
|
CENTER,
|
|
348
|
-
CENTER
|
|
370
|
+
CENTER,
|
|
349
371
|
);
|
|
350
372
|
}
|
|
351
373
|
}
|
|
@@ -357,8 +379,20 @@ export class Line<
|
|
|
357
379
|
if (coordProps.includes(key as keyof UniqueLineCoords)) {
|
|
358
380
|
this._setWidthHeight();
|
|
359
381
|
this.dirty = true;
|
|
382
|
+
|
|
383
|
+
// Update gradient coordinates if stroke is a gradient (but not during SVG export)
|
|
384
|
+
if (this.stroke instanceof Gradient && !this._exportingSVG) {
|
|
385
|
+
this.stroke.coords.x1 = this.x1;
|
|
386
|
+
this.stroke.coords.y1 = this.y1;
|
|
387
|
+
this.stroke.coords.x2 = this.x2;
|
|
388
|
+
this.stroke.coords.y2 = this.y2;
|
|
389
|
+
}
|
|
360
390
|
}
|
|
361
|
-
if (
|
|
391
|
+
if (
|
|
392
|
+
(key === 'left' || key === 'top') &&
|
|
393
|
+
this.canvas &&
|
|
394
|
+
!this._updatingEndpoints
|
|
395
|
+
) {
|
|
362
396
|
const deltaX = this.left - oldLeft;
|
|
363
397
|
const deltaY = this.top - oldTop;
|
|
364
398
|
if (deltaX !== 0 || deltaY !== 0) {
|
|
@@ -367,6 +401,15 @@ export class Line<
|
|
|
367
401
|
this.y1 += deltaY;
|
|
368
402
|
this.x2 += deltaX;
|
|
369
403
|
this.y2 += deltaY;
|
|
404
|
+
|
|
405
|
+
// Update gradient coordinates if stroke is a gradient
|
|
406
|
+
if (this.stroke instanceof Gradient) {
|
|
407
|
+
this.stroke.coords.x1 = this.x1;
|
|
408
|
+
this.stroke.coords.y1 = this.y1;
|
|
409
|
+
this.stroke.coords.x2 = this.x2;
|
|
410
|
+
this.stroke.coords.y2 = this.y2;
|
|
411
|
+
}
|
|
412
|
+
|
|
370
413
|
this._updatingEndpoints = false;
|
|
371
414
|
}
|
|
372
415
|
}
|
|
@@ -385,13 +428,21 @@ export class Line<
|
|
|
385
428
|
if (!this.visible) return;
|
|
386
429
|
ctx.save();
|
|
387
430
|
ctx.globalAlpha = this.opacity;
|
|
388
|
-
ctx.strokeStyle = this.stroke?.toString() || '#000';
|
|
389
431
|
ctx.lineWidth = this.strokeWidth;
|
|
390
432
|
ctx.lineCap = this.strokeLineCap || 'butt';
|
|
391
433
|
ctx.beginPath();
|
|
392
434
|
ctx.moveTo(this.x1, this.y1);
|
|
393
435
|
ctx.lineTo(this.x2, this.y2);
|
|
436
|
+
|
|
437
|
+
const origStrokeStyle = ctx.strokeStyle;
|
|
438
|
+
if (isFiller(this.stroke)) {
|
|
439
|
+
ctx.strokeStyle = this.stroke.toLive(ctx)!;
|
|
440
|
+
} else {
|
|
441
|
+
ctx.strokeStyle = this.stroke?.toString() || '#000';
|
|
442
|
+
}
|
|
443
|
+
|
|
394
444
|
ctx.stroke();
|
|
445
|
+
ctx.strokeStyle = origStrokeStyle;
|
|
395
446
|
ctx.restore();
|
|
396
447
|
}
|
|
397
448
|
|
|
@@ -414,10 +465,19 @@ export class Line<
|
|
|
414
465
|
return new Point((this.x1 + this.x2) / 2, (this.y1 + this.y2) / 2);
|
|
415
466
|
}
|
|
416
467
|
|
|
417
|
-
toObject<
|
|
468
|
+
toObject<
|
|
418
469
|
T extends Omit<Props & TClassProperties<this>, keyof SProps>,
|
|
419
|
-
K extends keyof T = never
|
|
470
|
+
K extends keyof T = never,
|
|
420
471
|
>(propertiesToInclude: K[] = []): Pick<T, K> & SProps {
|
|
472
|
+
if (this._useEndpointCoords) {
|
|
473
|
+
return {
|
|
474
|
+
...super.toObject(propertiesToInclude),
|
|
475
|
+
x1: this.x1,
|
|
476
|
+
y1: this.y1,
|
|
477
|
+
x2: this.x2,
|
|
478
|
+
y2: this.y2,
|
|
479
|
+
};
|
|
480
|
+
}
|
|
421
481
|
return {
|
|
422
482
|
...super.toObject(propertiesToInclude),
|
|
423
483
|
...this.calcLinePoints(),
|
|
@@ -458,8 +518,21 @@ export class Line<
|
|
|
458
518
|
_toSVG() {
|
|
459
519
|
if (this._useEndpointCoords) {
|
|
460
520
|
// Use absolute coordinates to bypass all Fabric.js transforms
|
|
521
|
+
// Handle gradients manually for proper SVG export
|
|
522
|
+
let strokeAttr = '';
|
|
523
|
+
if (this.stroke instanceof Gradient) {
|
|
524
|
+
// Let Fabric.js handle gradient definition, but we'll use the reference
|
|
525
|
+
strokeAttr = `stroke="url(#${this.stroke.id})"`;
|
|
526
|
+
} else {
|
|
527
|
+
strokeAttr = `stroke="${this.stroke || 'none'}"`;
|
|
528
|
+
}
|
|
529
|
+
|
|
461
530
|
return [
|
|
462
|
-
`<line
|
|
531
|
+
`<line ${strokeAttr} stroke-width="${this.strokeWidth}" stroke-linecap="${this.strokeLineCap}" `,
|
|
532
|
+
`stroke-dasharray="${this.strokeDashArray ? this.strokeDashArray.join(' ') : 'none'}" `,
|
|
533
|
+
`stroke-dashoffset="${this.strokeDashOffset}" stroke-linejoin="${this.strokeLineJoin}" `,
|
|
534
|
+
`stroke-miterlimit="${this.strokeMiterLimit}" fill="${this.fill || 'none'}" `,
|
|
535
|
+
`fill-rule="${this.fillRule}" opacity="${this.opacity}" `,
|
|
463
536
|
`x1="${this.x1}" y1="${this.y1}" x2="${this.x2}" y2="${this.y2}" />\n`,
|
|
464
537
|
];
|
|
465
538
|
} else {
|
|
@@ -475,9 +548,33 @@ export class Line<
|
|
|
475
548
|
|
|
476
549
|
toSVG(reviver?: (markup: string) => string): string {
|
|
477
550
|
if (this._useEndpointCoords) {
|
|
478
|
-
//
|
|
479
|
-
|
|
480
|
-
|
|
551
|
+
// For endpoint coords, we need to bypass transforms but still allow gradients
|
|
552
|
+
// Let's temporarily disable transforms during SVG generation
|
|
553
|
+
const originalLeft = this.left;
|
|
554
|
+
const originalTop = this.top;
|
|
555
|
+
|
|
556
|
+
// Set position to center of line for gradient calculation
|
|
557
|
+
this.left = (this.x1 + this.x2) / 2;
|
|
558
|
+
this.top = (this.y1 + this.y2) / 2;
|
|
559
|
+
|
|
560
|
+
// Get the SVG with standard system (for gradient handling)
|
|
561
|
+
const standardSVG = super.toSVG(reviver);
|
|
562
|
+
|
|
563
|
+
// Restore original position
|
|
564
|
+
this.left = originalLeft;
|
|
565
|
+
this.top = originalTop;
|
|
566
|
+
|
|
567
|
+
// Extract gradient definition and clean up the line element
|
|
568
|
+
// Remove the transform wrapper and update coordinates
|
|
569
|
+
const cleanSVG = standardSVG
|
|
570
|
+
.replace(/<g transform="[^"]*"[^>]*>/g, '')
|
|
571
|
+
.replace(/<\/g>/g, '')
|
|
572
|
+
.replace(/x1="[^"]*"/g, `x1="${this.x1}"`)
|
|
573
|
+
.replace(/y1="[^"]*"/g, `y1="${this.y1}"`)
|
|
574
|
+
.replace(/x2="[^"]*"/g, `x2="${this.x2}"`)
|
|
575
|
+
.replace(/y2="[^"]*"/g, `y2="${this.y2}"`);
|
|
576
|
+
|
|
577
|
+
return cleanSVG;
|
|
481
578
|
}
|
|
482
579
|
// Use default behavior for legacy mode
|
|
483
580
|
return super.toSVG(reviver);
|
|
@@ -488,7 +585,7 @@ export class Line<
|
|
|
488
585
|
static async fromElement(
|
|
489
586
|
element: HTMLElement,
|
|
490
587
|
options?: Abortable,
|
|
491
|
-
cssRules?: CSSRules
|
|
588
|
+
cssRules?: CSSRules,
|
|
492
589
|
) {
|
|
493
590
|
const {
|
|
494
591
|
x1 = 0,
|
|
@@ -500,7 +597,7 @@ export class Line<
|
|
|
500
597
|
return new this([x1, y1, x2, y2], parsedAttributes);
|
|
501
598
|
}
|
|
502
599
|
|
|
503
|
-
static fromObject<T extends TOptions<SerializedLineProps>>({
|
|
600
|
+
static fromObject<T extends TOptions<SerializedLineProps>>({
|
|
504
601
|
x1,
|
|
505
602
|
y1,
|
|
506
603
|
x2,
|
|
@@ -509,10 +606,10 @@ export class Line<
|
|
|
509
606
|
}: T) {
|
|
510
607
|
return this._fromObject<Line>(
|
|
511
608
|
{ ...object, points: [x1, y1, x2, y2] },
|
|
512
|
-
{ extraParam: 'points' }
|
|
609
|
+
{ extraParam: 'points' },
|
|
513
610
|
);
|
|
514
611
|
}
|
|
515
612
|
}
|
|
516
613
|
|
|
517
614
|
classRegistry.setClass(Line);
|
|
518
|
-
classRegistry.setSVGClass(Line);
|
|
615
|
+
classRegistry.setSVGClass(Line);
|