fabric 6.0.2 → 6.2.0

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 (207) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/index.js +250 -101
  3. package/dist/index.js.map +1 -1
  4. package/dist/index.min.js +1 -1
  5. package/dist/index.min.js.map +1 -1
  6. package/dist/index.min.mjs +1 -1
  7. package/dist/index.min.mjs.map +1 -1
  8. package/dist/index.mjs +250 -101
  9. package/dist/index.mjs.map +1 -1
  10. package/dist/index.node.cjs +250 -101
  11. package/dist/index.node.cjs.map +1 -1
  12. package/dist/index.node.mjs +250 -101
  13. package/dist/index.node.mjs.map +1 -1
  14. package/dist/package.json.min.mjs +1 -1
  15. package/dist/package.json.mjs +1 -1
  16. package/dist/src/ClassRegistry.d.ts +1 -0
  17. package/dist/src/ClassRegistry.d.ts.map +1 -1
  18. package/dist/src/ClassRegistry.min.mjs +1 -1
  19. package/dist/src/ClassRegistry.min.mjs.map +1 -1
  20. package/dist/src/ClassRegistry.mjs +3 -0
  21. package/dist/src/ClassRegistry.mjs.map +1 -1
  22. package/dist/src/Collection.d.ts +3 -3
  23. package/dist/src/Collection.d.ts.map +1 -1
  24. package/dist/src/EventTypeDefs.d.ts +19 -11
  25. package/dist/src/EventTypeDefs.d.ts.map +1 -1
  26. package/dist/src/LayoutManager/LayoutManager.d.ts.map +1 -1
  27. package/dist/src/LayoutManager/LayoutManager.min.mjs +1 -1
  28. package/dist/src/LayoutManager/LayoutManager.min.mjs.map +1 -1
  29. package/dist/src/LayoutManager/LayoutManager.mjs +2 -2
  30. package/dist/src/LayoutManager/LayoutManager.mjs.map +1 -1
  31. package/dist/src/Shadow.d.ts.map +1 -1
  32. package/dist/src/Shadow.min.mjs +1 -1
  33. package/dist/src/Shadow.min.mjs.map +1 -1
  34. package/dist/src/Shadow.mjs +1 -5
  35. package/dist/src/Shadow.mjs.map +1 -1
  36. package/dist/src/canvas/DOMManagers/util.d.ts.map +1 -1
  37. package/dist/src/canvas/DOMManagers/util.min.mjs +1 -1
  38. package/dist/src/canvas/DOMManagers/util.min.mjs.map +1 -1
  39. package/dist/src/canvas/DOMManagers/util.mjs +9 -15
  40. package/dist/src/canvas/DOMManagers/util.mjs.map +1 -1
  41. package/dist/src/canvas/SelectableCanvas.d.ts.map +1 -1
  42. package/dist/src/canvas/SelectableCanvas.min.mjs +1 -1
  43. package/dist/src/canvas/SelectableCanvas.min.mjs.map +1 -1
  44. package/dist/src/canvas/SelectableCanvas.mjs +3 -0
  45. package/dist/src/canvas/SelectableCanvas.mjs.map +1 -1
  46. package/dist/src/canvas/StaticCanvas.d.ts +20 -24
  47. package/dist/src/canvas/StaticCanvas.d.ts.map +1 -1
  48. package/dist/src/color/util.d.ts.map +1 -1
  49. package/dist/src/constants.d.ts +1 -0
  50. package/dist/src/constants.d.ts.map +1 -1
  51. package/dist/src/constants.min.mjs +1 -1
  52. package/dist/src/constants.min.mjs.map +1 -1
  53. package/dist/src/constants.mjs +2 -1
  54. package/dist/src/constants.mjs.map +1 -1
  55. package/dist/src/controls/controlRendering.d.ts +1 -1
  56. package/dist/src/controls/controlRendering.d.ts.map +1 -1
  57. package/dist/src/controls/controlRendering.min.mjs.map +1 -1
  58. package/dist/src/controls/controlRendering.mjs.map +1 -1
  59. package/dist/src/controls/fireEvent.d.ts +2 -2
  60. package/dist/src/controls/fireEvent.d.ts.map +1 -1
  61. package/dist/src/controls/fireEvent.min.mjs.map +1 -1
  62. package/dist/src/controls/fireEvent.mjs.map +1 -1
  63. package/dist/src/controls/index.d.ts +1 -0
  64. package/dist/src/controls/index.d.ts.map +1 -1
  65. package/dist/src/controls/index.min.mjs +1 -1
  66. package/dist/src/controls/index.mjs +1 -0
  67. package/dist/src/controls/index.mjs.map +1 -1
  68. package/dist/src/controls/pathControl.d.ts +12 -0
  69. package/dist/src/controls/pathControl.d.ts.map +1 -0
  70. package/dist/src/controls/pathControl.min.mjs +2 -0
  71. package/dist/src/controls/pathControl.min.mjs.map +1 -0
  72. package/dist/src/controls/pathControl.mjs +156 -0
  73. package/dist/src/controls/pathControl.mjs.map +1 -0
  74. package/dist/src/controls/polyControl.d.ts.map +1 -1
  75. package/dist/src/controls/polyControl.min.mjs +1 -1
  76. package/dist/src/controls/polyControl.min.mjs.map +1 -1
  77. package/dist/src/controls/polyControl.mjs +1 -9
  78. package/dist/src/controls/polyControl.mjs.map +1 -1
  79. package/dist/src/controls/util.d.ts +1 -1
  80. package/dist/src/controls/wrapWithFireEvent.d.ts +5 -3
  81. package/dist/src/controls/wrapWithFireEvent.d.ts.map +1 -1
  82. package/dist/src/controls/wrapWithFireEvent.min.mjs +1 -1
  83. package/dist/src/controls/wrapWithFireEvent.min.mjs.map +1 -1
  84. package/dist/src/controls/wrapWithFireEvent.mjs +7 -4
  85. package/dist/src/controls/wrapWithFireEvent.mjs.map +1 -1
  86. package/dist/src/controls/wrapWithFixedAnchor.d.ts.map +1 -1
  87. package/dist/src/env/index.d.ts.map +1 -1
  88. package/dist/src/env/node.d.ts.map +1 -1
  89. package/dist/src/filters/BaseFilter.d.ts.map +1 -1
  90. package/dist/src/filters/BaseFilter.min.mjs.map +1 -1
  91. package/dist/src/filters/BaseFilter.mjs +0 -1
  92. package/dist/src/filters/BaseFilter.mjs.map +1 -1
  93. package/dist/src/filters/ColorMatrix.d.ts.map +1 -1
  94. package/dist/src/filters/ColorMatrixFilters.d.ts +8 -96
  95. package/dist/src/filters/ColorMatrixFilters.d.ts.map +1 -1
  96. package/dist/src/filters/Convolute.d.ts +2 -1
  97. package/dist/src/filters/Convolute.d.ts.map +1 -1
  98. package/dist/src/filters/shaders/baseFilter.d.ts +1 -1
  99. package/dist/src/filters/shaders/baseFilter.d.ts.map +1 -1
  100. package/dist/src/filters/utils.d.ts.map +1 -1
  101. package/dist/src/parser/normalizeAttr.d.ts.map +1 -1
  102. package/dist/src/parser/parseStyleString.d.ts.map +1 -1
  103. package/dist/src/parser/parseStyleString.min.mjs +1 -1
  104. package/dist/src/parser/parseStyleString.min.mjs.map +1 -1
  105. package/dist/src/parser/parseStyleString.mjs +1 -0
  106. package/dist/src/parser/parseStyleString.mjs.map +1 -1
  107. package/dist/src/parser/parseUseDirectives.d.ts.map +1 -1
  108. package/dist/src/parser/parseUseDirectives.min.mjs +1 -1
  109. package/dist/src/parser/parseUseDirectives.min.mjs.map +1 -1
  110. package/dist/src/parser/parseUseDirectives.mjs +61 -43
  111. package/dist/src/parser/parseUseDirectives.mjs.map +1 -1
  112. package/dist/src/shapes/Group.d.ts +20 -20
  113. package/dist/src/shapes/Group.d.ts.map +1 -1
  114. package/dist/src/shapes/IText/DraggableTextDelegate.d.ts.map +1 -1
  115. package/dist/src/shapes/IText/ITextBehavior.d.ts.map +1 -1
  116. package/dist/src/shapes/IText/ITextBehavior.min.mjs.map +1 -1
  117. package/dist/src/shapes/IText/ITextBehavior.mjs +0 -1
  118. package/dist/src/shapes/IText/ITextBehavior.mjs.map +1 -1
  119. package/dist/src/shapes/Image.d.ts.map +1 -1
  120. package/dist/src/shapes/Image.min.mjs.map +1 -1
  121. package/dist/src/shapes/Image.mjs +0 -2
  122. package/dist/src/shapes/Image.mjs.map +1 -1
  123. package/dist/src/shapes/Path.d.ts +9 -9
  124. package/dist/src/shapes/Path.d.ts.map +1 -1
  125. package/dist/src/shapes/Polyline.d.ts +1 -1
  126. package/dist/src/shapes/Rect.d.ts +1 -1
  127. package/dist/src/shapes/Text/Text.d.ts +1 -1
  128. package/dist/src/shapes/Text/Text.d.ts.map +1 -1
  129. package/dist/src/shapes/Text/TextSVGExportMixin.d.ts.map +1 -1
  130. package/dist/src/shapes/Text/TextSVGExportMixin.min.mjs.map +1 -1
  131. package/dist/src/shapes/Text/TextSVGExportMixin.mjs +0 -2
  132. package/dist/src/shapes/Text/TextSVGExportMixin.mjs.map +1 -1
  133. package/dist/src/shapes/Textbox.d.ts.map +1 -1
  134. package/dist/src/shapes/Textbox.min.mjs.map +1 -1
  135. package/dist/src/shapes/Textbox.mjs +0 -2
  136. package/dist/src/shapes/Textbox.mjs.map +1 -1
  137. package/dist/src/util/applyMixins.d.ts.map +1 -1
  138. package/dist/src/util/dom_misc.d.ts.map +1 -1
  139. package/dist/src/util/dom_misc.min.mjs +1 -1
  140. package/dist/src/util/dom_misc.min.mjs.map +1 -1
  141. package/dist/src/util/dom_misc.mjs +7 -9
  142. package/dist/src/util/dom_misc.mjs.map +1 -1
  143. package/dist/src/util/internals/cloneDeep.d.ts.map +1 -1
  144. package/dist/src/util/internals/console.d.ts +1 -1
  145. package/dist/src/util/internals/findRight.d.ts.map +1 -1
  146. package/dist/src/util/internals/removeFromArray.d.ts.map +1 -1
  147. package/dist/src/util/misc/dom.d.ts.map +1 -1
  148. package/dist/src/util/misc/groupSVGElements.d.ts.map +1 -1
  149. package/dist/src/util/misc/matrix.d.ts.map +1 -1
  150. package/dist/src/util/misc/objectEnlive.d.ts +1 -1
  151. package/dist/src/util/misc/objectEnlive.d.ts.map +1 -1
  152. package/dist/src/util/misc/objectEnlive.min.mjs +1 -1
  153. package/dist/src/util/misc/objectEnlive.min.mjs.map +1 -1
  154. package/dist/src/util/misc/objectEnlive.mjs +7 -11
  155. package/dist/src/util/misc/objectEnlive.mjs.map +1 -1
  156. package/dist/src/util/misc/objectTransforms.d.ts.map +1 -1
  157. package/dist/src/util/misc/pick.d.ts.map +1 -1
  158. package/dist/src/util/misc/planeChange.d.ts.map +1 -1
  159. package/dist/src/util/misc/svgParsing.d.ts.map +1 -1
  160. package/dist/src/util/misc/textStyles.d.ts.map +1 -1
  161. package/dist/src/util/path/index.d.ts +0 -1
  162. package/dist/src/util/path/index.d.ts.map +1 -1
  163. package/dist/src/util/path/index.min.mjs +1 -1
  164. package/dist/src/util/path/index.min.mjs.map +1 -1
  165. package/dist/src/util/path/index.mjs +1 -2
  166. package/dist/src/util/path/index.mjs.map +1 -1
  167. package/dist/src/util/path/typedefs.d.ts +1 -0
  168. package/dist/src/util/path/typedefs.d.ts.map +1 -1
  169. package/dist/src/util/typeAssertions.d.ts +2 -2
  170. package/dist/src/util/typeAssertions.d.ts.map +1 -1
  171. package/lib/aligning_guidelines.js +76 -1
  172. package/lib/centering_guidelines.js +3 -1
  173. package/package.json +3 -3
  174. package/src/ClassRegistry.spec.ts +39 -0
  175. package/src/ClassRegistry.ts +4 -0
  176. package/src/EventTypeDefs.ts +22 -10
  177. package/src/LayoutManager/ActiveSelectionLayoutManager.spec.ts +1 -0
  178. package/src/LayoutManager/LayoutManager.spec.ts +1 -0
  179. package/src/LayoutManager/LayoutManager.ts +2 -0
  180. package/src/Shadow.ts +1 -6
  181. package/src/canvas/DOMManagers/util.ts +11 -15
  182. package/src/canvas/SelectableCanvas.spec.ts +18 -0
  183. package/src/canvas/SelectableCanvas.ts +3 -0
  184. package/src/constants.ts +1 -0
  185. package/src/controls/controlRendering.ts +4 -2
  186. package/src/controls/fireEvent.ts +2 -2
  187. package/src/controls/index.ts +1 -0
  188. package/src/controls/pathControl.spec.ts +75 -0
  189. package/src/controls/pathControl.ts +293 -0
  190. package/src/controls/polyControl.ts +1 -1
  191. package/src/controls/wrapWithFireEvent.ts +14 -5
  192. package/src/filters/BaseFilter.ts +1 -2
  193. package/src/parser/parseStyleString.ts +1 -0
  194. package/src/parser/parseUseDirectives.test.ts +113 -0
  195. package/src/parser/parseUseDirectives.ts +64 -58
  196. package/src/shapes/IText/ITextBehavior.ts +4 -2
  197. package/src/shapes/Image.ts +0 -2
  198. package/src/shapes/Text/TextSVGExportMixin.ts +0 -2
  199. package/src/shapes/Textbox.ts +0 -2
  200. package/src/util/dom_misc.ts +17 -10
  201. package/src/util/misc/objectEnlive.spec.ts +68 -0
  202. package/src/util/misc/objectEnlive.ts +7 -13
  203. package/src/util/path/__snapshots__/index.spec.ts.snap +327 -0
  204. package/src/util/path/index.spec.ts +13 -2
  205. package/src/util/path/index.ts +2 -4
  206. package/src/util/path/typedefs.ts +2 -0
  207. package/.gitmodules +0 -3
