@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.
- package/dist/index.js +504 -102
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/index.min.mjs +1 -1
- package/dist/index.min.mjs.map +1 -1
- package/dist/index.mjs +504 -102
- package/dist/index.mjs.map +1 -1
- package/dist/index.node.cjs +504 -102
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.mjs +504 -102
- package/dist/index.node.mjs.map +1 -1
- package/dist/package.json.min.mjs +1 -1
- package/dist/package.json.mjs +1 -1
- package/dist/src/shapes/Polyline.d.ts +7 -0
- package/dist/src/shapes/Polyline.d.ts.map +1 -1
- package/dist/src/shapes/Polyline.min.mjs +1 -1
- package/dist/src/shapes/Polyline.min.mjs.map +1 -1
- package/dist/src/shapes/Polyline.mjs +48 -16
- package/dist/src/shapes/Polyline.mjs.map +1 -1
- package/dist/src/shapes/Text/Text.d.ts.map +1 -1
- package/dist/src/shapes/Text/Text.min.mjs +1 -1
- package/dist/src/shapes/Text/Text.min.mjs.map +1 -1
- package/dist/src/shapes/Text/Text.mjs +64 -12
- package/dist/src/shapes/Text/Text.mjs.map +1 -1
- package/dist/src/shapes/Textbox.d.ts.map +1 -1
- package/dist/src/shapes/Textbox.min.mjs +1 -1
- package/dist/src/shapes/Textbox.min.mjs.map +1 -1
- package/dist/src/shapes/Textbox.mjs +2 -52
- package/dist/src/shapes/Textbox.mjs.map +1 -1
- package/dist/src/shapes/Triangle.d.ts +27 -2
- package/dist/src/shapes/Triangle.d.ts.map +1 -1
- package/dist/src/shapes/Triangle.min.mjs +1 -1
- package/dist/src/shapes/Triangle.min.mjs.map +1 -1
- package/dist/src/shapes/Triangle.mjs +72 -12
- package/dist/src/shapes/Triangle.mjs.map +1 -1
- package/dist/src/text/overlayEditor.d.ts.map +1 -1
- package/dist/src/text/overlayEditor.min.mjs +1 -1
- package/dist/src/text/overlayEditor.min.mjs.map +1 -1
- package/dist/src/text/overlayEditor.mjs +143 -9
- package/dist/src/text/overlayEditor.mjs.map +1 -1
- package/dist/src/util/misc/cornerRadius.d.ts +70 -0
- package/dist/src/util/misc/cornerRadius.d.ts.map +1 -0
- package/dist/src/util/misc/cornerRadius.min.mjs +2 -0
- package/dist/src/util/misc/cornerRadius.min.mjs.map +1 -0
- package/dist/src/util/misc/cornerRadius.mjs +181 -0
- package/dist/src/util/misc/cornerRadius.mjs.map +1 -0
- package/dist-extensions/src/shapes/Polyline.d.ts +7 -0
- package/dist-extensions/src/shapes/Polyline.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Text/Text.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Textbox.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Triangle.d.ts +27 -2
- package/dist-extensions/src/shapes/Triangle.d.ts.map +1 -1
- package/dist-extensions/src/text/overlayEditor.d.ts.map +1 -1
- package/dist-extensions/src/util/misc/cornerRadius.d.ts +70 -0
- package/dist-extensions/src/util/misc/cornerRadius.d.ts.map +1 -0
- package/fabric-test-editor.html +1048 -0
- package/package.json +164 -164
- package/src/shapes/Polyline.ts +70 -29
- package/src/shapes/Text/Text.ts +79 -14
- package/src/shapes/Textbox.ts +1 -1
- package/src/shapes/Triangle.spec.ts +76 -0
- package/src/shapes/Triangle.ts +85 -15
- package/src/text/overlayEditor.ts +152 -12
- package/src/util/misc/cornerRadius.spec.ts +141 -0
- 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>
|