@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.
@@ -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 { multiplyTransformMatrices } from '../util/misc/matrix';
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([x1, y1, x2, y2] = [0, 0, 100, 0], options: Partial<Props & {hitStrokeWidth?: number | 'auto'}> = {}) {
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 || this.borderColor || 'rgba(100, 200, 200, 0.5)';
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 = this.hitStrokeWidth === 'auto'
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 = this.hitStrokeWidth === 'auto'
232
- ? this.strokeWidth
233
- : this.hitStrokeWidth || 1;
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, y1 = this.y1, x2 = this.x2, y2 = this.y2;
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((px - closestX) * (px - closestX) + (py - closestY) * (py - closestY));
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', { transform: transformData, target: this, e: eventData });
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 ((key === 'left' || key === 'top') && this.canvas && !this._updatingEndpoints) {
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 stroke="${this.stroke}" stroke-width="${this.strokeWidth}" stroke-linecap="${this.strokeLineCap}" `,
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
- // Override toSVG to prevent Fabric.js from adding transform wrapper
479
- const markup = this._toSVG().join('');
480
- return reviver ? reviver(markup) : markup;
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);