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

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 (66) hide show
  1. package/dist/index.js +504 -102
  2. package/dist/index.js.map +1 -1
  3. package/dist/index.min.js +1 -1
  4. package/dist/index.min.js.map +1 -1
  5. package/dist/index.min.mjs +1 -1
  6. package/dist/index.min.mjs.map +1 -1
  7. package/dist/index.mjs +504 -102
  8. package/dist/index.mjs.map +1 -1
  9. package/dist/index.node.cjs +504 -102
  10. package/dist/index.node.cjs.map +1 -1
  11. package/dist/index.node.mjs +504 -102
  12. package/dist/index.node.mjs.map +1 -1
  13. package/dist/package.json.min.mjs +1 -1
  14. package/dist/package.json.mjs +1 -1
  15. package/dist/src/shapes/Polyline.d.ts +7 -0
  16. package/dist/src/shapes/Polyline.d.ts.map +1 -1
  17. package/dist/src/shapes/Polyline.min.mjs +1 -1
  18. package/dist/src/shapes/Polyline.min.mjs.map +1 -1
  19. package/dist/src/shapes/Polyline.mjs +48 -16
  20. package/dist/src/shapes/Polyline.mjs.map +1 -1
  21. package/dist/src/shapes/Text/Text.d.ts.map +1 -1
  22. package/dist/src/shapes/Text/Text.min.mjs +1 -1
  23. package/dist/src/shapes/Text/Text.min.mjs.map +1 -1
  24. package/dist/src/shapes/Text/Text.mjs +64 -12
  25. package/dist/src/shapes/Text/Text.mjs.map +1 -1
  26. package/dist/src/shapes/Textbox.d.ts.map +1 -1
  27. package/dist/src/shapes/Textbox.min.mjs +1 -1
  28. package/dist/src/shapes/Textbox.min.mjs.map +1 -1
  29. package/dist/src/shapes/Textbox.mjs +2 -52
  30. package/dist/src/shapes/Textbox.mjs.map +1 -1
  31. package/dist/src/shapes/Triangle.d.ts +27 -2
  32. package/dist/src/shapes/Triangle.d.ts.map +1 -1
  33. package/dist/src/shapes/Triangle.min.mjs +1 -1
  34. package/dist/src/shapes/Triangle.min.mjs.map +1 -1
  35. package/dist/src/shapes/Triangle.mjs +72 -12
  36. package/dist/src/shapes/Triangle.mjs.map +1 -1
  37. package/dist/src/text/overlayEditor.d.ts.map +1 -1
  38. package/dist/src/text/overlayEditor.min.mjs +1 -1
  39. package/dist/src/text/overlayEditor.min.mjs.map +1 -1
  40. package/dist/src/text/overlayEditor.mjs +143 -9
  41. package/dist/src/text/overlayEditor.mjs.map +1 -1
  42. package/dist/src/util/misc/cornerRadius.d.ts +70 -0
  43. package/dist/src/util/misc/cornerRadius.d.ts.map +1 -0
  44. package/dist/src/util/misc/cornerRadius.min.mjs +2 -0
  45. package/dist/src/util/misc/cornerRadius.min.mjs.map +1 -0
  46. package/dist/src/util/misc/cornerRadius.mjs +181 -0
  47. package/dist/src/util/misc/cornerRadius.mjs.map +1 -0
  48. package/dist-extensions/src/shapes/Polyline.d.ts +7 -0
  49. package/dist-extensions/src/shapes/Polyline.d.ts.map +1 -1
  50. package/dist-extensions/src/shapes/Text/Text.d.ts.map +1 -1
  51. package/dist-extensions/src/shapes/Textbox.d.ts.map +1 -1
  52. package/dist-extensions/src/shapes/Triangle.d.ts +27 -2
  53. package/dist-extensions/src/shapes/Triangle.d.ts.map +1 -1
  54. package/dist-extensions/src/text/overlayEditor.d.ts.map +1 -1
  55. package/dist-extensions/src/util/misc/cornerRadius.d.ts +70 -0
  56. package/dist-extensions/src/util/misc/cornerRadius.d.ts.map +1 -0
  57. package/fabric-test-editor.html +1048 -0
  58. package/package.json +164 -164
  59. package/src/shapes/Polyline.ts +70 -29
  60. package/src/shapes/Text/Text.ts +79 -14
  61. package/src/shapes/Textbox.ts +1 -1
  62. package/src/shapes/Triangle.spec.ts +76 -0
  63. package/src/shapes/Triangle.ts +85 -15
  64. package/src/text/overlayEditor.ts +152 -12
  65. package/src/util/misc/cornerRadius.spec.ts +141 -0
  66. package/src/util/misc/cornerRadius.ts +269 -0
