@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
@@ -0,0 +1,647 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Fabric.js 7 Line Test</title>
7
+ <style>
8
+ body {
9
+ margin: 20px;
10
+ font-family: Arial, sans-serif;
11
+ }
12
+
13
+ #canvas-container {
14
+ border: 2px solid #ddd;
15
+ display: inline-block;
16
+ margin: 10px 0;
17
+ }
18
+
19
+ .controls {
20
+ margin: 10px 0;
21
+ }
22
+
23
+ button {
24
+ margin: 5px;
25
+ padding: 8px 16px;
26
+ font-size: 14px;
27
+ }
28
+
29
+ .info {
30
+ background: #f5f5f5;
31
+ padding: 10px;
32
+ margin: 10px 0;
33
+ border-radius: 4px;
34
+ }
35
+ </style>
36
+ </head>
37
+ <body>
38
+ <h1>Fabric.js 7 Line Rendering Test</h1>
39
+
40
+ <div class="info">
41
+ <strong>Test Instructions:</strong>
42
+ <ol>
43
+ <li>Click "Add Line" to create a line</li>
44
+ <li>Select the line to see endpoint controls</li>
45
+ <li>Drag the endpoint controls (blue circles)</li>
46
+ <li>Observe if the line renders correctly during drag preview</li>
47
+ <li>Check if the line renders correctly after releasing the drag</li>
48
+ </ol>
49
+ </div>
50
+
51
+ <div class="controls">
52
+ <button id="addLineBtn">Add Line</button>
53
+ <button id="addGradientLineBtn">Add Gradient Line</button>
54
+ <button id="clearCanvasBtn">Clear Canvas</button>
55
+ <button id="toggleDebugBtn">Toggle Debug</button>
56
+ <button id="exportPngBtn">Export as PNG</button>
57
+ <button id="exportSvgBtn">Export as SVG</button>
58
+ <button id="saveJsonBtn">Save JSON</button>
59
+ <button id="loadJsonBtn">Load JSON</button>
60
+ <input type="file" id="loadJsonFile" accept=".json" style="display: none;">
61
+ </div>
62
+
63
+ <div style="display: flex; gap: 20px;">
64
+ <div>
65
+ <h3>Fabric.js 7 Implementation</h3>
66
+ <div id="canvas-container">
67
+ <canvas id="canvas" width="800" height="600"></canvas>
68
+ </div>
69
+ </div>
70
+
71
+ <div>
72
+ <h3>Native Canvas 2D (CFX) Implementation</h3>
73
+ <div style="border: 2px solid #ddd; display: inline-block;">
74
+ <canvas id="cfx-canvas" width="800" height="600"></canvas>
75
+ </div>
76
+ <div class="controls">
77
+ <button id="addCfxLineBtn">Add CFX Line</button>
78
+ <button id="clearCfxCanvasBtn">Clear CFX Canvas</button>
79
+ </div>
80
+ </div>
81
+ </div>
82
+
83
+ <div class="info">
84
+ <strong>Expected Behavior:</strong> Line should render smoothly during endpoint dragging without clipping or positioning issues.
85
+ <br><strong>Problem:</strong> Line may not render correctly in drag preview but appears fine after drag completion.
86
+ </div>
87
+
88
+ <!-- Load Fabric.js 7 from local build -->
89
+ <script src="./dist/index.js"></script>
90
+
91
+ <script>
92
+ // Initialize canvas
93
+ const canvas = new fabric.Canvas('canvas', {
94
+ backgroundColor: 'white',
95
+ selection: true
96
+ });
97
+
98
+ let debugMode = false;
99
+ let lineCounter = 1;
100
+
101
+ // Add Line button
102
+ document.getElementById('addLineBtn').addEventListener('click', () => {
103
+ // Create a line with some default coordinates
104
+ const line = new fabric.Line([100, 100, 300, 200], {
105
+ stroke: '#ff0000',
106
+ strokeWidth: 3,
107
+ strokeLineCap: 'round',
108
+ selectable: true,
109
+ evented: true
110
+ });
111
+
112
+ canvas.add(line);
113
+ canvas.setActiveObject(line);
114
+ canvas.renderAll();
115
+
116
+ console.log('Added line:', line);
117
+ lineCounter++;
118
+ });
119
+
120
+ // Add Gradient Line button
121
+ document.getElementById('addGradientLineBtn').addEventListener('click', () => {
122
+ // Line coordinates
123
+ const x1 = 150, y1 = 120, x2 = 450, y2 = 280;
124
+
125
+ // Create a linear gradient that follows the line direction using absolute coordinates
126
+ const gradient = new fabric.Gradient({
127
+ type: 'linear',
128
+ gradientUnits: 'pixels',
129
+ coords: {
130
+ x1: x1,
131
+ y1: y1,
132
+ x2: x2,
133
+ y2: y2
134
+ },
135
+ colorStops: [
136
+ { offset: 0, color: '#ff0000' },
137
+ { offset: 0.5, color: '#00ff00' },
138
+ { offset: 1, color: '#0000ff' }
139
+ ]
140
+ });
141
+
142
+ // Create a line with gradient stroke
143
+ const line = new fabric.Line([x1, y1, x2, y2], {
144
+ stroke: gradient,
145
+ strokeWidth: 8,
146
+ strokeLineCap: 'round',
147
+ selectable: true,
148
+ evented: true
149
+ });
150
+
151
+ canvas.add(line);
152
+ canvas.setActiveObject(line);
153
+ canvas.renderAll();
154
+
155
+ console.log('Added gradient line:', line);
156
+ console.log('Gradient object:', gradient);
157
+ console.log('Line coordinates:', { x1, y1, x2, y2 });
158
+ console.log('Gradient coordinates:', gradient.coords);
159
+ lineCounter++;
160
+ });
161
+
162
+ // Clear Canvas button
163
+ document.getElementById('clearCanvasBtn').addEventListener('click', () => {
164
+ canvas.clear();
165
+ canvas.backgroundColor = 'white';
166
+ canvas.renderAll();
167
+ lineCounter = 1;
168
+ });
169
+
170
+ // Toggle Debug button
171
+ document.getElementById('toggleDebugBtn').addEventListener('click', () => {
172
+ debugMode = !debugMode;
173
+ const button = document.getElementById('toggleDebugBtn');
174
+ button.textContent = debugMode ? 'Disable Debug' : 'Enable Debug';
175
+
176
+ // Toggle debug visualization for all lines
177
+ canvas.getObjects('line').forEach(line => {
178
+ if (line._debugBoundingBox) {
179
+ // Enable/disable debug rendering
180
+ line._showDebug = debugMode;
181
+ }
182
+ });
183
+
184
+ canvas.renderAll();
185
+ });
186
+
187
+ // Export as PNG button
188
+ document.getElementById('exportPngBtn').addEventListener('click', () => {
189
+ try {
190
+ // Ensure canvas is rendered before export
191
+ canvas.renderAll();
192
+
193
+ // Get the canvas data URL
194
+ const dataURL = canvas.toDataURL({
195
+ format: 'png',
196
+ quality: 1.0,
197
+ multiplier: 1
198
+ });
199
+
200
+ // Create download link
201
+ const link = document.createElement('a');
202
+ link.download = `fabric-line-export-${Date.now()}.png`;
203
+ link.href = dataURL;
204
+
205
+ // Trigger download
206
+ document.body.appendChild(link);
207
+ link.click();
208
+ document.body.removeChild(link);
209
+
210
+ console.log('PNG export completed successfully');
211
+ } catch (error) {
212
+ console.error('PNG export failed:', error);
213
+ alert('Export failed: ' + error.message);
214
+ }
215
+ });
216
+
217
+ // Export as SVG button
218
+ document.getElementById('exportSvgBtn').addEventListener('click', () => {
219
+ try {
220
+ // Ensure canvas is rendered before export
221
+ canvas.renderAll();
222
+
223
+ // Get the SVG string
224
+ const svgString = canvas.toSVG({
225
+ viewBox: {
226
+ x: 0,
227
+ y: 0,
228
+ width: canvas.getWidth(),
229
+ height: canvas.getHeight()
230
+ },
231
+ width: canvas.getWidth(),
232
+ height: canvas.getHeight()
233
+ });
234
+
235
+ // Create blob and download link
236
+ const blob = new Blob([svgString], { type: 'image/svg+xml;charset=utf-8' });
237
+ const url = URL.createObjectURL(blob);
238
+
239
+ const link = document.createElement('a');
240
+ link.download = `fabric-line-export-${Date.now()}.svg`;
241
+ link.href = url;
242
+
243
+ // Trigger download
244
+ document.body.appendChild(link);
245
+ link.click();
246
+ document.body.removeChild(link);
247
+
248
+ // Clean up
249
+ URL.revokeObjectURL(url);
250
+
251
+ console.log('SVG export completed successfully');
252
+ console.log('SVG content preview:', svgString.substring(0, 200) + '...');
253
+ } catch (error) {
254
+ console.error('SVG export failed:', error);
255
+ alert('SVG export failed: ' + error.message);
256
+ }
257
+ });
258
+
259
+ // Canvas event listeners for debugging
260
+ canvas.on('selection:created', (e) => {
261
+ console.log('Selection created:', e.target);
262
+ });
263
+
264
+ canvas.on('selection:updated', (e) => {
265
+ console.log('Selection updated:', e.target);
266
+ });
267
+
268
+ canvas.on('object:moving', (e) => {
269
+ if (debugMode) {
270
+ console.log('Object moving:', e.target.type, {
271
+ left: e.target.left,
272
+ top: e.target.top,
273
+ x1: e.target.x1,
274
+ y1: e.target.y1,
275
+ x2: e.target.x2,
276
+ y2: e.target.y2
277
+ });
278
+ }
279
+ });
280
+
281
+ canvas.on('object:modified', (e) => {
282
+ console.log('Object modified:', e.target.type, {
283
+ left: e.target.left,
284
+ top: e.target.top,
285
+ x1: e.target.x1,
286
+ y1: e.target.y1,
287
+ x2: e.target.x2,
288
+ y2: e.target.y2
289
+ });
290
+ });
291
+
292
+ // Save JSON functionality
293
+ document.getElementById('saveJsonBtn').addEventListener('click', () => {
294
+ try {
295
+ // Get the canvas as JSON
296
+ const canvasJson = canvas.toJSON();
297
+
298
+ // Create a beautiful formatted JSON string
299
+ const jsonString = JSON.stringify(canvasJson, null, 2);
300
+
301
+ // Create a blob and download link
302
+ const blob = new Blob([jsonString], { type: 'application/json' });
303
+ const url = URL.createObjectURL(blob);
304
+
305
+ const link = document.createElement('a');
306
+ link.download = `fabric-canvas-${Date.now()}.json`;
307
+ link.href = url;
308
+
309
+ // Trigger download
310
+ document.body.appendChild(link);
311
+ link.click();
312
+ document.body.removeChild(link);
313
+
314
+ // Clean up
315
+ URL.revokeObjectURL(url);
316
+
317
+ console.log('Canvas saved as JSON:', canvasJson);
318
+ console.log('JSON string length:', jsonString.length);
319
+ } catch (error) {
320
+ console.error('Save JSON failed:', error);
321
+ alert('Save failed: ' + error.message);
322
+ }
323
+ });
324
+
325
+ // Load JSON functionality
326
+ document.getElementById('loadJsonBtn').addEventListener('click', () => {
327
+ document.getElementById('loadJsonFile').click();
328
+ });
329
+
330
+ document.getElementById('loadJsonFile').addEventListener('change', (e) => {
331
+ const file = e.target.files[0];
332
+ if (!file) return;
333
+
334
+ const reader = new FileReader();
335
+ reader.onload = function(e) {
336
+ try {
337
+ const jsonData = JSON.parse(e.target.result);
338
+
339
+ console.log('Loading JSON data:', jsonData);
340
+
341
+ // Clear existing canvas
342
+ canvas.clear();
343
+
344
+ // Load the JSON data into the canvas
345
+ canvas.loadFromJSON(jsonData, () => {
346
+ // This callback is called after loading is complete
347
+ canvas.renderAll();
348
+ console.log('Canvas loaded from JSON successfully');
349
+ console.log('Loaded objects:', canvas.getObjects());
350
+
351
+ // Log details about loaded lines
352
+ const lines = canvas.getObjects('line');
353
+ lines.forEach((line, index) => {
354
+ console.log(`Line ${index + 1}:`, {
355
+ type: line.type,
356
+ left: line.left,
357
+ top: line.top,
358
+ x1: line.x1,
359
+ y1: line.y1,
360
+ x2: line.x2,
361
+ y2: line.y2,
362
+ stroke: line.stroke,
363
+ strokeWidth: line.strokeWidth
364
+ });
365
+ });
366
+ });
367
+
368
+ // Clear the file input for next use
369
+ e.target.value = '';
370
+
371
+ } catch (error) {
372
+ console.error('Load JSON failed:', error);
373
+ alert('Load failed: ' + error.message);
374
+ }
375
+ };
376
+
377
+ reader.readAsText(file);
378
+ });
379
+
380
+ // Add initial line for testing
381
+ const initialLine = new fabric.Line([150, 150, 350, 250], {
382
+ stroke: '#2196F3',
383
+ strokeWidth: 4,
384
+ strokeLineCap: 'round',
385
+ selectable: true,
386
+ evented: true
387
+ });
388
+
389
+ canvas.add(initialLine);
390
+ canvas.renderAll();
391
+
392
+ console.log('Fabric.js version:', fabric.version);
393
+ console.log('Initial line added:', initialLine);
394
+ </script>
395
+
396
+ <script>
397
+ // CFX (Native Canvas 2D) Implementation
398
+ class CFXLine {
399
+ constructor(x1, y1, x2, y2, options = {}) {
400
+ this.x1 = x1;
401
+ this.y1 = y1;
402
+ this.x2 = x2;
403
+ this.y2 = y2;
404
+ this.stroke = options.stroke || '#000000';
405
+ this.strokeWidth = options.strokeWidth || 1;
406
+ this.strokeLineCap = options.strokeLineCap || 'butt';
407
+ this.selected = false;
408
+ this.dragging = null; // null, 'p1', 'p2', or 'line'
409
+ this.controlSize = 12;
410
+ }
411
+
412
+ draw(ctx) {
413
+ ctx.save();
414
+ ctx.strokeStyle = this.stroke;
415
+ ctx.lineWidth = this.strokeWidth;
416
+ ctx.lineCap = this.strokeLineCap;
417
+
418
+ ctx.beginPath();
419
+ ctx.moveTo(this.x1, this.y1);
420
+ ctx.lineTo(this.x2, this.y2);
421
+ ctx.stroke();
422
+
423
+ // Draw endpoint controls if selected
424
+ if (this.selected) {
425
+ this.drawControls(ctx);
426
+ }
427
+
428
+ ctx.restore();
429
+ }
430
+
431
+ drawControls(ctx) {
432
+ ctx.save();
433
+ ctx.fillStyle = '#007bff';
434
+ ctx.strokeStyle = '#ffffff';
435
+ ctx.lineWidth = 2;
436
+
437
+ // Draw p1 control
438
+ ctx.beginPath();
439
+ ctx.arc(this.x1, this.y1, this.controlSize / 2, 0, 2 * Math.PI);
440
+ ctx.fill();
441
+ ctx.stroke();
442
+
443
+ // Draw p2 control
444
+ ctx.beginPath();
445
+ ctx.arc(this.x2, this.y2, this.controlSize / 2, 0, 2 * Math.PI);
446
+ ctx.fill();
447
+ ctx.stroke();
448
+
449
+ ctx.restore();
450
+ }
451
+
452
+ hitTest(x, y) {
453
+ // Test endpoint controls first
454
+ if (this.selected) {
455
+ const dist1 = Math.sqrt((x - this.x1) ** 2 + (y - this.y1) ** 2);
456
+ if (dist1 <= this.controlSize) return 'p1';
457
+
458
+ const dist2 = Math.sqrt((x - this.x2) ** 2 + (y - this.y2) ** 2);
459
+ if (dist2 <= this.controlSize) return 'p2';
460
+ }
461
+
462
+ // Test line itself (using distance from point to line)
463
+ const A = this.y2 - this.y1;
464
+ const B = this.x1 - this.x2;
465
+ const C = this.x2 * this.y1 - this.x1 * this.y2;
466
+ const distance = Math.abs(A * x + B * y + C) / Math.sqrt(A * A + B * B);
467
+
468
+ if (distance <= this.strokeWidth / 2 + 3) {
469
+ // Check if point is within line segment bounds
470
+ const minX = Math.min(this.x1, this.x2) - 5;
471
+ const maxX = Math.max(this.x1, this.x2) + 5;
472
+ const minY = Math.min(this.y1, this.y2) - 5;
473
+ const maxY = Math.max(this.y1, this.y2) + 5;
474
+
475
+ if (x >= minX && x <= maxX && y >= minY && y <= maxY) {
476
+ return 'line';
477
+ }
478
+ }
479
+
480
+ return null;
481
+ }
482
+
483
+ updateEndpoint(endpoint, x, y, shiftKey = false) {
484
+ if (shiftKey) {
485
+ // Snap to 15-degree angles
486
+ const fixedX = endpoint === 'p1' ? this.x2 : this.x1;
487
+ const fixedY = endpoint === 'p1' ? this.y2 : this.y1;
488
+
489
+ const deltaX = x - fixedX;
490
+ const deltaY = y - fixedY;
491
+ const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
492
+
493
+ if (distance > 0) {
494
+ let angle = Math.atan2(deltaY, deltaX) * (180 / Math.PI);
495
+ const snapIncrement = 15;
496
+ const snappedAngle = Math.round(angle / snapIncrement) * snapIncrement;
497
+ const snappedRadians = snappedAngle * (Math.PI / 180);
498
+
499
+ x = fixedX + Math.cos(snappedRadians) * distance;
500
+ y = fixedY + Math.sin(snappedRadians) * distance;
501
+ }
502
+ }
503
+
504
+ if (endpoint === 'p1') {
505
+ this.x1 = x;
506
+ this.y1 = y;
507
+ } else if (endpoint === 'p2') {
508
+ this.x2 = x;
509
+ this.y2 = y;
510
+ }
511
+ }
512
+
513
+ move(deltaX, deltaY) {
514
+ this.x1 += deltaX;
515
+ this.y1 += deltaY;
516
+ this.x2 += deltaX;
517
+ this.y2 += deltaY;
518
+ }
519
+ }
520
+
521
+ // CFX Canvas Setup
522
+ const cfxCanvas = document.getElementById('cfx-canvas');
523
+ const cfxCtx = cfxCanvas.getContext('2d');
524
+ let cfxLines = [];
525
+ let cfxSelectedLine = null;
526
+ let cfxDragStart = null;
527
+ let cfxDragType = null;
528
+
529
+ function cfxRender() {
530
+ cfxCtx.clearRect(0, 0, cfxCanvas.width, cfxCanvas.height);
531
+ cfxCtx.fillStyle = 'white';
532
+ cfxCtx.fillRect(0, 0, cfxCanvas.width, cfxCanvas.height);
533
+
534
+ cfxLines.forEach(line => line.draw(cfxCtx));
535
+ }
536
+
537
+ function cfxGetMousePos(e) {
538
+ const rect = cfxCanvas.getBoundingClientRect();
539
+ return {
540
+ x: e.clientX - rect.left,
541
+ y: e.clientY - rect.top
542
+ };
543
+ }
544
+
545
+ // CFX Mouse Events
546
+ cfxCanvas.addEventListener('mousedown', (e) => {
547
+ const pos = cfxGetMousePos(e);
548
+
549
+ // Test all lines for hit
550
+ for (let line of cfxLines) {
551
+ const hit = line.hitTest(pos.x, pos.y);
552
+ if (hit) {
553
+ cfxSelectedLine = line;
554
+ cfxDragType = hit;
555
+ cfxDragStart = pos;
556
+
557
+ // Update selection
558
+ cfxLines.forEach(l => l.selected = (l === line));
559
+ cfxRender();
560
+ return;
561
+ }
562
+ }
563
+
564
+ // No hit - deselect all
565
+ cfxSelectedLine = null;
566
+ cfxLines.forEach(l => l.selected = false);
567
+ cfxRender();
568
+ });
569
+
570
+ cfxCanvas.addEventListener('mousemove', (e) => {
571
+ if (!cfxSelectedLine || !cfxDragStart) return;
572
+
573
+ const pos = cfxGetMousePos(e);
574
+ const deltaX = pos.x - cfxDragStart.x;
575
+ const deltaY = pos.y - cfxDragStart.y;
576
+
577
+ if (cfxDragType === 'p1') {
578
+ cfxSelectedLine.updateEndpoint('p1', pos.x, pos.y, e.shiftKey);
579
+ } else if (cfxDragType === 'p2') {
580
+ cfxSelectedLine.updateEndpoint('p2', pos.x, pos.y, e.shiftKey);
581
+ } else if (cfxDragType === 'line') {
582
+ cfxSelectedLine.move(deltaX, deltaY);
583
+ cfxDragStart = pos;
584
+ }
585
+
586
+ cfxRender();
587
+ });
588
+
589
+ cfxCanvas.addEventListener('mouseup', (e) => {
590
+ cfxDragStart = null;
591
+ cfxDragType = null;
592
+ });
593
+
594
+ // CFX Cursor handling
595
+ cfxCanvas.addEventListener('mousemove', (e) => {
596
+ if (cfxDragStart) return; // Don't change cursor while dragging
597
+
598
+ const pos = cfxGetMousePos(e);
599
+ let cursor = 'default';
600
+
601
+ for (let line of cfxLines) {
602
+ const hit = line.hitTest(pos.x, pos.y);
603
+ if (hit === 'p1' || hit === 'p2') {
604
+ cursor = 'move';
605
+ break;
606
+ } else if (hit === 'line') {
607
+ cursor = 'move';
608
+ break;
609
+ }
610
+ }
611
+
612
+ cfxCanvas.style.cursor = cursor;
613
+ });
614
+
615
+ // CFX Controls
616
+ document.getElementById('addCfxLineBtn').addEventListener('click', () => {
617
+ const line = new CFXLine(150, 150, 350, 250, {
618
+ stroke: '#ff0000',
619
+ strokeWidth: 3,
620
+ strokeLineCap: 'round'
621
+ });
622
+
623
+ cfxLines.push(line);
624
+ cfxRender();
625
+ console.log('Added CFX line:', line);
626
+ });
627
+
628
+ document.getElementById('clearCfxCanvasBtn').addEventListener('click', () => {
629
+ cfxLines = [];
630
+ cfxSelectedLine = null;
631
+ cfxRender();
632
+ });
633
+
634
+ // Add initial CFX line
635
+ const initialCfxLine = new CFXLine(150, 150, 350, 250, {
636
+ stroke: '#2196F3',
637
+ strokeWidth: 4,
638
+ strokeLineCap: 'round'
639
+ });
640
+
641
+ cfxLines.push(initialCfxLine);
642
+ cfxRender();
643
+
644
+ console.log('Initial CFX line added:', initialCfxLine);
645
+ </script>
646
+ </body>
647
+ </html>