@@ -0,0 +1,293 @@
1
+ import { Point } from '../Point';
2
+ import { Control } from './Control';
3
+ import type { TMat2D } from '../typedefs';
4
+ import type { Path } from '../shapes/Path';
5
+ import { multiplyTransformMatrices } from '../util/misc/matrix';
6
+ import type {
7
+ TModificationEvents,
8
+ TPointerEvent,
9
+ Transform,
10
+ } from '../EventTypeDefs';
11
+ import { sendPointToPlane } from '../util/misc/planeChange';
12
+ import type { TSimpleParseCommandType } from '../util/path/typedefs';
13
+ import type { ControlRenderingStyleOverride } from './controlRendering';
14
+ import { fireEvent } from './fireEvent';
15
+ import { commonEventInfo } from './util';
16
+
17
+ const ACTION_NAME: TModificationEvents = 'modifyPath' as const;
18
+
19
+ type TTransformAnchor = Transform;
20
+
21
+ export type PathPointControlStyle = {
22
+ controlFill?: string;
23
+ controlStroke?: string;
24
+ connectionDashArray?: number[];
25
+ };
26
+
27
+ const calcPathPointPosition = (
28
+ pathObject: Path,
29
+ commandIndex: number,
30
+ pointIndex: number
31
+ ) => {
32
+ const { path, pathOffset } = pathObject;
33
+ const command = path[commandIndex];
34
+ return new Point(
35
+ (command[pointIndex] as number) - pathOffset.x,
36
+ (command[pointIndex + 1] as number) - pathOffset.y
37
+ ).transform(
38
+ multiplyTransformMatrices(
39
+ pathObject.getViewportTransform(),
40
+ pathObject.calcTransformMatrix()
41
+ )
42
+ );
43
+ };
44
+
45
+ const movePathPoint = (
46
+ pathObject: Path,
47
+ x: number,
48
+ y: number,
49
+ commandIndex: number,
50
+ pointIndex: number
51
+ ) => {
52
+ const { path, pathOffset } = pathObject;
53
+
54
+ const anchorCommand =
55
+ path[(commandIndex > 0 ? commandIndex : path.length) - 1];
56
+ const anchorPoint = new Point(
57
+ anchorCommand[pointIndex] as number,
58
+ anchorCommand[pointIndex + 1] as number
59
+ );
60
+
61
+ const anchorPointInParentPlane = anchorPoint
62
+ .subtract(pathOffset)
63
+ .transform(pathObject.calcOwnMatrix());
64
+
65
+ const mouseLocalPosition = sendPointToPlane(
66
+ new Point(x, y),
67
+ undefined,
68
+ pathObject.calcOwnMatrix()
69
+ );
70
+
71
+ path[commandIndex][pointIndex] = mouseLocalPosition.x + pathOffset.x;
72
+ path[commandIndex][pointIndex + 1] = mouseLocalPosition.y + pathOffset.y;
73
+ pathObject.setDimensions();
74
+
75
+ const newAnchorPointInParentPlane = anchorPoint
76
+ .subtract(pathObject.pathOffset)
77
+ .transform(pathObject.calcOwnMatrix());
78
+
79
+ const diff = newAnchorPointInParentPlane.subtract(anchorPointInParentPlane);
80
+ pathObject.left -= diff.x;
81
+ pathObject.top -= diff.y;
82
+ pathObject.set('dirty', true);
83
+ return true;
84
+ };
85
+
86
+ /**
87
+ * This function locates the controls.
88
+ * It'll be used both for drawing and for interaction.
89
+ */
90
+ function pathPositionHandler(
91
+ this: PathPointControl,
92
+ dim: Point,
93
+ finalMatrix: TMat2D,
94
+ pathObject: Path
95
+ ) {
96
+ const { commandIndex, pointIndex } = this;
97
+ return calcPathPointPosition(pathObject, commandIndex, pointIndex);
98
+ }
99
+
100
+ /**
101
+ * This function defines what the control does.
102
+ * It'll be called on every mouse move after a control has been clicked and is being dragged.
103
+ * The function receives as argument the mouse event, the current transform object
104
+ * and the current position in canvas coordinate `transform.target` is a reference to the
105
+ * current object being transformed.
106
+ */
107
+ function pathActionHandler(
108
+ this: PathPointControl,
109
+ eventData: TPointerEvent,
110
+ transform: TTransformAnchor,
111
+ x: number,
112
+ y: number
113
+ ) {
114
+ const { target } = transform;
115
+ const { commandIndex, pointIndex } = this;
116
+ const actionPerformed = movePathPoint(
117
+ target as Path,
118
+ x,
119
+ y,
120
+ commandIndex,
121
+ pointIndex
122
+ );
123
+ if (actionPerformed) {
124
+ fireEvent(this.actionName as TModificationEvents, {
125
+ ...commonEventInfo(eventData, transform, x, y),
126
+ commandIndex,
127
+ pointIndex,
128
+ });
129
+ }
130
+ return actionPerformed;
131
+ }
132
+
133
+ const indexFromPrevCommand = (previousCommandType: TSimpleParseCommandType) =>
134
+ previousCommandType === 'C' ? 5 : previousCommandType === 'Q' ? 3 : 1;
135
+
136
+ class PathPointControl extends Control {
137
+ declare commandIndex: number;
138
+ declare pointIndex: number;
139
+ declare controlFill: string;
140
+ declare controlStroke: string;
141
+ constructor(options?: Partial<PathPointControl>) {
142
+ super(options);
143
+ }
144
+
145
+ render(
146
+ ctx: CanvasRenderingContext2D,
147
+ left: number,
148
+ top: number,
149
+ styleOverride: ControlRenderingStyleOverride | undefined,
150
+ fabricObject: Path
151
+ ) {
152
+ const overrides: ControlRenderingStyleOverride = {
153
+ ...styleOverride,
154
+ cornerColor: this.controlFill,
155
+ cornerStrokeColor: this.controlStroke,
156
+ transparentCorners: !this.controlFill,
157
+ };
158
+ super.render(ctx, left, top, overrides, fabricObject);
159
+ }
160
+ }
161
+
162
+ class PathControlPointControl extends PathPointControl {
163
+ declare connectionDashArray?: number[];
164
+ declare connectToCommandIndex: number;
165
+ declare connectToPointIndex: number;
166
+ constructor(options?: Partial<PathControlPointControl>) {
167
+ super(options);
168
+ }
169
+
170
+ render(
171
+ this: PathControlPointControl,
172
+ ctx: CanvasRenderingContext2D,
173
+ left: number,
174
+ top: number,
175
+ styleOverride: ControlRenderingStyleOverride | undefined,
176
+ fabricObject: Path
177
+ ) {
178
+ const { path } = fabricObject;
179
+ const {
180
+ commandIndex,
181
+ pointIndex,
182
+ connectToCommandIndex,
183
+ connectToPointIndex,
184
+ } = this;
185
+ ctx.save();
186
+ ctx.strokeStyle = this.controlStroke;
187
+ if (this.connectionDashArray) {
188
+ ctx.setLineDash(this.connectionDashArray);
189
+ }
190
+ const [commandType] = path[commandIndex];
191
+ const point = calcPathPointPosition(
192
+ fabricObject,
193
+ connectToCommandIndex,
194
+ connectToPointIndex
195
+ );
196
+
197
+ if (commandType === 'Q') {
198
+ // one control point connects to 2 points
199
+ const point2 = calcPathPointPosition(
200
+ fabricObject,
201
+ commandIndex,
202
+ pointIndex + 2
203
+ );
204
+ ctx.moveTo(point2.x, point2.y);
205
+ ctx.lineTo(left, top);
206
+ } else {
207
+ ctx.moveTo(left, top);
208
+ }
209
+ ctx.lineTo(point.x, point.y);
210
+ ctx.stroke();
211
+ ctx.restore();
212
+
213
+ super.render(ctx, left, top, styleOverride, fabricObject);
214
+ }
215
+ }
216
+
217
+ const createControl = (
218
+ commandIndexPos: number,
219
+ pointIndexPos: number,
220
+ isControlPoint: boolean,
221
+ options: Partial<Control> & {
222
+ controlPointStyle?: PathPointControlStyle;
223
+ pointStyle?: PathPointControlStyle;
224
+ },
225
+ connectToCommandIndex?: number,
226
+ connectToPointIndex?: number
227
+ ) =>
228
+ new (isControlPoint ? PathControlPointControl : PathPointControl)({
229
+ commandIndex: commandIndexPos,
230
+ pointIndex: pointIndexPos,
231
+ actionName: ACTION_NAME,
232
+ positionHandler: pathPositionHandler,
233
+ actionHandler: pathActionHandler,
234
+ connectToCommandIndex,
235
+ connectToPointIndex,
236
+ ...options,
237
+ ...(isControlPoint ? options.controlPointStyle : options.pointStyle),
238
+ } as Partial<PathControlPointControl>);
239
+
240
+ export function createPathControls(
241
+ path: Path,
242
+ options: Partial<Control> & {
243
+ controlPointStyle?: PathPointControlStyle;
244
+ pointStyle?: PathPointControlStyle;
245
+ } = {}
246
+ ): Record<string, Control> {
247
+ const controls = {} as Record<string, Control>;
248
+ let previousCommandType: TSimpleParseCommandType = 'M';
249
+ path.path.forEach((command, commandIndex) => {
250
+ const commandType = command[0];
251
+
252
+ if (commandType !== 'Z') {
253
+ controls[`c_${commandIndex}_${commandType}`] = createControl(
254
+ commandIndex,
255
+ command.length - 2,
256
+ false,
257
+ options
258
+ );
259
+ }
260
+ switch (commandType) {
261
+ case 'C':
262
+ controls[`c_${commandIndex}_C_CP_1`] = createControl(
263
+ commandIndex,
264
+ 1,
265
+ true,
266
+ options,
267
+ commandIndex - 1,
268
+ indexFromPrevCommand(previousCommandType)
269
+ );
270
+ controls[`c_${commandIndex}_C_CP_2`] = createControl(
271
+ commandIndex,
272
+ 3,
273
+ true,
274
+ options,
275
+ commandIndex,
276
+ 5
277
+ );
278
+ break;
279
+ case 'Q':
280
+ controls[`c_${commandIndex}_Q_CP_1`] = createControl(
281
+ commandIndex,
282
+ 1,
283
+ true,
284
+ options,
285
+ commandIndex,
286
+ 3
287
+ );
288
+ break;
289
+ }
290
+ previousCommandType = commandType;
291
+ });
292
+ return controls;
293
+ }
@@ -10,7 +10,7 @@ import type {
10
10
  TransformActionHandler,
11
11
  } from '../EventTypeDefs';