@@ -0,0 +1,1048 @@
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 Test Editor</title>
7
+ <script src="./dist/index.js"></script>
8
+ <style>
9
+ * {
10
+ margin: 0;
11
+ padding: 0;
12
+ box-sizing: border-box;
13
+ }
14
+
15
+ body {
16
+ font-family:
17
+ -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
18
+ background-color: #f5f5f5;
19
+ display: flex;
20
+ height: 100vh;
21
+ }
22
+
23
+ .sidebar {
24
+ width: 300px;
25
+ background: white;
26
+ border-right: 1px solid #e5e7eb;
27
+ padding: 20px;
28
+ overflow-y: auto;
29
+ }
30
+
31
+ .canvas-container {
32
+ flex: 1;
33
+ display: flex;
34
+ align-items: center;
35
+ justify-content: center;
36
+ padding: 20px;
37
+ position: relative;
38
+ }
39
+
40
+ #canvas {
41
+ border: 1px solid #d1d5db;
42
+ border-radius: 8px;
43
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
44
+ }
45
+
46
+ /* Fix for Fabric.js canvas alignment and centering */
47
+ .canvas-container [data-fabric='wrapper'] {
48
+ position: relative !important;
49
+ margin: 0 auto !important;
50
+ }
51
+
52
+ .canvas-container [data-fabric='wrapper'] canvas {
53
+ left: 0 !important;
54
+ top: 0 !important;
55
+ }
56
+
57
+ .control-group {
58
+ margin-bottom: 20px;
59
+ }
60
+
61
+ .control-group h3 {
62
+ margin-bottom: 10px;
63
+ color: #374151;
64
+ font-size: 14px;
65
+ font-weight: 600;
66
+ }
67
+
68
+ button {
69
+ width: 100%;
70
+ margin-bottom: 8px;
71
+ padding: 10px 16px;
72
+ background: #3b82f6;
73
+ color: white;
74
+ border: none;
75
+ border-radius: 6px;
76
+ cursor: pointer;
77
+ font-size: 14px;
78
+ font-weight: 500;
79
+ transition: background-color 0.2s;
80
+ }
81
+
82
+ button:hover {
83
+ background: #2563eb;
84
+ }
85
+
86
+ button.secondary {
87
+ background: #6b7280;
88
+ }
89
+
90
+ button.secondary:hover {
91
+ background: #4b5563;
92
+ }
93
+
94
+ input,
95
+ select {
96
+ width: 100%;
97
+ margin-bottom: 8px;
98
+ padding: 8px 12px;
99
+ border: 1px solid #d1d5db;
100
+ border-radius: 6px;
101
+ font-size: 14px;
102
+ }
103
+
104
+ .color-input {
105
+ height: 40px;
106
+ cursor: pointer;
107
+ }
108
+
109
+ .info {
110
+ background: #f3f4f6;
111
+ padding: 12px;
112
+ border-radius: 6px;
113
+ font-size: 12px;
114
+ color: #4b5563;
115
+ margin-bottom: 20px;
116
+ }
117
+
118
+ .workspace {
119
+ background: white;
120
+ border: 2px solid #e5e7eb;
121
+ }
122
+
123
+ /* Slider styling */
124
+ .slider-container {
125
+ margin-bottom: 8px;
126
+ }
127
+
128
+ .slider-container label {
129
+ display: block;
130
+ font-size: 12px;
131
+ color: #6b7280;
132
+ margin-bottom: 4px;
133
+ }
134
+
135
+ input[type='range'] {
136
+ width: 100%;
137
+ height: 6px;
138
+ border-radius: 3px;
139
+ background: #e5e7eb;
140
+ outline: none;
141
+ -webkit-appearance: none;
142
+ appearance: none;
143
+ }
144
+
145
+ input[type='range']::-webkit-slider-thumb {
146
+ -webkit-appearance: none;
147
+ appearance: none;
148
+ width: 18px;
149
+ height: 18px;
150
+ border-radius: 50%;
151
+ background: #3b82f6;
152
+ cursor: pointer;
153
+ border: 2px solid #ffffff;
154
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
155
+ }
156
+
157
+ input[type='range']::-webkit-slider-thumb:hover {
158
+ background: #2563eb;
159
+ }
160
+
161
+ input[type='range']::-moz-range-thumb {
162
+ width: 18px;
163
+ height: 18px;
164
+ border-radius: 50%;
165
+ background: #3b82f6;
166
+ cursor: pointer;
167
+ border: 2px solid #ffffff;
168
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
169
+ }
170
+
171
+ .slider-value {
172
+ font-size: 12px;
173
+ color: #374151;
174
+ font-weight: 500;
175
+ text-align: center;
176
+ margin-top: 4px;
177
+ }
178
+ </style>
179
+ </head>
180
+ <body>
181
+ <div class="sidebar">
182
+ <div class="info">
183
+ <strong>Fabric.js 7 Beta Test Editor</strong><br />
184
+ Test your Fabric.js changes here with useOverlayEditing enabled.
185
+ </div>
186
+
187
+ <div class="control-group">
188
+ <h3>Add Objects</h3>
189
+ <button onclick="addText()">Add Text</button>
190
+ <button onclick="addRectangle()">Add Rectangle</button>
191
+ <button onclick="addCircle()">Add Circle</button>
192
+ <button onclick="addTriangle()">Add Triangle</button>
193
+ <button onclick="addPolygon()">Add Polygon</button>
194
+ </div>
195
+
196
+ <div class="control-group">
197
+ <h3>Text Options</h3>
198
+ <input
199
+ type="text"
200
+ id="textValue"
201
+ placeholder="Enter text..."
202
+ value="Sample Text"
203
+ />
204
+ <select id="textDirection">
205
+ <option value="ltr">LTR (Left to Right)</option>
206
+ <option value="rtl">RTL (Right to Left)</option>
207
+ </select>
208
+ <select id="textAlign">
209
+ <option value="left">Left</option>
210
+ <option value="center">Center</option>
211
+ <option value="right">Right</option>
212
+ <option value="justify">Justify</option>
213
+ <option value="justify-left">Justify Left</option>
214
+ <option value="justify-center">Justify Center</option>
215
+ <option value="justify-right">Justify Right</option>
216
+ </select>
217
+ <input
218
+ type="number"
219
+ id="fontSize"
220
+ placeholder="Font Size"
221
+ value="32"
222
+ min="8"
223
+ max="200"
224
+ />
225
+ </div>
226
+
227
+ <div class="control-group">
228
+ <h3>Font Style</h3>
229
+ <select id="fontWeight">
230
+ <option value="normal">Normal</option>
231
+ <option value="bold">Bold</option>
232
+ <option value="100">100 - Thin</option>
233
+ <option value="200">200 - Extra Light</option>
234
+ <option value="300">300 - Light</option>
235
+ <option value="400">400 - Normal</option>
236
+ <option value="500">500 - Medium</option>
237
+ <option value="600">600 - Semi Bold</option>
238
+ <option value="700">700 - Bold</option>
239
+ <option value="800">800 - Extra Bold</option>
240
+ <option value="900">900 - Black</option>
241
+ </select>
242
+ <select id="fontStyle">
243
+ <option value="normal">Normal</option>
244
+ <option value="italic">Italic</option>
245
+ <option value="oblique">Oblique</option>
246
+ </select>
247
+ <select id="fontFamily">
248
+ <option value="Arial">Arial</option>
249
+ <option value="Georgia">Georgia</option>
250
+ <option value="Times New Roman">Times New Roman</option>
251
+ <option value="Courier New">Courier New</option>
252
+ <option value="Helvetica">Helvetica</option>
253
+ <option value="Verdana">Verdana</option>
254
+ </select>
255
+ </div>
256
+
257
+ <div class="control-group">
258
+ <h3>Colors</h3>
259
+ <input
260
+ type="color"
261
+ id="fillColor"
262
+ class="color-input"
263
+ value="#000000"
264
+ />
265
+ <label for="fillColor" style="font-size: 12px; color: #6b7280"
266
+ >Fill Color</label
267
+ >
268
+ <input
269
+ type="color"
270
+ id="strokeColor"
271
+ class="color-input"
272
+ value="#000000"
273
+ />
274
+ <label for="strokeColor" style="font-size: 12px; color: #6b7280"
275
+ >Stroke Color</label
276
+ >
277
+ <div class="slider-container">
278
+ <label for="strokeWidth">Stroke Width</label>
279
+ <input
280
+ type="range"
281
+ id="strokeWidth"
282
+ min="0"
283
+ max="20"
284
+ value="2"
285
+ step="1"
286
+ />
287
+ <div class="slider-value" id="strokeWidthValue">2px</div>
288
+ </div>
289
+ </div>
290
+
291
+ <div class="control-group">
292
+ <h3>Corner Radius (Canva-style)</h3>
293
+ <div class="slider-container">
294
+ <label for="cornerRadius">Corner Radius</label>
295
+ <input
296
+ type="range"
297
+ id="cornerRadius"
298
+ min="0"
299
+ max="50"
300
+ value="0"
301
+ step="1"
302
+ />
303
+ <div class="slider-value" id="cornerRadiusValue">0px</div>
304
+ </div>
305
+ <button onclick="testCornerRadius()">Test All Corner Radius</button>
306
+ </div>
307
+
308
+ <div class="control-group">
309
+ <h3>Drawing Mode</h3>
310
+ <button id="enableDrawing" onclick="enableDrawing()">Enable Drawing</button>
311
+ <button id="disableDrawing" onclick="disableDrawing()">Disable Drawing</button>
312
+ <div class="slider-container">
313
+ <label for="brushWidth">Brush Width</label>
314
+ <input
315
+ type="range"
316
+ id="brushWidth"
317
+ min="1"
318
+ max="50"
319
+ value="5"
320
+ step="1"
321
+ />
322
+ <div class="slider-value" id="brushWidthValue">5px</div>
323
+ </div>
324
+ </div>
325
+
326
+ <div class="control-group">
327
+ <h3>Text Alignment</h3>
328
+ <button onclick="setTextAlign('left')">Align Left</button>
329
+ <button onclick="setTextAlign('center')">Align Center</button>
330
+ <button onclick="setTextAlign('right')">Align Right</button>
331
+ <button onclick="setTextAlign('justify')">Justify</button>
332
+ <button onclick="setTextAlign('justify-left')">Justify Left</button>
333
+ <button onclick="setTextAlign('justify-center')">Justify Center</button>
334
+ <button onclick="setTextAlign('justify-right')">Justify Right</button>
335
+ </div>
336
+
337
+ <div class="control-group">
338
+ <h3>Font Controls</h3>
339
+ <button onclick="toggleBold()">Toggle Bold</button>
340
+ <button onclick="toggleItalic()">Toggle Italic</button>
341
+ <button onclick="changeFontFamily()">Change Font</button>
342
+ </div>
343
+
344
+ <div class="control-group">
345
+ <h3>Actions</h3>
346
+ <button onclick="deleteSelected()" class="secondary">
347
+ Delete Selected
348
+ </button>
349
+ <button onclick="clearCanvas()" class="secondary">Clear Canvas</button>
350
+ <button onclick="exportJSON()" class="secondary">Export JSON</button>
351
+ </div>
352
+
353
+ <div class="control-group">
354
+ <h3>Canvas Info</h3>
355
+ <div id="canvasInfo" style="font-size: 12px; color: #6b7280">
356
+ Objects: 0<br />
357
+ Selected: None
358
+ </div>
359
+ </div>
360
+ </div>
361
+
362
+ <div class="canvas-container">
363
+ <canvas id="canvas" width="800" height="600"></canvas>
364
+ </div>
365
+
366
+ <script>
367
+ // Initialize Fabric.js canvas
368
+ const canvas = new fabric.Canvas('canvas', {
369
+ backgroundColor: 'white',
370
+ selection: true,
371
+ preserveObjectStacking: true,
372
+ });
373
+
374
+ // Create workspace (like your editor)
375
+ const workspace = new fabric.Rect({
376
+ left: 100,
377
+ top: 100,
378
+ width: 600,
379
+ height: 400,
380
+ fill: 'white',
381
+ stroke: '#e5e7eb',
382
+ strokeWidth: 2,
383
+ selectable: false,
384
+ evented: false,
385
+ name: 'clip',
386
+ });
387
+
388
+ canvas.add(workspace);
389
+ canvas.centerObject(workspace);
390
+ canvas.renderAll();
391
+
392
+ // Function to fix canvas positioning
393
+ function fixCanvasPositioning() {
394
+ const wrapper = document.querySelector('[data-fabric="wrapper"]');
395
+ if (wrapper) {
396
+ // Force wrapper to stay centered
397
+ wrapper.style.position = 'relative';
398
+ wrapper.style.margin = '0 auto';
399
+ wrapper.style.left = '';
400
+ wrapper.style.right = '';
401
+ wrapper.style.transform = '';
402
+
403
+ // Get both canvas elements
404
+ const lowerCanvas = wrapper.querySelector('[data-fabric="main"]');
405
+ const upperCanvas = wrapper.querySelector('[data-fabric="top"]');
406
+
407
+ if (lowerCanvas && upperCanvas) {
408
+ // Ensure both canvases are positioned identically within wrapper
409
+ lowerCanvas.style.position = 'absolute';
410
+ upperCanvas.style.position = 'absolute';
411
+ lowerCanvas.style.left = '0px';
412
+ lowerCanvas.style.top = '0px';
413
+ upperCanvas.style.left = '0px';
414
+ upperCanvas.style.top = '0px';
415
+ }
416
+ }
417
+ }
418
+
419
+ // Apply fix multiple times to catch Fabric.js overrides
420
+ setTimeout(fixCanvasPositioning, 0);
421
+ setTimeout(fixCanvasPositioning, 50);
422
+ setTimeout(fixCanvasPositioning, 100);
423
+ setTimeout(fixCanvasPositioning, 200);
424
+ setTimeout(fixCanvasPositioning, 500);
425
+
426
+ // Also fix on canvas events that might trigger repositioning
427
+ canvas.on('after:render', fixCanvasPositioning);
428
+ canvas.on('canvas:cleared', fixCanvasPositioning);
429
+
430
+ // Use MutationObserver to catch any DOM changes to the wrapper
431
+ const observer = new MutationObserver((mutations) => {
432
+ mutations.forEach((mutation) => {
433
+ if (
434
+ mutation.type === 'attributes' &&
435
+ mutation.target.matches('[data-fabric="wrapper"]') &&
436
+ (mutation.attributeName === 'style' ||
437
+ mutation.attributeName === 'class')
438
+ ) {
439
+ setTimeout(fixCanvasPositioning, 0);
440
+ }
441
+ });
442
+ });
443
+
444
+ // Start observing after a brief delay
445
+ setTimeout(() => {
446
+ const wrapper = document.querySelector('[data-fabric="wrapper"]');
447
+ if (wrapper) {
448
+ observer.observe(wrapper, {
449
+ attributes: true,
450
+ attributeFilter: ['style', 'class'],
451
+ });
452
+ }
453
+ }, 100);
454
+
455
+ // Text options are now dynamically set from UI controls
456
+
457
+ // Helper function to auto-detect text direction (same logic as overlay editor)
458
+ function autoDetectTextDirection(text) {
459
+ // Check for RTL characters (Arabic, Hebrew, etc.)
460
+ const rtlChars = /[\u0590-\u08FF\uFB1D-\uFDFF\uFE70-\uFEFF]/;
461
+ const ltrChars = /[A-Za-z]/;
462
+
463
+ const hasRtl = rtlChars.test(text);
464
+ const hasLtr = ltrChars.test(text);
465
+
466
+ if (hasRtl && !hasLtr) return 'rtl';
467
+ if (hasLtr && !hasRtl) return 'ltr';
468
+ if (hasRtl && hasLtr) {
469
+ // Mixed text - determine by first strong character
470
+ for (let char of text) {
471
+ if (rtlChars.test(char)) return 'rtl';
472
+ if (ltrChars.test(char)) return 'ltr';
473
+ }
474
+ }
475
+ return 'ltr'; // default
476
+ }
477
+
478
+ // Add text function (matching your editor)
479
+ function addText() {
480
+ const value =
481
+ document.getElementById('textValue').value || 'Sample Text';
482
+ let direction = document.getElementById('textDirection').value;
483
+ const textAlign = document.getElementById('textAlign').value;
484
+ const fontSize =
485
+ parseInt(document.getElementById('fontSize').value) || 32;
486
+ const fillColor = document.getElementById('fillColor').value;
487
+
488
+ // Get font properties from controls
489
+ const fontWeight =
490
+ document.getElementById('fontWeight').value || 'normal';
491
+ const fontStyle =
492
+ document.getElementById('fontStyle').value || 'normal';
493
+ const fontFamily =
494
+ document.getElementById('fontFamily').value || 'Arial';
495
+
496
+ // Auto-detect direction if user hasn't manually changed it and text looks RTL
497
+ const autoDetectedDirection = autoDetectTextDirection(value);
498
+ if (direction === 'ltr' && autoDetectedDirection === 'rtl') {
499
+ direction = 'rtl';
500
+ // Update the dropdown to reflect the auto-detected direction
501
+ document.getElementById('textDirection').value = 'rtl';
502
+ console.log('🔄 Auto-detected RTL text, switched direction to RTL');
503
+ }
504
+
505
+ const textbox = new fabric.Textbox(value, {
506
+ left: 200,
507
+ top: 200,
508
+ fill: fillColor,
509
+ fontSize: fontSize,
510
+ fontFamily: fontFamily,
511
+ fontWeight: fontWeight,
512
+ fontStyle: fontStyle,
513
+ lineHeight: 1.2,
514
+
515
+ // RTL/LTR configuration (now with auto-detection)
516
+ direction: direction,
517
+ textAlign: textAlign,
518
+
519
+ // ✅ Enable overlay editing (this handles most text editing)
520
+ useOverlayEditing: true,
521
+
522
+ // ✅ Keep these
523
+ lockMovementX: false,
524
+ lockMovementY: false,
525
+ });
526
+
527
+ canvas.add(textbox);
528
+ canvas.setActiveObject(textbox);
529
+ canvas.renderAll();
530
+ updateCanvasInfo();
531
+ }
532
+
533
+ // Add shapes functions
534
+ function addRectangle() {
535
+ const fillColor = document.getElementById('fillColor').value;
536
+ const strokeColor = document.getElementById('strokeColor').value;
537
+ const strokeWidth =
538
+ parseInt(document.getElementById('strokeWidth').value) || 2;
539
+ const cornerRadius =
540
+ parseInt(document.getElementById('cornerRadius').value) || 0;
541
+
542
+ const rect = new fabric.Rect({
543
+ left: 150,
544
+ top: 150,
545
+ width: 100,
546
+ height: 100,
547
+ fill: fillColor,
548
+ stroke: strokeColor,
549
+ strokeWidth: strokeWidth,
550
+ rx: cornerRadius,
551
+ ry: cornerRadius,
552
+ });
553
+
554
+ canvas.add(rect);
555
+ canvas.setActiveObject(rect);
556
+ canvas.renderAll();
557
+ updateCanvasInfo();
558
+ }
559
+
560
+ function addCircle() {
561
+ const fillColor = document.getElementById('fillColor').value;
562
+ const strokeColor = document.getElementById('strokeColor').value;
563
+ const strokeWidth =
564
+ parseInt(document.getElementById('strokeWidth').value) || 2;
565
+
566
+ const circle = new fabric.Circle({
567
+ left: 150,
568
+ top: 150,
569
+ radius: 50,
570
+ fill: fillColor,
571
+ stroke: strokeColor,
572
+ strokeWidth: strokeWidth,
573
+ });
574
+
575
+ canvas.add(circle);
576
+ canvas.setActiveObject(circle);
577
+ canvas.renderAll();
578
+ updateCanvasInfo();
579
+ }
580
+
581
+ function addTriangle() {
582
+ const fillColor = document.getElementById('fillColor').value;
583
+ const strokeColor = document.getElementById('strokeColor').value;
584
+ const strokeWidth =
585
+ parseInt(document.getElementById('strokeWidth').value) || 2;
586
+ const cornerRadius =
587
+ parseInt(document.getElementById('cornerRadius').value) || 0;
588
+
589
+ const triangle = new fabric.Triangle({
590
+ left: 150,
591
+ top: 150,
592
+ width: 100,
593
+ height: 100,
594
+ fill: fillColor,
595
+ stroke: strokeColor,
596
+ strokeWidth: strokeWidth,
597
+ cornerRadius: cornerRadius,
598
+ });
599
+
600
+ canvas.add(triangle);
601
+ canvas.setActiveObject(triangle);
602
+ canvas.renderAll();
603
+ updateCanvasInfo();
604
+ }
605
+
606
+ function addPolygon() {
607
+ const fillColor = document.getElementById('fillColor').value;
608
+ const strokeColor = document.getElementById('strokeColor').value;
609
+ const strokeWidth =
610
+ parseInt(document.getElementById('strokeWidth').value) || 2;
611
+ const cornerRadius =
612
+ parseInt(document.getElementById('cornerRadius').value) || 0;
613
+
614
+ // Create a hexagon
615
+ const points = [];
616
+ const sides = 6;
617
+ const radius = 50;
618
+ for (let i = 0; i < sides; i++) {
619
+ const angle = (i * 2 * Math.PI) / sides - Math.PI / 2;
620
+ points.push({
621
+ x: radius * Math.cos(angle),
622
+ y: radius * Math.sin(angle),
623
+ });
624
+ }
625
+
626
+ const polygon = new fabric.Polygon(points, {
627
+ left: 200,
628
+ top: 200,
629
+ fill: fillColor,
630
+ stroke: strokeColor,
631
+ strokeWidth: strokeWidth,
632
+ cornerRadius: cornerRadius,
633
+ });
634
+
635
+ canvas.add(polygon);
636
+ canvas.setActiveObject(polygon);
637
+ canvas.renderAll();
638
+ updateCanvasInfo();
639
+ }
640
+
641
+ // Text alignment function
642
+ function setTextAlign(alignment) {
643
+ const activeObject = canvas.getActiveObject();
644
+ if (
645
+ activeObject &&
646
+ (activeObject.type === 'textbox' ||
647
+ activeObject.type === 'text' ||
648
+ activeObject.type === 'i-text')
649
+ ) {
650
+ activeObject.set('textAlign', alignment);
651
+ canvas.renderAll();
652
+ updateCanvasInfo();
653
+ }
654
+ }
655
+
656
+ // Font control functions
657
+ function toggleBold() {
658
+ const activeObject = canvas.getActiveObject();
659
+ if (
660
+ activeObject &&
661
+ (activeObject.type === 'textbox' ||
662
+ activeObject.type === 'text' ||
663
+ activeObject.type === 'i-text')
664
+ ) {
665
+ const currentWeight = activeObject.fontWeight;
666
+ const newWeight =
667
+ currentWeight === 'bold' || currentWeight === '700'
668
+ ? 'normal'
669
+ : 'bold';
670
+ activeObject.set('fontWeight', newWeight);
671
+ // Update the dropdown to reflect the change
672
+ document.getElementById('fontWeight').value = newWeight;
673
+ canvas.renderAll();
674
+ updateCanvasInfo();
675
+ console.log('🔤 Font weight changed to:', newWeight);
676
+ }
677
+ }
678
+
679
+ function toggleItalic() {
680
+ const activeObject = canvas.getActiveObject();
681
+ if (
682
+ activeObject &&
683
+ (activeObject.type === 'textbox' ||
684
+ activeObject.type === 'text' ||
685
+ activeObject.type === 'i-text')
686
+ ) {
687
+ const currentStyle = activeObject.fontStyle;
688
+ const newStyle = currentStyle === 'italic' ? 'normal' : 'italic';
689
+ activeObject.set('fontStyle', newStyle);
690
+ // Update the dropdown to reflect the change
691
+ document.getElementById('fontStyle').value = newStyle;
692
+ canvas.renderAll();
693
+ updateCanvasInfo();
694
+ console.log('🔤 Font style changed to:', newStyle);
695
+ }
696
+ }
697
+
698
+ function changeFontFamily() {
699
+ const activeObject = canvas.getActiveObject();
700
+ if (
701
+ activeObject &&
702
+ (activeObject.type === 'textbox' ||
703
+ activeObject.type === 'text' ||
704
+ activeObject.type === 'i-text')
705
+ ) {
706
+ const selectedFamily = document.getElementById('fontFamily').value;
707
+ activeObject.set('fontFamily', selectedFamily);
708
+ canvas.renderAll();
709
+ updateCanvasInfo();
710
+ console.log('🔤 Font family changed to:', selectedFamily);
711
+ }
712
+ }
713
+
714
+ // Action functions
715
+ function deleteSelected() {
716
+ const activeObjects = canvas.getActiveObjects();
717
+ if (activeObjects.length) {
718
+ canvas.remove(...activeObjects);
719
+ canvas.discardActiveObject();
720
+ canvas.renderAll();
721
+ updateCanvasInfo();
722
+ }
723
+ }
724
+
725
+ function clearCanvas() {
726
+ canvas
727
+ .getObjects()
728
+ .filter((obj) => obj.name !== 'clip')
729
+ .forEach((obj) => canvas.remove(obj));
730
+ canvas.discardActiveObject();
731
+ canvas.renderAll();
732
+ updateCanvasInfo();
733
+ }
734
+
735
+ function exportJSON() {
736
+ const json = canvas.toJSON();
737
+ console.log('Canvas JSON:', json);
738
+
739
+ // Create downloadable JSON file
740
+ const dataStr = JSON.stringify(json, null, 2);
741
+ const dataBlob = new Blob([dataStr], { type: 'application/json' });
742
+ const url = URL.createObjectURL(dataBlob);
743
+ const link = document.createElement('a');
744
+ link.href = url;
745
+ link.download = 'canvas-export.json';
746
+ link.click();
747
+ URL.revokeObjectURL(url);
748
+ }
749
+
750
+ // Update canvas info
751
+ function updateCanvasInfo() {
752
+ const objects = canvas
753
+ .getObjects()
754
+ .filter((obj) => obj.name !== 'clip');
755
+ const activeObject = canvas.getActiveObject();
756
+
757
+ let selectedInfo = 'None';
758
+ let directionInfo = '';
759
+
760
+ if (activeObject) {
761
+ if (activeObject.type === 'activeSelection') {
762
+ selectedInfo = `${activeObject._objects.length} objects`;
763
+ } else {
764
+ selectedInfo = activeObject.type;
765
+
766
+ // Show direction info for text objects
767
+ if (
768
+ activeObject.type === 'textbox' ||
769
+ activeObject.type === 'text' ||
770
+ activeObject.type === 'i-text'
771
+ ) {
772
+ const direction = activeObject.direction || 'ltr';
773
+ const textAlign = activeObject.textAlign || 'left';
774
+ directionInfo = `<br>Direction: ${direction.toUpperCase()}<br>Align: ${textAlign}`;
775
+ }
776
+
777
+ // Sync stroke width slider with selected object
778
+ if (activeObject.strokeWidth !== undefined) {
779
+ strokeWidthSlider.value = activeObject.strokeWidth;
780
+ strokeWidthValue.textContent = activeObject.strokeWidth + 'px';
781
+ }
782
+ }
783
+ }
784
+
785
+ document.getElementById('canvasInfo').innerHTML = `
786
+ Objects: ${objects.length}<br>
787
+ Selected: ${selectedInfo}${directionInfo}
788
+ `;
789
+ }
790
+
791
+ // Event listeners for canvas changes
792
+ canvas.on('object:added', updateCanvasInfo);
793
+ canvas.on('object:removed', updateCanvasInfo);
794
+ canvas.on('selection:created', updateCanvasInfo);
795
+ canvas.on('selection:updated', updateCanvasInfo);
796
+ canvas.on('selection:cleared', updateCanvasInfo);
797
+
798
+ // Initial info update
799
+ updateCanvasInfo();
800
+
801
+ // Stroke width slider functionality
802
+ const strokeWidthSlider = document.getElementById('strokeWidth');
803
+ const strokeWidthValue = document.getElementById('strokeWidthValue');
804
+
805
+ // Update slider value display and selected object in real-time
806
+ strokeWidthSlider.addEventListener('input', function () {
807
+ const value = parseInt(this.value);
808
+ strokeWidthValue.textContent = value + 'px';
809
+
810
+ // Update stroke width of selected object(s) in real-time
811
+ const activeObjects = canvas.getActiveObjects();
812
+ if (activeObjects.length > 0) {
813
+ activeObjects.forEach((obj) => {
814
+ if (obj.stroke) {
815
+ obj.set('strokeWidth', value);
816
+ }
817
+ });
818
+ canvas.renderAll();
819
+ }
820
+ });
821
+
822
+ // Drawing mode functions
823
+ function enableDrawing() {
824
+ const strokeColor = document.getElementById('strokeColor').value;
825
+ const brushWidth = parseInt(document.getElementById('brushWidth').value) || 5;
826
+
827
+ console.log('🎨 Enabling drawing mode with:', { strokeColor, brushWidth });
828
+
829
+ // Ensure brush is properly initialized
830
+ if (!canvas.freeDrawingBrush) {
831
+ canvas.freeDrawingBrush = new fabric.PencilBrush(canvas);
832
+ }
833
+
834
+ canvas.freeDrawingBrush.width = brushWidth;
835
+ canvas.freeDrawingBrush.color = strokeColor;
836
+ canvas.isDrawingMode = true;
837
+
838
+ console.log('🎨 Drawing mode enabled');
839
+ updateCanvasInfo();
840
+ }
841
+
842
+ function disableDrawing() {
843
+ console.log('🎨 Disabling drawing mode');
844
+ canvas.isDrawingMode = false;
845
+ updateCanvasInfo();
846
+ }
847
+
848
+ // Brush width slider functionality
849
+ const brushWidthSlider = document.getElementById('brushWidth');
850
+ const brushWidthValue = document.getElementById('brushWidthValue');
851
+
852
+ brushWidthSlider.addEventListener('input', function () {
853
+ const value = parseInt(this.value);
854
+ brushWidthValue.textContent = value + 'px';
855
+
856
+ console.log('🎨 Brush width changed to:', value, 'Canvas objects before:', canvas.getObjects().length);
857
+
858
+ // Update brush width in real-time
859
+ if (canvas.freeDrawingBrush) {
860
+ canvas.freeDrawingBrush.width = value;
861
+ console.log('🎨 Set brush width to:', value);
862
+ }
863
+
864
+ console.log('🎨 Canvas objects after setting brush width:', canvas.getObjects().length);
865
+ });
866
+
867
+ // Corner radius slider functionality
868
+ const cornerRadiusSlider = document.getElementById('cornerRadius');
869
+ const cornerRadiusValue = document.getElementById('cornerRadiusValue');
870
+
871
+ cornerRadiusSlider.addEventListener('input', function () {
872
+ const value = parseInt(this.value);
873
+ cornerRadiusValue.textContent = value + 'px';
874
+
875
+ // Update corner radius of selected object(s) in real-time
876
+ const activeObjects = canvas.getActiveObjects();
877
+ if (activeObjects.length > 0) {
878
+ activeObjects.forEach((obj) => {
879
+ if (obj.type === 'triangle' || obj.type === 'polygon' || obj.type === 'polyline') {
880
+ obj.set('cornerRadius', value);
881
+ } else if (obj.type === 'rect') {
882
+ // For rectangles, use rx and ry properties
883
+ obj.set('rx', value);
884
+ obj.set('ry', value);
885
+ }
886
+ });
887
+ canvas.renderAll();
888
+ }
889
+ });
890
+
891
+ // Test function to demonstrate corner radius on all shapes
892
+ function testCornerRadius() {
893
+ // Clear canvas first
894
+ clearCanvas();
895
+
896
+ // Create different shapes with corner radius
897
+ const cornerRadius = 15;
898
+ const fillColor = '#3b82f6';
899
+ const strokeColor = '#1d4ed8';
900
+ const strokeWidth = 2;
901
+
902
+ // Rounded Rectangle
903
+ const rect = new fabric.Rect({
904
+ left: 150,
905
+ top: 100,
906
+ width: 80,
907
+ height: 60,
908
+ fill: fillColor,
909
+ stroke: strokeColor,
910
+ strokeWidth: strokeWidth,
911
+ rx: cornerRadius,
912
+ ry: cornerRadius,
913
+ });
914
+
915
+ // Rounded Triangle
916
+ const triangle = new fabric.Triangle({
917
+ left: 150,
918
+ top: 150,
919
+ width: 80,
920
+ height: 80,
921
+ fill: fillColor,
922
+ stroke: strokeColor,
923
+ strokeWidth: strokeWidth,
924
+ cornerRadius: cornerRadius,
925
+ });
926
+
927
+ // Rounded Hexagon
928
+ const hexPoints = [];
929
+ const sides = 6;
930
+ const radius = 40;
931
+ for (let i = 0; i < sides; i++) {
932
+ const angle = (i * 2 * Math.PI) / sides - Math.PI / 2;
933
+ hexPoints.push({
934
+ x: radius * Math.cos(angle),
935
+ y: radius * Math.sin(angle),
936
+ });
937
+ }
938
+
939
+ const hexagon = new fabric.Polygon(hexPoints, {
940
+ left: 300,
941
+ top: 150,
942
+ fill: '#10b981',
943
+ stroke: '#047857',
944
+ strokeWidth: strokeWidth,
945
+ cornerRadius: cornerRadius,
946
+ });
947
+
948
+ // Rounded Pentagon
949
+ const pentPoints = [];
950
+ const pentSides = 5;
951
+ const pentRadius = 40;
952
+ for (let i = 0; i < pentSides; i++) {
953
+ const angle = (i * 2 * Math.PI) / pentSides - Math.PI / 2;
954
+ pentPoints.push({
955
+ x: pentRadius * Math.cos(angle),
956
+ y: pentRadius * Math.sin(angle),
957
+ });
958
+ }
959
+
960
+ const pentagon = new fabric.Polygon(pentPoints, {
961
+ left: 450,
962
+ top: 150,
963
+ fill: '#f59e0b',
964
+ stroke: '#d97706',
965
+ strokeWidth: strokeWidth,
966
+ cornerRadius: cornerRadius,
967
+ });
968
+
969
+ // Rounded Star
970
+ const starPoints = [];
971
+ const outerRadius = 40;
972
+ const innerRadius = 20;
973
+ const starSides = 5;
974
+ for (let i = 0; i < starSides * 2; i++) {
975
+ const angle = (i * Math.PI) / starSides - Math.PI / 2;
976
+ const radius = i % 2 === 0 ? outerRadius : innerRadius;
977
+ starPoints.push({
978
+ x: radius * Math.cos(angle),
979
+ y: radius * Math.sin(angle),
980
+ });
981
+ }
982
+
983
+ const star = new fabric.Polygon(starPoints, {
984
+ left: 150,
985
+ top: 300,
986
+ fill: '#ef4444',
987
+ stroke: '#dc2626',
988
+ strokeWidth: strokeWidth,
989
+ cornerRadius: cornerRadius,
990
+ });
991
+
992
+ // Rounded Arrow
993
+ const arrowPoints = [
994
+ { x: 0, y: -20 },
995
+ { x: 30, y: -20 },
996
+ { x: 30, y: -40 },
997
+ { x: 60, y: 0 },
998
+ { x: 30, y: 40 },
999
+ { x: 30, y: 20 },
1000
+ { x: 0, y: 20 },
1001
+ ];
1002
+
1003
+ const arrow = new fabric.Polygon(arrowPoints, {
1004
+ left: 350,
1005
+ top: 300,
1006
+ fill: '#8b5cf6',
1007
+ stroke: '#7c3aed',
1008
+ strokeWidth: strokeWidth,
1009
+ cornerRadius: cornerRadius,
1010
+ });
1011
+
1012
+ // Add all shapes to canvas
1013
+ canvas.add(rect, triangle, hexagon, pentagon, star, arrow);
1014
+
1015
+ // Add text label
1016
+ const label = new fabric.Textbox('Corner Radius Demo - All shapes support rounded corners like Canva!', {
1017
+ left: 150,
1018
+ top: 400,
1019
+ fontSize: 16,
1020
+ fontFamily: 'Arial',
1021
+ fill: '#374151',
1022
+ textAlign: 'center',
1023
+ width: 400,
1024
+ });
1025
+
1026
+ canvas.add(label);
1027
+ canvas.renderAll();
1028
+ updateCanvasInfo();
1029
+
1030
+ console.log('🎨 Corner radius demo created! All shapes now support corner radius like Canva.');
1031
+ }
1032
+
1033
+ // Log Fabric.js version
1034
+ console.log('Fabric.js version:', fabric.version);
1035
+ console.log('Canvas initialized with useOverlayEditing support');
1036
+ console.log('🎨 Corner radius support added for all shapes (Triangle, Polygon, etc.)');
1037
+
1038
+ // Test overlay editing on double-click
1039
+ canvas.on('mouse:dblclick', function (options) {
1040
+ if (options.target && options.target.type === 'textbox') {
1041
+ console.log(
1042
+ 'Double-clicked textbox - overlay editing should activate',
1043
+ );
1044
+ }
1045
+ });
1046
+ </script>
1047
+ </body>
1048
+ </html>