@nasser-sw/fabric 7.0.1-beta1 → 7.0.1-beta10

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.
Files changed (181) hide show
  1. package/0 +0 -0
  2. package/debug/{konva → konva-master}/CHANGELOG.md +2 -1
  3. package/debug/{konva → konva-master}/README.md +7 -3
  4. package/debug/{konva → konva-master}/package.json +1 -1
  5. package/debug/{konva → konva-master}/release.sh +1 -4
  6. package/debug/{konva → konva-master}/src/Canvas.ts +37 -0
  7. package/debug/{konva → konva-master}/src/shapes/Text.ts +2 -2
  8. package/dist/index.js +1853 -288
  9. package/dist/index.js.map +1 -1
  10. package/dist/index.min.js +1 -1
  11. package/dist/index.min.js.map +1 -1
  12. package/dist/index.min.mjs +1 -1
  13. package/dist/index.min.mjs.map +1 -1
  14. package/dist/index.mjs +1853 -288
  15. package/dist/index.mjs.map +1 -1
  16. package/dist/index.node.cjs +1853 -288
  17. package/dist/index.node.cjs.map +1 -1
  18. package/dist/index.node.mjs +1853 -288
  19. package/dist/index.node.mjs.map +1 -1
  20. package/dist/package.json.min.mjs +1 -1
  21. package/dist/package.json.mjs +1 -1
  22. package/dist/src/shapes/Line.d.ts +33 -86
  23. package/dist/src/shapes/Line.d.ts.map +1 -1
  24. package/dist/src/shapes/Line.min.mjs +1 -1
  25. package/dist/src/shapes/Line.min.mjs.map +1 -1
  26. package/dist/src/shapes/Line.mjs +405 -159
  27. package/dist/src/shapes/Line.mjs.map +1 -1
  28. package/dist/src/shapes/Polyline.d.ts +7 -0
  29. package/dist/src/shapes/Polyline.d.ts.map +1 -1
  30. package/dist/src/shapes/Polyline.min.mjs +1 -1
  31. package/dist/src/shapes/Polyline.min.mjs.map +1 -1
  32. package/dist/src/shapes/Polyline.mjs +48 -16
  33. package/dist/src/shapes/Polyline.mjs.map +1 -1
  34. package/dist/src/shapes/Text/Text.d.ts +19 -0
  35. package/dist/src/shapes/Text/Text.d.ts.map +1 -1
  36. package/dist/src/shapes/Text/Text.min.mjs +1 -1
  37. package/dist/src/shapes/Text/Text.min.mjs.map +1 -1
  38. package/dist/src/shapes/Text/Text.mjs +302 -16
  39. package/dist/src/shapes/Text/Text.mjs.map +1 -1
  40. package/dist/src/shapes/Textbox.d.ts +43 -1
  41. package/dist/src/shapes/Textbox.d.ts.map +1 -1
  42. package/dist/src/shapes/Textbox.min.mjs +1 -1
  43. package/dist/src/shapes/Textbox.min.mjs.map +1 -1
  44. package/dist/src/shapes/Textbox.mjs +521 -67
  45. package/dist/src/shapes/Textbox.mjs.map +1 -1
  46. package/dist/src/shapes/Triangle.d.ts +27 -2
  47. package/dist/src/shapes/Triangle.d.ts.map +1 -1
  48. package/dist/src/shapes/Triangle.min.mjs +1 -1
  49. package/dist/src/shapes/Triangle.min.mjs.map +1 -1
  50. package/dist/src/shapes/Triangle.mjs +72 -12
  51. package/dist/src/shapes/Triangle.mjs.map +1 -1
  52. package/dist/src/text/examples/arabicTextExample.d.ts +60 -0
  53. package/dist/src/text/examples/arabicTextExample.d.ts.map +1 -0
  54. package/dist/src/text/measure.d.ts +9 -0
  55. package/dist/src/text/measure.d.ts.map +1 -1
  56. package/dist/src/text/measure.min.mjs +1 -1
  57. package/dist/src/text/measure.min.mjs.map +1 -1
  58. package/dist/src/text/measure.mjs +175 -4
  59. package/dist/src/text/measure.mjs.map +1 -1
  60. package/dist/src/text/overlayEditor.d.ts.map +1 -1
  61. package/dist/src/text/overlayEditor.min.mjs +1 -1
  62. package/dist/src/text/overlayEditor.min.mjs.map +1 -1
  63. package/dist/src/text/overlayEditor.mjs +155 -9
  64. package/dist/src/text/overlayEditor.mjs.map +1 -1
  65. package/dist/src/text/scriptUtils.d.ts +142 -0
  66. package/dist/src/text/scriptUtils.d.ts.map +1 -0
  67. package/dist/src/text/scriptUtils.min.mjs +2 -0
  68. package/dist/src/text/scriptUtils.min.mjs.map +1 -0
  69. package/dist/src/text/scriptUtils.mjs +212 -0
  70. package/dist/src/text/scriptUtils.mjs.map +1 -0
  71. package/dist/src/util/misc/cornerRadius.d.ts +70 -0
  72. package/dist/src/util/misc/cornerRadius.d.ts.map +1 -0
  73. package/dist/src/util/misc/cornerRadius.min.mjs +2 -0
  74. package/dist/src/util/misc/cornerRadius.min.mjs.map +1 -0
  75. package/dist/src/util/misc/cornerRadius.mjs +181 -0
  76. package/dist/src/util/misc/cornerRadius.mjs.map +1 -0
  77. package/dist-extensions/src/shapes/CustomLine.d.ts +10 -0
  78. package/dist-extensions/src/shapes/CustomLine.d.ts.map +1 -0
  79. package/dist-extensions/src/shapes/Line.d.ts +33 -86
  80. package/dist-extensions/src/shapes/Line.d.ts.map +1 -1
  81. package/dist-extensions/src/shapes/Polyline.d.ts +7 -0
  82. package/dist-extensions/src/shapes/Polyline.d.ts.map +1 -1
  83. package/dist-extensions/src/shapes/Text/Text.d.ts +19 -0
  84. package/dist-extensions/src/shapes/Text/Text.d.ts.map +1 -1
  85. package/dist-extensions/src/shapes/Textbox.d.ts +43 -1
  86. package/dist-extensions/src/shapes/Textbox.d.ts.map +1 -1
  87. package/dist-extensions/src/shapes/Triangle.d.ts +27 -2
  88. package/dist-extensions/src/shapes/Triangle.d.ts.map +1 -1
  89. package/dist-extensions/src/text/measure.d.ts +9 -0
  90. package/dist-extensions/src/text/measure.d.ts.map +1 -1
  91. package/dist-extensions/src/text/overlayEditor.d.ts.map +1 -1
  92. package/dist-extensions/src/text/scriptUtils.d.ts +142 -0
  93. package/dist-extensions/src/text/scriptUtils.d.ts.map +1 -0
  94. package/dist-extensions/src/util/misc/cornerRadius.d.ts +70 -0
  95. package/dist-extensions/src/util/misc/cornerRadius.d.ts.map +1 -0
  96. package/fabric-test-editor.html +3552 -0
  97. package/fabric-test2.html +647 -0
  98. package/fabric.ts +182 -182
  99. package/fonts/STV Bold.ttf +0 -0
  100. package/fonts/STV Light.ttf +0 -0
  101. package/fonts/STV Regular.ttf +0 -0
  102. package/package.json +164 -164
  103. package/src/shapes/Line.ts +484 -157
  104. package/src/shapes/Polyline.ts +70 -29
  105. package/src/shapes/Text/Text.ts +317 -19
  106. package/src/shapes/Textbox.ts +544 -12
  107. package/src/shapes/Triangle.spec.ts +76 -0
  108. package/src/shapes/Triangle.ts +85 -15
  109. package/src/text/measure.ts +200 -50
  110. package/src/text/overlayEditor.ts +164 -12
  111. package/src/util/misc/cornerRadius.spec.ts +141 -0
  112. package/src/util/misc/cornerRadius.ts +269 -0
  113. /package/debug/{konva → konva-master}/LICENSE +0 -0
  114. /package/debug/{konva → konva-master}/gulpfile.mjs +0 -0
  115. /package/debug/{konva → konva-master}/resources/doc-includes/ContainerParams.txt +0 -0
  116. /package/debug/{konva → konva-master}/resources/doc-includes/NodeParams.txt +0 -0
  117. /package/debug/{konva → konva-master}/resources/doc-includes/ShapeParams.txt +0 -0
  118. /package/debug/{konva → konva-master}/resources/jsdoc.conf.json +0 -0
  119. /package/debug/{konva → konva-master}/rollup.config.mjs +0 -0
  120. /package/debug/{konva → konva-master}/src/Animation.ts +0 -0
  121. /package/debug/{konva → konva-master}/src/BezierFunctions.ts +0 -0
  122. /package/debug/{konva → konva-master}/src/Container.ts +0 -0
  123. /package/debug/{konva → konva-master}/src/Context.ts +0 -0
  124. /package/debug/{konva → konva-master}/src/Core.ts +0 -0
  125. /package/debug/{konva → konva-master}/src/DragAndDrop.ts +0 -0
  126. /package/debug/{konva → konva-master}/src/Factory.ts +0 -0
  127. /package/debug/{konva → konva-master}/src/FastLayer.ts +0 -0
  128. /package/debug/{konva → konva-master}/src/Global.ts +0 -0
  129. /package/debug/{konva → konva-master}/src/Group.ts +0 -0
  130. /package/debug/{konva → konva-master}/src/Layer.ts +0 -0
  131. /package/debug/{konva → konva-master}/src/Node.ts +0 -0
  132. /package/debug/{konva → konva-master}/src/PointerEvents.ts +0 -0
  133. /package/debug/{konva → konva-master}/src/Shape.ts +0 -0
  134. /package/debug/{konva → konva-master}/src/Stage.ts +0 -0
  135. /package/debug/{konva → konva-master}/src/Tween.ts +0 -0
  136. /package/debug/{konva → konva-master}/src/Util.ts +0 -0
  137. /package/debug/{konva → konva-master}/src/Validators.ts +0 -0
  138. /package/debug/{konva → konva-master}/src/_CoreInternals.ts +0 -0
  139. /package/debug/{konva → konva-master}/src/_FullInternals.ts +0 -0
  140. /package/debug/{konva → konva-master}/src/canvas-backend.ts +0 -0
  141. /package/debug/{konva → konva-master}/src/filters/Blur.ts +0 -0
  142. /package/debug/{konva → konva-master}/src/filters/Brighten.ts +0 -0
  143. /package/debug/{konva → konva-master}/src/filters/Brightness.ts +0 -0
  144. /package/debug/{konva → konva-master}/src/filters/Contrast.ts +0 -0
  145. /package/debug/{konva → konva-master}/src/filters/Emboss.ts +0 -0
  146. /package/debug/{konva → konva-master}/src/filters/Enhance.ts +0 -0
  147. /package/debug/{konva → konva-master}/src/filters/Grayscale.ts +0 -0
  148. /package/debug/{konva → konva-master}/src/filters/HSL.ts +0 -0
  149. /package/debug/{konva → konva-master}/src/filters/HSV.ts +0 -0
  150. /package/debug/{konva → konva-master}/src/filters/Invert.ts +0 -0
  151. /package/debug/{konva → konva-master}/src/filters/Kaleidoscope.ts +0 -0
  152. /package/debug/{konva → konva-master}/src/filters/Mask.ts +0 -0
  153. /package/debug/{konva → konva-master}/src/filters/Noise.ts +0 -0
  154. /package/debug/{konva → konva-master}/src/filters/Pixelate.ts +0 -0
  155. /package/debug/{konva → konva-master}/src/filters/Posterize.ts +0 -0
  156. /package/debug/{konva → konva-master}/src/filters/RGB.ts +0 -0
  157. /package/debug/{konva → konva-master}/src/filters/RGBA.ts +0 -0
  158. /package/debug/{konva → konva-master}/src/filters/Sepia.ts +0 -0
  159. /package/debug/{konva → konva-master}/src/filters/Solarize.ts +0 -0
  160. /package/debug/{konva → konva-master}/src/filters/Threshold.ts +0 -0
  161. /package/debug/{konva → konva-master}/src/index.ts +0 -0
  162. /package/debug/{konva → konva-master}/src/shapes/Arc.ts +0 -0
  163. /package/debug/{konva → konva-master}/src/shapes/Arrow.ts +0 -0
  164. /package/debug/{konva → konva-master}/src/shapes/Circle.ts +0 -0
  165. /package/debug/{konva → konva-master}/src/shapes/Ellipse.ts +0 -0
  166. /package/debug/{konva → konva-master}/src/shapes/Image.ts +0 -0
  167. /package/debug/{konva → konva-master}/src/shapes/Label.ts +0 -0
  168. /package/debug/{konva → konva-master}/src/shapes/Line.ts +0 -0
  169. /package/debug/{konva → konva-master}/src/shapes/Path.ts +0 -0
  170. /package/debug/{konva → konva-master}/src/shapes/Rect.ts +0 -0
  171. /package/debug/{konva → konva-master}/src/shapes/RegularPolygon.ts +0 -0
  172. /package/debug/{konva → konva-master}/src/shapes/Ring.ts +0 -0
  173. /package/debug/{konva → konva-master}/src/shapes/Sprite.ts +0 -0
  174. /package/debug/{konva → konva-master}/src/shapes/Star.ts +0 -0
  175. /package/debug/{konva → konva-master}/src/shapes/TextPath.ts +0 -0
  176. /package/debug/{konva → konva-master}/src/shapes/Transformer.ts +0 -0
  177. /package/debug/{konva → konva-master}/src/shapes/Wedge.ts +0 -0
  178. /package/debug/{konva → konva-master}/src/skia-backend.ts +0 -0
  179. /package/debug/{konva → konva-master}/src/types.ts +0 -0
  180. /package/debug/{konva → konva-master}/tsconfig.json +0 -0
  181. /package/debug/{konva → konva-master}/tsconfig.test.json +0 -0