12
12
  import { wrapWithFireEvent } from './wrapWithFireEvent';
13
- import { sendPointToPlane } from '../util';
13
+ import { sendPointToPlane } from '../util/misc/planeChange';
14
14
  import { MODIFY_POLY } from '../constants';
15
15
 
16
16
  const ACTION_NAME: TModificationEvents = MODIFY_POLY;
@@ -8,17 +8,26 @@ import { commonEventInfo } from './util';
8
8
 
9
9
  /**
10
10
  * Wrap an action handler with firing an event if the action is performed
11
- * @param {Function} actionHandler the function to wrap
12
- * @return {Function} a function with an action handler signature
11
+ * @param {TModificationEvents} eventName the event we want to fire
12
+ * @param {TransformActionHandler<T>} actionHandler the function to wrap
13
+ * @param {object} extraEventInfo extra information to pas to the event handler
14
+ * @return {TransformActionHandler<T>} a function with an action handler signature
13
15
  */
14
- export const wrapWithFireEvent = <T extends Transform>(
16
+ export const wrapWithFireEvent = <
17
+ T extends Transform,
18
+ P extends object = Record<string, never>
19
+ >(
15
20
  eventName: TModificationEvents,
16
- actionHandler: TransformActionHandler<T>
21
+ actionHandler: TransformActionHandler<T>,
22
+ extraEventInfo?: P
17
23
  ) => {
18
24
  return ((eventData, transform, x, y) => {
19
25
  const actionPerformed = actionHandler(eventData, transform, x, y);
20
26
  if (actionPerformed) {
21
- fireEvent(eventName, commonEventInfo(eventData, transform, x, y));
27
+ fireEvent(eventName, {
28
+ ...commonEventInfo(eventData, transform, x, y),
29
+ ...extraEventInfo,
30
+ });
22
31
  }
23
32
  return actionPerformed;
24
33
  }) as TransformActionHandler<T>;
@@ -387,8 +387,7 @@ export class BaseFilter<
387
387
  return {
388
388
  type: this.type,
389
389
  ...defaultKeys.reduce<OwnProps>((acc, key) => {
390
- //@ts-expect-error TS doesn't get i want an object that looks like this
391
- acc[key] = this[key as keyof this];
390
+ acc[key] = this[key as keyof this] as unknown as typeof acc[typeof key];
392
391
  return acc;
393
392
  }, {} as OwnProps),
394
393
  };
@@ -12,6 +12,7 @@ export function parseStyleString(
12
12
  .replace(/;\s*$/, '')
13
13
  .split(';')
14
14
  .forEach((chunk) => {
15
+ if (!chunk) return;
15
16
  const [attr, value] = chunk.split(':');
16
17
  oStyle[attr.trim().toLowerCase()] = value.trim();
17
18
  });
@@ -0,0 +1,113 @@
1
+ import { getFabricWindow } from '../env';
2
+ import { parseUseDirectives } from './parseUseDirectives';
3
+
4
+ describe('parseUseDirectives', () => {
5
+ it('returns successful parse where use tag uses fill style prioritizing path tag when both tags have a style', async () => {
6
+ const str = `<svg id="svg" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
7
+ <path id="heart" d="M10,30 A20,20,0,0,1,50,30 A20,20,0,0,1,90,30 Q90,60,50,90 Q10,60,10,30 Z"
8
+ style="stroke:#000000;fill:#ff0000" />
9
+ <use x="100" y="0" xlink:href="#heart" style="stroke-width:5.0;fill:#0000ff" />
10
+ </svg>`;
11
+
12
+ const parser = new (getFabricWindow().DOMParser)();
13
+ const doc = parser.parseFromString(str.trim(), 'text/xml');
14
+ parseUseDirectives(doc);
15
+
16
+ const elements = Array.from(doc.documentElement.getElementsByTagName('*'));
17
+ expect(elements[0]).not.toBeNull();
18
+ if (elements[0] !== null) {
19
+ const style0 = elements[0].getAttribute('style');
20
+ expect(style0).toContain('fill:#ff0000');
21
+ }
22
+ expect(elements[1]).not.toBeNull();
23
+ if (elements[1] !== null) {
24
+ const style1 = elements[1].getAttribute('style');
25
+ expect(style1).toContain('fill:#ff0000');
26
+ // also contains extra style that path tag does not have
27
+ expect(style1).toContain('stroke-width:5.0');
28
+ }
29
+ });
30
+ it('returns successful parse where use tag uses fill style from itself when path tag empty', async () => {
31
+ const str = `<svg id="svg" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
32
+ <path id="heart" d="M10,30 A20,20,0,0,1,50,30 A20,20,0,0,1,90,30 Q90,60,50,90 Q10,60,10,30 Z" />
33
+ <use x="100" y="0" xlink:href="#heart" style="stroke-width:5.0;fill:#0000ff" />
34
+ </svg>`;
35
+
36
+ const parser = new (getFabricWindow().DOMParser)();
37
+ const doc = parser.parseFromString(str.trim(), 'text/xml');
38
+ parseUseDirectives(doc);
39
+
40
+ const elements = Array.from(doc.documentElement.getElementsByTagName('*'));
41
+ expect(elements[0]).not.toBeNull();
42
+ if (elements[0] !== null) {
43
+ const style0 = elements[0].getAttribute('style');
44
+ expect(style0).toBeNull();
45
+ }
46
+ expect(elements[1]).not.toBeNull();
47
+ if (elements[1] !== null) {
48
+ const style1 = elements[1].getAttribute('style');
49
+ expect(style1).toContain('fill:#0000ff');
50
+ // also contains extra style that path tag does not have
51
+ expect(style1).toContain('stroke-width:5.0');
52
+ }
53
+ });
54
+ it('returns successful parse where use tag uses fill style from path when its style tag is empty', async () => {
55
+ const str = `<svg id="svg" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
56
+ <path id="heart" d="M10,30 A20,20,0,0,1,50,30 A20,20,0,0,1,90,30 Q90,60,50,90 Q10,60,10,30 Z"
57
+ style="stroke:#000000;fill:#ff0000" />
58
+ <use x="100" y="0" xlink:href="#heart" />
59
+ </svg>`;
60
+
61
+ const parser = new (getFabricWindow().DOMParser)();
62
+ const doc = parser.parseFromString(str.trim(), 'text/xml');
63
+ parseUseDirectives(doc);
64
+
65
+ const elements = Array.from(doc.documentElement.getElementsByTagName('*'));
66
+ expect(elements[0]).not.toBeNull();
67
+ if (elements[0] !== null) {
68
+ const style0 = elements[0].getAttribute('style');
69
+ expect(style0).toContain('fill:#ff0000');
70
+ }
71
+ expect(elements[1]).not.toBeNull();
72
+ if (elements[1] !== null) {
73
+ const style1 = elements[1].getAttribute('style');
74
+ expect(style1).toContain('fill:#ff0000');
75
+ }
76
+ });
77
+ it('correctly merge styles tags considering attributes', async () => {
78
+ const str = `<svg id="svg" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
79
+ <path fill="red" id="heart" d="M10,30 A20,20,0,0,1,50,30 A20,20,0,0,1,90,30 Q90,60,50,90 Q10,60,10,30 Z" />
80
+ <use x="100" y="0" xlink:href="#heart" style="stroke:#000000;fill:green" />
81
+ </svg>`;
82
+
83
+ const parser = new (getFabricWindow().DOMParser)();
84
+ const doc = parser.parseFromString(str.trim(), 'text/xml');
85
+ parseUseDirectives(doc);
86
+
87
+ const elements = Array.from(doc.documentElement.getElementsByTagName('*'));
88
+ expect(elements[0]).not.toBeNull();
89
+ expect(elements[1]).not.toBeNull();
90
+ if (elements[1] !== null) {
91
+ const style1 = elements[1].getAttribute('style');
92
+ expect(style1).toContain('fill:red');
93
+ }
94
+ });
95
+ it('Will not override existing attributes', async () => {
96
+ const str = `<svg id="svg" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
97
+ <path fill="yellow" id="heart" d="M10,30 A20,20,0,0,1,50,30 A20,20,0,0,1,90,30 Q90,60,50,90 Q10,60,10,30 Z" />
98
+ <use x="100" y="0" xlink:href="#heart" fill="blue" />
99
+ </svg>`;
100
+
101
+ const parser = new (getFabricWindow().DOMParser)();
102
+ const doc = parser.parseFromString(str.trim(), 'text/xml');
103
+ parseUseDirectives(doc);
104
+
105
+ const elements = Array.from(doc.documentElement.getElementsByTagName('*'));
106
+ expect(elements[0]).not.toBeNull();
107
+ expect(elements[1]).not.toBeNull();
108
+ if (elements[1] !== null) {
109
+ const style1 = elements[1].getAttribute('fill');
110
+ expect(style1).toBe('yellow');
111
+ }
112
+ });
113
+ });
@@ -1,85 +1,91 @@
1
1
  import { svgNS } from './constants';
2
2
  import { getMultipleNodes } from './getMultipleNodes';
3
3
  import { applyViewboxTransform } from './applyViewboxTransform';
4
+ import { parseStyleString } from './parseStyleString';
4
5
 
5
6
  export function parseUseDirectives(doc: Document) {
6
7
  const nodelist = getMultipleNodes(doc, ['use', 'svg:use']);
7
- let i = 0;
8
- while (nodelist.length && i < nodelist.length) {
9
- const el = nodelist[i],
10
- xlinkAttribute = el.getAttribute('xlink:href') || el.getAttribute('href');
8
+ const skipAttributes = ['x', 'y', 'xlink:href', 'href', 'transform'];
11
9
 
12
- if (xlinkAttribute === null) {
13
- return;
10
+ for (const useElement of nodelist) {
11
+ const useAttributes: NamedNodeMap = useElement.attributes;
12
+
13
+ const useAttrMap: Record<string, string> = {};
14
+ for (const attr of useAttributes) {
15
+ attr.value && (useAttrMap[attr.name] = attr.value);
14
16
  }
15
17
 
16
- const xlink = xlinkAttribute.slice(1);
17
- const x = el.getAttribute('x') || 0;
18
- const y = el.getAttribute('y') || 0;
19
- const el2Orig = doc.getElementById(xlink);
20
- if (el2Orig === null) {
18
+ const xlink = (useAttrMap['xlink:href'] || useAttrMap.href || '').slice(1);
19
+
20
+ if (xlink === '') {
21
+ return;
22
+ }
23
+ const referencedElement = doc.getElementById(xlink);
24
+ if (referencedElement === null) {
21
25
  // if we can't find the target of the xlink, consider this use tag bad, similar to no xlink
22
26
  return;
23
27
  }
24
- let el2 = el2Orig.cloneNode(true) as Element;
25
- let currentTrans =
26
- (el2.getAttribute('transform') || '') +
27
- ' translate(' +
28
- x +
29
- ', ' +
30
- y +
31
- ')';
32
- const oldLength = nodelist.length;
33
- const namespace = svgNS;
28
+ let clonedOriginal = referencedElement.cloneNode(true) as Element;
34
29
 
35
- applyViewboxTransform(el2);
36
- if (/^svg$/i.test(el2.nodeName)) {
37
- const el3 = el2.ownerDocument.createElementNS(namespace, 'g');
38
- for (
39
- let j = 0, attrs = el2.attributes, len = attrs.length;
40
- j < len;
41
- j++
42
- ) {
43
- const attr: Attr | null = attrs.item(j);
44
- attr && el3.setAttributeNS(namespace, attr.nodeName, attr.nodeValue!);
45
- }
46
- // el2.firstChild != null
47
- while (el2.firstChild) {
48
- el3.appendChild(el2.firstChild);
49
- }
50
- el2 = el3;
30
+ const originalAttributes: NamedNodeMap = clonedOriginal.attributes;
31
+
32
+ const originalAttrMap: Record<string, string> = {};
33
+ for (const attr of originalAttributes) {
34
+ attr.value && (originalAttrMap[attr.name] = attr.value);
35
+ }
36
+
37
+ // Transform attribute needs to be merged in a particular way
38
+ const { x = 0, y = 0, transform = '' } = useAttrMap;
39
+ const currentTrans = `${transform} ${
40
+ originalAttrMap.transform || ''
41
+ } translate(${x}, ${y})`;
42
+
43
+ applyViewboxTransform(clonedOriginal);
44
+
45
+ if (/^svg$/i.test(clonedOriginal.nodeName)) {
46
+ // if is an SVG, create a group and apply all the attributes on top of it
47
+ const el3 = clonedOriginal.ownerDocument.createElementNS(svgNS, 'g');
48
+ Object.entries(originalAttrMap).forEach(([name, value]) =>
49
+ el3.setAttributeNS(svgNS, name, value)
50
+ );
51
+ el3.append(...clonedOriginal.childNodes);
52
+ clonedOriginal = el3;
51
53
  }
52
54
 
53
- for (let j = 0, attrs = el.attributes, len = attrs.length; j < len; j++) {
54
- const attr = attrs.item(j);
55
+ for (const attr of useAttributes) {
55
56
  if (!attr) {
56
57
  continue;
57
58
  }
58
- const { nodeName, nodeValue } = attr;
59
- if (
60
- nodeName === 'x' ||
61
- nodeName === 'y' ||
62
- nodeName === 'xlink:href' ||
63
- nodeName === 'href'
64
- ) {
59
+ const { name, value } = attr;
60
+ if (skipAttributes.includes(name)) {
65
61
  continue;
66
62
  }
67
63
 
68
- if (nodeName === 'transform') {
69
- currentTrans = nodeValue + ' ' + currentTrans;
64
+ if (name === 'style') {
65
+ // when use has a style, merge the two styles, with the ref being priority (not use)
66
+ // priority is by feature. an attribute for fill on the original element
67
+ // will overwrite the fill in style or attribute for tha use
68
+ const styleRecord: Record<string, any> = {};
69
+ parseStyleString(value!, styleRecord);
70
+ // cleanup styleRecord from attributes of original
71
+ Object.entries(originalAttrMap).forEach(([name, value]) => {
72
+ styleRecord[name] = value;
73
+ });
74
+ // now we can put in the style of the original that will overwrite the original attributes
75
+ parseStyleString(originalAttrMap.style || '', styleRecord);
76
+ const mergedStyles = Object.entries(styleRecord)
77
+ .map((entry) => entry.join(':'))
78
+ .join(';');
79
+ clonedOriginal.setAttribute(name, mergedStyles);
70
80
  } else {
71
- el2.setAttribute(nodeName, nodeValue!);
81
+ // set the attribute from use element only if the original does not have it already
82
+ !originalAttrMap[name] && clonedOriginal.setAttribute(name, value!);
72
83
  }
73
84
  }
74
85
 
75
- el2.setAttribute('transform', currentTrans);
76
- el2.setAttribute('instantiated_by_use', '1');
77
- el2.removeAttribute('id');
78
- const parentNode = el.parentNode;
79
- parentNode!.replaceChild(el2, el);
80
- // some browsers do not shorten nodelist after replaceChild (IE8)
81
- if (nodelist.length === oldLength) {
82
- i++;
83
- }
86
+ clonedOriginal.setAttribute('transform', currentTrans);
87
+ clonedOriginal.setAttribute('instantiated_by_use', '1');
88
+ clonedOriginal.removeAttribute('id');
89
+ useElement.parentNode!.replaceChild(clonedOriginal, useElement);
84
90
  }
85
91
  }
@@ -406,8 +406,10 @@ export abstract class ITextBehavior<
406
406
  this.fire('editing:entered', e ? { e } : undefined);
407
407
  this._fireSelectionChanged();
408
408
  if (this.canvas) {
409
- // @ts-expect-error in reality it is an IText instance
410
- this.canvas.fire('text:editing:entered', { target: this, e });
409
+ this.canvas.fire('text:editing:entered', {
410
+ target: this as unknown as IText,
411
+ e,
412
+ });
411
413
  this.canvas.requestRenderAll();
412
414
  }
413
415
  }
@@ -607,8 +607,6 @@ export class FabricImage<
607
607
  ctx: CanvasRenderingContext2D
608
608
  ) {
609
609
  ctx.imageSmoothingEnabled = this.imageSmoothing;
610
- // cant use ts-expect-error because of ts 5.3 cross check
611
- // @ts-ignore TS doesn't respect this type casting
612
610
  super.drawCacheOnCanvas(ctx);
613
611
  }
614
612
 
@@ -297,8 +297,6 @@ export class TextSVGExportMixin extends FabricObjectSVGExportMixin {
297
297
  * @return {String}
298
298
  */
299
299
  getSvgStyles(this: TextSVGExportMixin & FabricText, skipShadow?: boolean) {
300
- // cant use ts-expect-error because of ts 5.3 cross check
301
- // @ts-ignore TS doesn't respect this type casting
302
300
  return `${super.getSvgStyles(skipShadow)} white-space: pre;`;
303
301
  }
304
302
 
@@ -568,8 +568,6 @@ export class Textbox<
568
568
  * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
569
569
  * @return {Object} object representation of an instance
570
570
  */
571
- // cant use ts-expect-error because of ts 5.3 cross check
572
- // @ts-ignore TS this typing limitations
573
571
  toObject<
574
572
  T extends Omit<Props & TClassProperties<this>, keyof SProps>,
575
573
  K extends keyof T = never