@@ -10,12 +10,13 @@ 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
- // @TODO this code is terrible and Line should be a special case of polyline.
13
+ import { Control } from '../controls/Control';
14
+ import type { TPointerEvent, Transform } from '../EventTypeDefs';
15
+ import { Gradient } from '../gradient/Gradient';
15
16
 
16
17
  const coordProps = ['x1', 'x2', 'y1', 'y2'] as const;
17
18
 
18
- interface UniqueLineProps {
19
+ interface UniqueLineCoords {
19
20
  x1: number;
20
21
  x2: number;
21
22
  y1: number;
@@ -24,225 +25,563 @@ interface UniqueLineProps {
24
25
 
25
26
  export interface SerializedLineProps
26
27
  extends SerializedObjectProps,
27
- UniqueLineProps {}
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
- */
28
+ UniqueLineCoords {}
29
+
36
30
  export class Line<
37
31
  Props extends TOptions<FabricObjectProps> = Partial<FabricObjectProps>,
38
32
  SProps extends SerializedLineProps = SerializedLineProps,
39
33
  EventSpec extends ObjectEvents = ObjectEvents,
40
34
  >
41
35
  extends FabricObject<Props, SProps, EventSpec>
42
- implements UniqueLineProps
36
+ implements UniqueLineCoords
43
37
  {
44
- /**
45
- * x value or first line edge
46
- * @type number
47
- */
48
38
  declare x1: number;
49
-
50
- /**
51
- * y value or first line edge
52
- * @type number
53
- */
54
39
  declare y1: number;
55
-
56
- /**
57
- * x value or second line edge
58
- * @type number
59
- */
60
40
  declare x2: number;
61
-
62
- /**
63
- * y value or second line edge
64
- * @type number
65
- */
66
41
  declare y2: number;
67
42
 
68
- static type = 'Line';
43
+ hitStrokeWidth: number | 'auto' = 'auto';
69
44
 
45
+ private _updatingEndpoints = false;
46
+ private _useEndpointCoords = true;
47
+ private _exportingSVG = false;
48
+
49
+ static type = 'Line';
70
50
  static cacheProperties = [...cacheProperties, ...coordProps];
71
- /**
72
- * Constructor
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> = {}) {
51
+
52
+ constructor(
53
+ [x1, y1, x2, y2] = [0, 0, 100, 0],
54
+ options: Partial<Props & { hitStrokeWidth?: number | 'auto' }> = {},
55
+ ) {
78
56
  super();
79
- Object.assign(this, Line.ownDefaults);
80
57
  this.setOptions(options);
81
58
  this.x1 = x1;
82
59
  this.x2 = x2;
83
60
  this.y1 = y1;
84
61
  this.y2 = y2;
62
+
63
+ if (options.hitStrokeWidth !== undefined) {
64
+ this.hitStrokeWidth = options.hitStrokeWidth;
65
+ }
66
+
67
+ this.hasBorders = false;
68
+ this.hasControls = true;
69
+ this.selectable = true;
70
+ this.hoverCursor = 'move';
71
+ this.perPixelTargetFind = false;
72
+ this.strokeLineCap = 'butt';
73
+
85
74
  this._setWidthHeight();
86
75
  const { left, top } = options;
87
76
  typeof left === 'number' && this.set(LEFT, left);
88
77
  typeof top === 'number' && this.set(TOP, top);
78
+ this._setupLineControls();
79
+ }
80
+
81
+ _setupLineControls() {
82
+ this.controls = {
83
+ p1: new Control({
84
+ x: 0,
85
+ y: 0,
86
+ cursorStyle: 'move',
87
+ actionHandler: this._endpointActionHandler.bind(this),
88
+ positionHandler: this._p1PositionHandler.bind(this),
89
+ render: this._renderEndpointControl.bind(this),
90
+ sizeX: 12,
91
+ sizeY: 12,
92
+ }),
93
+ p2: new Control({
94
+ x: 0,
95
+ y: 0,
96
+ cursorStyle: 'move',
97
+ actionHandler: this._endpointActionHandler.bind(this),
98
+ positionHandler: this._p2PositionHandler.bind(this),
99
+ render: this._renderEndpointControl.bind(this),
100
+ sizeX: 12,
101
+ sizeY: 12,
102
+ }),
103
+ };
104
+ }
105
+
106
+ _p1PositionHandler() {
107
+ return new Point(this.x1, this.y1).transform(this.getViewportTransform());
108
+ }
109
+
110
+ _p2PositionHandler() {
111
+ return new Point(this.x2, this.y2).transform(this.getViewportTransform());
112
+ }
113
+
114
+ _renderEndpointControl(
115
+ ctx: CanvasRenderingContext2D,
116
+ left: number,
117
+ top: number,
118
+ ) {
119
+ const size = 12;
120
+ ctx.save();
121
+ ctx.fillStyle = '#007bff';
122
+ ctx.strokeStyle = '#ffffff';
123
+ ctx.lineWidth = 2;
124
+ ctx.beginPath();
125
+ ctx.arc(left, top, size / 2, 0, 2 * Math.PI);
126
+ ctx.fill();
127
+ ctx.stroke();
128
+ ctx.restore();
129
+ }
130
+
131
+ drawBorders(ctx: CanvasRenderingContext2D, styleOverride: any = {}) {
132
+ if (this._useEndpointCoords) {
133
+ this._drawLineBorders(ctx, styleOverride);
134
+ return this;
135
+ }
136
+ return super.drawBorders(ctx, styleOverride, {});
137
+ }
138
+
139
+ _drawLineBorders(ctx: CanvasRenderingContext2D, styleOverride: any = {}) {
140
+ const vpt = this.canvas?.viewportTransform || [1, 0, 0, 1, 0, 0];
141
+ ctx.save();
142
+ ctx.setTransform(vpt[0], vpt[1], vpt[2], vpt[3], vpt[4], vpt[5]);
143
+ ctx.strokeStyle =
144
+ styleOverride.borderColor ||
145
+ this.borderColor ||
146
+ 'rgba(100, 200, 200, 0.5)';
147
+ ctx.lineWidth = (this.strokeWidth || 1) + 5;
148
+ ctx.lineCap = this.strokeLineCap || 'butt';
149
+ ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
150
+ ctx.beginPath();
151
+ ctx.moveTo(this.x1, this.y1);
152
+ ctx.lineTo(this.x2, this.y2);
153
+ ctx.stroke();
154
+ ctx.restore();
155
+ }
156
+
157
+ _renderControls(ctx: CanvasRenderingContext2D, styleOverride: any = {}) {
158
+ ctx.save();
159
+ ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
160
+ this.drawControls(ctx, styleOverride);
161
+ ctx.restore();
162
+ }
163
+
164
+ getBoundingRect() {
165
+ if (this._useEndpointCoords) {
166
+ const { x1, y1, x2, y2 } = this;
167
+ const effectiveStrokeWidth =
168
+ this.hitStrokeWidth === 'auto' ? this.strokeWidth : this.hitStrokeWidth;
169
+ const padding = Math.max(effectiveStrokeWidth / 2 + 5, 10);
170
+ return {
171
+ left: Math.min(x1, x2) - padding,
172
+ top: Math.min(y1, y2) - padding,
173
+ width: Math.abs(x2 - x1) + padding * 2 || padding * 2,
174
+ height: Math.abs(y2 - y1) + padding * 2 || padding * 2,
175
+ };
176
+ }
177
+ return super.getBoundingRect();
178
+ }
179
+
180
+ setCoords() {
181
+ if (this._useEndpointCoords) {
182
+ // Set width and height for hit detection and bounding box
183
+ const effectiveStrokeWidth =
184
+ this.hitStrokeWidth === 'auto' ? this.strokeWidth : this.hitStrokeWidth;
185
+ const hitPadding = Math.max(effectiveStrokeWidth / 2 + 5, 10);
186
+ this.width = Math.abs(this.x2 - this.x1) + hitPadding * 2;
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
+ }
195
+ }
196
+ super.setCoords();
197
+ }
198
+
199
+ getCoords(): [Point, Point, Point, Point] {
200
+ if (this._useEndpointCoords) {
201
+ const deltaX = this.x2 - this.x1;
202
+ const deltaY = this.y2 - this.y1;
203
+ const length = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
204
+
205
+ if (length === 0) {
206
+ return super.getCoords() as [Point, Point, Point, Point];
207
+ }
208
+
209
+ const effectiveStrokeWidth =
210
+ this.hitStrokeWidth === 'auto' ? this.strokeWidth : this.hitStrokeWidth;
211
+ const halfWidth = Math.max(effectiveStrokeWidth / 2 + 2, 5);
212
+
213
+ // Unit vector perpendicular to line
214
+ const perpX = -deltaY / length;
215
+ const perpY = deltaX / length;
216
+
217
+ // Four corners of oriented rectangle
218
+ return [
219
+ new Point(this.x1 + perpX * halfWidth, this.y1 + perpY * halfWidth),
220
+ new Point(this.x2 + perpX * halfWidth, this.y2 + perpY * halfWidth),
221
+ new Point(this.x2 - perpX * halfWidth, this.y2 - perpY * halfWidth),
222
+ new Point(this.x1 - perpX * halfWidth, this.y1 - perpY * halfWidth),
223
+ ];
224
+ }
225
+ return super.getCoords() as [Point, Point, Point, Point];
226
+ }
227
+
228
+ containsPoint(point: Point): boolean {
229
+ if (this._useEndpointCoords) {
230
+ if (this.canvas?.getActiveObject() === this) {
231
+ return super.containsPoint(point);
232
+ }
233
+ const distance = this._distanceToLineSegment(point.x, point.y);
234
+ const effectiveStrokeWidth =
235
+ this.hitStrokeWidth === 'auto'
236
+ ? this.strokeWidth
237
+ : this.hitStrokeWidth || 1;
238
+
239
+ const tolerance = Math.max(effectiveStrokeWidth / 2 + 2, 5);
240
+ return distance <= tolerance;
241
+ }
242
+ return super.containsPoint(point);
89
243
  }
90
244
 
91
- /**
92
- * @private
93
- * @param {Object} [options] Options
94
- */
95
- _setWidthHeight() {
96
- const { x1, y1, x2, y2 } = this;
97
- this.width = Math.abs(x2 - x1);
98
- this.height = Math.abs(y2 - y1);
99
- const { left, top, width, height } = makeBoundingBoxFromPoints([
100
- { x: x1, y: y1 },
101
- { x: x2, y: y2 },
102
- ]);
103
- const position = new Point(left + width / 2, top + height / 2);
104
- this.setPositionByOrigin(position, CENTER, CENTER);
245
+ _distanceToLineSegment(px: number, py: number): number {
246
+ const x1 = this.x1,
247
+ y1 = this.y1,
248
+ x2 = this.x2,
249
+ y2 = this.y2;
250
+
251
+ const pd2 = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2);
252
+ if (pd2 === 0) {
253
+ return Math.sqrt((px - x1) * (px - x1) + (py - y1) * (py - y1));
254
+ }
255
+
256
+ const u = ((px - x1) * (x2 - x1) + (py - y1) * (y2 - y1)) / pd2;
257
+
258
+ let closestX: number, closestY: number;
259
+ if (u < 0) {
260
+ closestX = x1;
261
+ closestY = y1;
262
+ } else if (u > 1) {
263
+ closestX = x2;
264
+ closestY = y2;
265
+ } else {
266
+ closestX = x1 + u * (x2 - x1);
267
+ closestY = y1 + u * (y2 - y1);
268
+ }
269
+
270
+ return Math.sqrt(
271
+ (px - closestX) * (px - closestX) + (py - closestY) * (py - closestY),
272
+ );
273
+ }
274
+
275
+ _endpointActionHandler(
276
+ eventData: TPointerEvent,
277
+ transformData: Transform,
278
+ x: number,
279
+ y: number,
280
+ ) {
281
+ const controlKey = transformData.corner;
282
+ const pointer = new Point(x, y);
283
+ let newX = pointer.x;
284
+ let newY = pointer.y;
285
+
286
+ if (eventData.shiftKey) {
287
+ const otherControl = controlKey === 'p1' ? 'p2' : 'p1';
288
+ const otherX = this[otherControl === 'p1' ? 'x1' : 'x2'];
289
+ const otherY = this[otherControl === 'p1' ? 'y1' : 'y2'];
290
+ const snapped = this._snapToAngle(otherX, otherY, newX, newY);
291
+ newX = snapped.x;
292
+ newY = snapped.y;
293
+ }
294
+
295
+ if (this._useEndpointCoords) {
296
+ if (controlKey === 'p1') {
297
+ this.x1 = newX;
298
+ this.y1 = newY;
299
+ } else if (controlKey === 'p2') {
300
+ this.x2 = newX;
301
+ this.y2 = newY;
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
+
312
+ this.dirty = true;
313
+ this.setCoords();
314
+ this.canvas?.requestRenderAll();
315
+ return true;
316
+ }
317
+
318
+ // Fallback for old system
319
+ this._updatingEndpoints = true;
320
+ if (controlKey === 'p1') {
321
+ this.x1 = newX;
322
+ this.y1 = newY;
323
+ } else if (controlKey === 'p2') {
324
+ this.x2 = newX;
325
+ this.y2 = newY;
326
+ }
327
+ this._setWidthHeight();
328
+ this.dirty = true;
329
+ this._updatingEndpoints = false;
330
+ this.canvas?.requestRenderAll();
331
+ this.fire('modified', {
332
+ transform: transformData,
333
+ target: this,
334
+ e: eventData,
335
+ });
336
+ return true;
337
+ }
338
+
339
+ _snapToAngle(
340
+ fromX: number,
341
+ fromY: number,
342
+ toX: number,
343
+ toY: number,
344
+ ): { x: number; y: number } {
345
+ const deltaX = toX - fromX;
346
+ const deltaY = toY - fromY;
347
+ const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
348
+ if (distance === 0) return { x: toX, y: toY };
349
+ let angle = Math.atan2(deltaY, deltaX) * (180 / Math.PI);
350
+ const snapIncrement = 15;
351
+ const snappedAngle = Math.round(angle / snapIncrement) * snapIncrement;
352
+ const snappedRadians = snappedAngle * (Math.PI / 180);
353
+ return {
354
+ x: fromX + Math.cos(snappedRadians) * distance,
355
+ y: fromY + Math.sin(snappedRadians) * distance,
356
+ };
357
+ }
358
+
359
+ _setWidthHeight(skipReposition = false) {
360
+ this.width = Math.abs(this.x2 - this.x1) || 1;
361
+ this.height = Math.abs(this.y2 - this.y1) || 1;
362
+ if (!skipReposition && !this._updatingEndpoints) {
363
+ const { left, top, width, height } = makeBoundingBoxFromPoints([
364
+ { x: this.x1, y: this.y1 },
365
+ { x: this.x2, y: this.y2 },
366
+ ]);
367
+ this.setPositionByOrigin(
368
+ new Point(left + width / 2, top + height / 2),
369
+ CENTER,
370
+ CENTER,
371
+ );
372
+ }
105
373
  }
106
374
 
107
- /**
108
- * @private
109
- * @param {String} key
110
- * @param {*} value
111
- */
112
375
  _set(key: string, value: any) {
376
+ const oldLeft = this.left;
377
+ const oldTop = this.top;
113
378
  super._set(key, value);
114
- if (coordProps.includes(key as keyof UniqueLineProps)) {
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
379
+ if (coordProps.includes(key as keyof UniqueLineCoords)) {
121
380
  this._setWidthHeight();
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
+ }
390
+ }
391
+ if (
392
+ (key === 'left' || key === 'top') &&
393
+ this.canvas &&
394
+ !this._updatingEndpoints
395
+ ) {
396
+ const deltaX = this.left - oldLeft;
397
+ const deltaY = this.top - oldTop;
398
+ if (deltaX !== 0 || deltaY !== 0) {
399
+ this._updatingEndpoints = true;
400
+ this.x1 += deltaX;
401
+ this.y1 += deltaY;
402
+ this.x2 += deltaX;
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
+
413
+ this._updatingEndpoints = false;
414
+ }
122
415
  }
123
416
  return this;
124
417
  }
125
418
 
126
- /**
127
- * @private
128
- * @param {CanvasRenderingContext2D} ctx Context to render on
129
- */
130
- _render(ctx: CanvasRenderingContext2D) {
419
+ render(ctx: CanvasRenderingContext2D) {
420
+ if (this._useEndpointCoords) {
421
+ this._renderDirectly(ctx);
422
+ return;
423
+ }
424
+ super.render(ctx);
425
+ }
426
+
427
+ _renderDirectly(ctx: CanvasRenderingContext2D) {
428
+ if (!this.visible) return;
429
+ ctx.save();
430
+ ctx.globalAlpha = this.opacity;
431
+ ctx.lineWidth = this.strokeWidth;
432
+ ctx.lineCap = this.strokeLineCap || 'butt';
131
433
  ctx.beginPath();
434
+ ctx.moveTo(this.x1, this.y1);
435
+ ctx.lineTo(this.x2, this.y2);
132
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
+
444
+ ctx.stroke();
445
+ ctx.strokeStyle = origStrokeStyle;
446
+ ctx.restore();
447
+ }
448
+
449
+ _render(ctx: CanvasRenderingContext2D) {
450
+ if (this._useEndpointCoords) return;
451
+ ctx.beginPath();
133
452
  const p = this.calcLinePoints();
134
453
  ctx.moveTo(p.x1, p.y1);
135
454
  ctx.lineTo(p.x2, p.y2);
136
-
137
455
  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
456
  const origStrokeStyle = ctx.strokeStyle;
143
457
  if (isFiller(this.stroke)) {
144
458
  ctx.strokeStyle = this.stroke.toLive(ctx)!;
145
- } else {
146
- ctx.strokeStyle = this.stroke ?? ctx.fillStyle;
147
459
  }
148
460
  this.stroke && this._renderStroke(ctx);
149
461
  ctx.strokeStyle = origStrokeStyle;
150
462
  }
151
463
 
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
464
  _findCenterFromElement(): Point {
159
465
  return new Point((this.x1 + this.x2) / 2, (this.y1 + this.y2) / 2);
160
466
  }
161
467
 
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
468
  toObject<
168
469
  T extends Omit<Props & TClassProperties<this>, keyof SProps>,
169
470
  K extends keyof T = never,
170
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
+ }
171
481
  return {
172
482
  ...super.toObject(propertiesToInclude),
173
483
  ...this.calcLinePoints(),
174
484
  };
175
485
  }
176
486
 
177
- /*
178
- * Calculate object dimensions from its properties
179
- * @private
180
- */
181
487
  _getNonTransformedDimensions(): Point {
182
488
  const dim = super._getNonTransformedDimensions();
183
- if (this.strokeLineCap === 'butt') {
184
- if (this.width === 0) {
185
- dim.y -= this.strokeWidth;
186
- }
187
- if (this.height === 0) {
188
- dim.x -= this.strokeWidth;
189
- }
489
+ if (this.strokeLineCap === 'round') {
490
+ dim.x += this.strokeWidth;
491
+ dim.y += this.strokeWidth;
190
492
  }
191
493
  return dim;
192
494
  }
193
495
 
194
- /**
195
- * Recalculates line points given width and height
196
- * Those points are simply placed around the center,
197
- * This is not useful outside internal render functions and svg output
198
- * Is not meant to be for the developer.
199
- * @private
200
- */
201
- calcLinePoints(): UniqueLineProps {
496
+ calcLinePoints(): UniqueLineCoords {
497
+ if (this._updatingEndpoints) {
498
+ const centerX = (this.x1 + this.x2) / 2;
499
+ const centerY = (this.y1 + this.y2) / 2;
500
+ return {
501
+ x1: this.x1 - centerX,
502
+ y1: this.y1 - centerY,
503
+ x2: this.x2 - centerX,
504
+ y2: this.y2 - centerY,
505
+ };
506
+ }
202
507
  const { x1: _x1, x2: _x2, y1: _y1, y2: _y2, width, height } = this;
203
- const xMult = _x1 <= _x2 ? -1 : 1,
204
- yMult = _y1 <= _y2 ? -1 : 1,
205
- x1 = (xMult * width) / 2,
206
- y1 = (yMult * height) / 2,
207
- x2 = (xMult * -width) / 2,
208
- y2 = (yMult * -height) / 2;
209
-
508
+ const xMult = _x1 <= _x2 ? -1 : 1;
509
+ const yMult = _y1 <= _y2 ? -1 : 1;
210
510
  return {
211
- x1,
212
- x2,
213
- y1,
214
- y2,
511
+ x1: (xMult * width) / 2,
512
+ y1: (yMult * height) / 2,
513
+ x2: (xMult * -width) / 2,
514
+ y2: (yMult * -height) / 2,
215
515
  };
216
516
  }
217
517
 
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
518
  _toSVG() {
226
- const { x1, x2, y1, y2 } = this.calcLinePoints();
227
- return [
228
- '<line ',
229
- 'COMMON_PARTS',
230
- `x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" />\n`,
231
- ];
519
+ if (this._useEndpointCoords) {
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
+
530
+ return [
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}" `,
536
+ `x1="${this.x1}" y1="${this.y1}" x2="${this.x2}" y2="${this.y2}" />\n`,
537
+ ];
538
+ } else {
539
+ // Use standard calcLinePoints for legacy mode
540
+ const { x1, x2, y1, y2 } = this.calcLinePoints();
541
+ return [
542
+ '<line ',
543
+ 'COMMON_PARTS',
544
+ `x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" />\n`,
545
+ ];
546
+ }
547
+ }
548
+
549
+ toSVG(reviver?: (markup: string) => string): string {
550
+ if (this._useEndpointCoords) {
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;
578
+ }
579
+ // Use default behavior for legacy mode
580
+ return super.toSVG(reviver);
232
581
  }
233
582
 
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
583
  static ATTRIBUTE_NAMES = SHARED_ATTRIBUTES.concat(coordProps);
239
584
 
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
585
  static async fromElement(
247
586
  element: HTMLElement,
248
587
  options?: Abortable,
@@ -258,13 +597,6 @@ export class Line<
258
597
  return new this([x1, y1, x2, y2], parsedAttributes);
259
598
  }
260
599
 
261
- /* _FROM_SVG_END_ */
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
600
  static fromObject<T extends TOptions<SerializedLineProps>>({
269
601
  x1,
270
602
  y1,
@@ -273,13 +605,8 @@ export class Line<
273
605
  ...object
274
606
  }: T) {
275
607
  return this._fromObject<Line>(
276
- {
277
- ...object,
278
- points: [x1, y1, x2, y2],
279
- },
280
- {
281
- extraParam: 'points',
282
- },
608
+ { ...object, points: [x1, y1, x2, y2] },
609
+ { extraParam: 'points' },
283
610
  );
284
611
  }
285
612
  }