@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.
- package/0 +0 -0
- package/debug/{konva → konva-master}/CHANGELOG.md +2 -1
- package/debug/{konva → konva-master}/README.md +7 -3
- package/debug/{konva → konva-master}/package.json +1 -1
- package/debug/{konva → konva-master}/release.sh +1 -4
- package/debug/{konva → konva-master}/src/Canvas.ts +37 -0
- package/debug/{konva → konva-master}/src/shapes/Text.ts +2 -2
- package/dist/index.js +1853 -288
- 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 +1853 -288
- package/dist/index.mjs.map +1 -1
- package/dist/index.node.cjs +1853 -288
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.mjs +1853 -288
- 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/Line.d.ts +33 -86
- package/dist/src/shapes/Line.d.ts.map +1 -1
- package/dist/src/shapes/Line.min.mjs +1 -1
- package/dist/src/shapes/Line.min.mjs.map +1 -1
- package/dist/src/shapes/Line.mjs +405 -159
- package/dist/src/shapes/Line.mjs.map +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 +19 -0
- 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 +302 -16
- package/dist/src/shapes/Text/Text.mjs.map +1 -1
- package/dist/src/shapes/Textbox.d.ts +43 -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 +521 -67
- 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/examples/arabicTextExample.d.ts +60 -0
- package/dist/src/text/examples/arabicTextExample.d.ts.map +1 -0
- package/dist/src/text/measure.d.ts +9 -0
- package/dist/src/text/measure.d.ts.map +1 -1
- package/dist/src/text/measure.min.mjs +1 -1
- package/dist/src/text/measure.min.mjs.map +1 -1
- package/dist/src/text/measure.mjs +175 -4
- package/dist/src/text/measure.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 +155 -9
- package/dist/src/text/overlayEditor.mjs.map +1 -1
- package/dist/src/text/scriptUtils.d.ts +142 -0
- package/dist/src/text/scriptUtils.d.ts.map +1 -0
- package/dist/src/text/scriptUtils.min.mjs +2 -0
- package/dist/src/text/scriptUtils.min.mjs.map +1 -0
- package/dist/src/text/scriptUtils.mjs +212 -0
- package/dist/src/text/scriptUtils.mjs.map +1 -0
- 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/CustomLine.d.ts +10 -0
- package/dist-extensions/src/shapes/CustomLine.d.ts.map +1 -0
- package/dist-extensions/src/shapes/Line.d.ts +33 -86
- package/dist-extensions/src/shapes/Line.d.ts.map +1 -1
- 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 +19 -0
- package/dist-extensions/src/shapes/Text/Text.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Textbox.d.ts +43 -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/measure.d.ts +9 -0
- package/dist-extensions/src/text/measure.d.ts.map +1 -1
- package/dist-extensions/src/text/overlayEditor.d.ts.map +1 -1
- package/dist-extensions/src/text/scriptUtils.d.ts +142 -0
- package/dist-extensions/src/text/scriptUtils.d.ts.map +1 -0
- 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 +3552 -0
- package/fabric-test2.html +647 -0
- package/fabric.ts +182 -182
- package/fonts/STV Bold.ttf +0 -0
- package/fonts/STV Light.ttf +0 -0
- package/fonts/STV Regular.ttf +0 -0
- package/package.json +164 -164
- package/src/shapes/Line.ts +484 -157
- package/src/shapes/Polyline.ts +70 -29
- package/src/shapes/Text/Text.ts +317 -19
- package/src/shapes/Textbox.ts +544 -12
- package/src/shapes/Triangle.spec.ts +76 -0
- package/src/shapes/Triangle.ts +85 -15
- package/src/text/measure.ts +200 -50
- package/src/text/overlayEditor.ts +164 -12
- package/src/util/misc/cornerRadius.spec.ts +141 -0
- package/src/util/misc/cornerRadius.ts +269 -0
- /package/debug/{konva → konva-master}/LICENSE +0 -0
- /package/debug/{konva → konva-master}/gulpfile.mjs +0 -0
- /package/debug/{konva → konva-master}/resources/doc-includes/ContainerParams.txt +0 -0
- /package/debug/{konva → konva-master}/resources/doc-includes/NodeParams.txt +0 -0
- /package/debug/{konva → konva-master}/resources/doc-includes/ShapeParams.txt +0 -0
- /package/debug/{konva → konva-master}/resources/jsdoc.conf.json +0 -0
- /package/debug/{konva → konva-master}/rollup.config.mjs +0 -0
- /package/debug/{konva → konva-master}/src/Animation.ts +0 -0
- /package/debug/{konva → konva-master}/src/BezierFunctions.ts +0 -0
- /package/debug/{konva → konva-master}/src/Container.ts +0 -0
- /package/debug/{konva → konva-master}/src/Context.ts +0 -0
- /package/debug/{konva → konva-master}/src/Core.ts +0 -0
- /package/debug/{konva → konva-master}/src/DragAndDrop.ts +0 -0
- /package/debug/{konva → konva-master}/src/Factory.ts +0 -0
- /package/debug/{konva → konva-master}/src/FastLayer.ts +0 -0
- /package/debug/{konva → konva-master}/src/Global.ts +0 -0
- /package/debug/{konva → konva-master}/src/Group.ts +0 -0
- /package/debug/{konva → konva-master}/src/Layer.ts +0 -0
- /package/debug/{konva → konva-master}/src/Node.ts +0 -0
- /package/debug/{konva → konva-master}/src/PointerEvents.ts +0 -0
- /package/debug/{konva → konva-master}/src/Shape.ts +0 -0
- /package/debug/{konva → konva-master}/src/Stage.ts +0 -0
- /package/debug/{konva → konva-master}/src/Tween.ts +0 -0
- /package/debug/{konva → konva-master}/src/Util.ts +0 -0
- /package/debug/{konva → konva-master}/src/Validators.ts +0 -0
- /package/debug/{konva → konva-master}/src/_CoreInternals.ts +0 -0
- /package/debug/{konva → konva-master}/src/_FullInternals.ts +0 -0
- /package/debug/{konva → konva-master}/src/canvas-backend.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Blur.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Brighten.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Brightness.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Contrast.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Emboss.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Enhance.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Grayscale.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/HSL.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/HSV.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Invert.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Kaleidoscope.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Mask.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Noise.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Pixelate.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Posterize.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/RGB.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/RGBA.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Sepia.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Solarize.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Threshold.ts +0 -0
- /package/debug/{konva → konva-master}/src/index.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Arc.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Arrow.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Circle.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Ellipse.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Image.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Label.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Line.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Path.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Rect.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/RegularPolygon.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Ring.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Sprite.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Star.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/TextPath.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Transformer.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Wedge.ts +0 -0
- /package/debug/{konva → konva-master}/src/skia-backend.ts +0 -0
- /package/debug/{konva → konva-master}/src/types.ts +0 -0
- /package/debug/{konva → konva-master}/tsconfig.json +0 -0
- /package/debug/{konva → konva-master}/tsconfig.test.json +0 -0
|
@@ -0,0 +1,3552 @@
|
|
|
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
|
+
/* Custom Fonts */
|
|
10
|
+
@font-face {
|
|
11
|
+
font-family: 'STV Bold';
|
|
12
|
+
src: url('./fonts/STV Bold.ttf') format('truetype');
|
|
13
|
+
font-weight: bold;
|
|
14
|
+
font-style: normal;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@font-face {
|
|
18
|
+
font-family: 'STV Light';
|
|
19
|
+
src: url('./fonts/STV Light.ttf') format('truetype');
|
|
20
|
+
font-weight: 300;
|
|
21
|
+
font-style: normal;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@font-face {
|
|
25
|
+
font-family: 'STV Regular';
|
|
26
|
+
src: url('./fonts/STV Regular.ttf') format('truetype');
|
|
27
|
+
font-weight: normal;
|
|
28
|
+
font-style: normal;
|
|
29
|
+
}
|
|
30
|
+
* {
|
|
31
|
+
margin: 0;
|
|
32
|
+
padding: 0;
|
|
33
|
+
box-sizing: border-box;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
body {
|
|
37
|
+
font-family:
|
|
38
|
+
-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
|
|
39
|
+
background-color: #f5f5f5;
|
|
40
|
+
display: flex;
|
|
41
|
+
height: 100vh;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.sidebar {
|
|
45
|
+
width: 300px;
|
|
46
|
+
background: white;
|
|
47
|
+
border-right: 1px solid #e5e7eb;
|
|
48
|
+
padding: 20px;
|
|
49
|
+
overflow-y: auto;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.canvas-container {
|
|
53
|
+
flex: 1;
|
|
54
|
+
display: flex;
|
|
55
|
+
align-items: center;
|
|
56
|
+
justify-content: center;
|
|
57
|
+
padding: 20px;
|
|
58
|
+
position: relative;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
#canvas {
|
|
62
|
+
border: 1px solid #d1d5db;
|
|
63
|
+
border-radius: 8px;
|
|
64
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* Fix for Fabric.js canvas alignment and centering */
|
|
68
|
+
.canvas-container [data-fabric='wrapper'] {
|
|
69
|
+
position: relative !important;
|
|
70
|
+
margin: 0 auto !important;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.canvas-container [data-fabric='wrapper'] canvas {
|
|
74
|
+
left: 0 !important;
|
|
75
|
+
top: 0 !important;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.control-group {
|
|
79
|
+
margin-bottom: 20px;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.control-group h3 {
|
|
83
|
+
margin-bottom: 10px;
|
|
84
|
+
color: #374151;
|
|
85
|
+
font-size: 14px;
|
|
86
|
+
font-weight: 600;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
button {
|
|
90
|
+
width: 100%;
|
|
91
|
+
margin-bottom: 8px;
|
|
92
|
+
padding: 10px 16px;
|
|
93
|
+
background: #3b82f6;
|
|
94
|
+
color: white;
|
|
95
|
+
border: none;
|
|
96
|
+
border-radius: 6px;
|
|
97
|
+
cursor: pointer;
|
|
98
|
+
font-size: 14px;
|
|
99
|
+
font-weight: 500;
|
|
100
|
+
transition: background-color 0.2s;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
button:hover {
|
|
104
|
+
background: #2563eb;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
button.secondary {
|
|
108
|
+
background: #6b7280;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
button.secondary:hover {
|
|
112
|
+
background: #4b5563;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
input,
|
|
116
|
+
select {
|
|
117
|
+
width: 100%;
|
|
118
|
+
margin-bottom: 8px;
|
|
119
|
+
padding: 8px 12px;
|
|
120
|
+
border: 1px solid #d1d5db;
|
|
121
|
+
border-radius: 6px;
|
|
122
|
+
font-size: 14px;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.color-input {
|
|
126
|
+
height: 40px;
|
|
127
|
+
cursor: pointer;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.info {
|
|
131
|
+
background: #f3f4f6;
|
|
132
|
+
padding: 12px;
|
|
133
|
+
border-radius: 6px;
|
|
134
|
+
font-size: 12px;
|
|
135
|
+
color: #4b5563;
|
|
136
|
+
margin-bottom: 20px;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.workspace {
|
|
140
|
+
background: white;
|
|
141
|
+
border: 2px solid #e5e7eb;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* Slider styling */
|
|
145
|
+
.slider-container {
|
|
146
|
+
margin-bottom: 8px;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.slider-container label {
|
|
150
|
+
display: block;
|
|
151
|
+
font-size: 12px;
|
|
152
|
+
color: #6b7280;
|
|
153
|
+
margin-bottom: 4px;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
input[type='range'] {
|
|
157
|
+
width: 100%;
|
|
158
|
+
height: 6px;
|
|
159
|
+
border-radius: 3px;
|
|
160
|
+
background: #e5e7eb;
|
|
161
|
+
outline: none;
|
|
162
|
+
-webkit-appearance: none;
|
|
163
|
+
appearance: none;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
input[type='range']::-webkit-slider-thumb {
|
|
167
|
+
-webkit-appearance: none;
|
|
168
|
+
appearance: none;
|
|
169
|
+
width: 18px;
|
|
170
|
+
height: 18px;
|
|
171
|
+
border-radius: 50%;
|
|
172
|
+
background: #3b82f6;
|
|
173
|
+
cursor: pointer;
|
|
174
|
+
border: 2px solid #ffffff;
|
|
175
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
input[type='range']::-webkit-slider-thumb:hover {
|
|
179
|
+
background: #2563eb;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
input[type='range']::-moz-range-thumb {
|
|
183
|
+
width: 18px;
|
|
184
|
+
height: 18px;
|
|
185
|
+
border-radius: 50%;
|
|
186
|
+
background: #3b82f6;
|
|
187
|
+
cursor: pointer;
|
|
188
|
+
border: 2px solid #ffffff;
|
|
189
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.slider-value {
|
|
193
|
+
font-size: 12px;
|
|
194
|
+
color: #374151;
|
|
195
|
+
font-weight: 500;
|
|
196
|
+
text-align: center;
|
|
197
|
+
margin-top: 4px;
|
|
198
|
+
}
|
|
199
|
+
</style>
|
|
200
|
+
</head>
|
|
201
|
+
<body>
|
|
202
|
+
<div class="sidebar">
|
|
203
|
+
<div class="info">
|
|
204
|
+
<strong>Fabric.js 7 Beta Test Editor</strong><br />
|
|
205
|
+
Test your Fabric.js changes here with useOverlayEditing enabled.
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
<div class="control-group">
|
|
209
|
+
<h3>Add Objects</h3>
|
|
210
|
+
<button onclick="addText()">Add Text</button>
|
|
211
|
+
<button onclick="addRectangle()">Add Rectangle</button>
|
|
212
|
+
<button onclick="addCircle()">Add Circle</button>
|
|
213
|
+
<button onclick="addTriangle()">Add Triangle</button>
|
|
214
|
+
<button onclick="addPolygon()">Add Polygon</button>
|
|
215
|
+
<button onclick="addLine()">Add Line</button>
|
|
216
|
+
</div>
|
|
217
|
+
|
|
218
|
+
<div class="control-group">
|
|
219
|
+
<h3>Text Options</h3>
|
|
220
|
+
<input
|
|
221
|
+
type="text"
|
|
222
|
+
id="textValue"
|
|
223
|
+
placeholder="Enter text..."
|
|
224
|
+
value="Sample Text"
|
|
225
|
+
/>
|
|
226
|
+
<select id="textDirection">
|
|
227
|
+
<option value="ltr">LTR (Left to Right)</option>
|
|
228
|
+
<option value="rtl">RTL (Right to Left)</option>
|
|
229
|
+
</select>
|
|
230
|
+
<select id="textAlign">
|
|
231
|
+
<option value="left">Left</option>
|
|
232
|
+
<option value="center">Center</option>
|
|
233
|
+
<option value="right">Right</option>
|
|
234
|
+
<option value="justify">Justify</option>
|
|
235
|
+
<option value="justify-left">Justify Left</option>
|
|
236
|
+
<option value="justify-center">Justify Center</option>
|
|
237
|
+
<option value="justify-right">Justify Right</option>
|
|
238
|
+
</select>
|
|
239
|
+
<input
|
|
240
|
+
type="number"
|
|
241
|
+
id="fontSize"
|
|
242
|
+
placeholder="Font Size"
|
|
243
|
+
value="32"
|
|
244
|
+
min="8"
|
|
245
|
+
max="200"
|
|
246
|
+
/>
|
|
247
|
+
</div>
|
|
248
|
+
|
|
249
|
+
<div class="control-group">
|
|
250
|
+
<h3>Font Style</h3>
|
|
251
|
+
<select id="fontWeight">
|
|
252
|
+
<option value="normal">Normal</option>
|
|
253
|
+
<option value="bold">Bold</option>
|
|
254
|
+
<option value="100">100 - Thin</option>
|
|
255
|
+
<option value="200">200 - Extra Light</option>
|
|
256
|
+
<option value="300">300 - Light</option>
|
|
257
|
+
<option value="400">400 - Normal</option>
|
|
258
|
+
<option value="500">500 - Medium</option>
|
|
259
|
+
<option value="600">600 - Semi Bold</option>
|
|
260
|
+
<option value="700">700 - Bold</option>
|
|
261
|
+
<option value="800">800 - Extra Bold</option>
|
|
262
|
+
<option value="900">900 - Black</option>
|
|
263
|
+
</select>
|
|
264
|
+
<select id="fontStyle">
|
|
265
|
+
<option value="normal">Normal</option>
|
|
266
|
+
<option value="italic">Italic</option>
|
|
267
|
+
<option value="oblique">Oblique</option>
|
|
268
|
+
</select>
|
|
269
|
+
<select id="fontFamily">
|
|
270
|
+
<option value="Arial">Arial</option>
|
|
271
|
+
<option value="Georgia">Georgia</option>
|
|
272
|
+
<option value="Times New Roman">Times New Roman</option>
|
|
273
|
+
<option value="Courier New">Courier New</option>
|
|
274
|
+
<option value="Helvetica">Helvetica</option>
|
|
275
|
+
<option value="Verdana">Verdana</option>
|
|
276
|
+
<option value="STV Bold">STV Bold</option>
|
|
277
|
+
<option value="STV Light">STV Light</option>
|
|
278
|
+
<option value="STV Regular">STV Regular</option>
|
|
279
|
+
</select>
|
|
280
|
+
</div>
|
|
281
|
+
|
|
282
|
+
<div class="control-group">
|
|
283
|
+
<h3>Colors</h3>
|
|
284
|
+
<input
|
|
285
|
+
type="color"
|
|
286
|
+
id="fillColor"
|
|
287
|
+
class="color-input"
|
|
288
|
+
value="#000000"
|
|
289
|
+
/>
|
|
290
|
+
<label for="fillColor" style="font-size: 12px; color: #6b7280"
|
|
291
|
+
>Fill Color</label
|
|
292
|
+
>
|
|
293
|
+
<input
|
|
294
|
+
type="color"
|
|
295
|
+
id="strokeColor"
|
|
296
|
+
class="color-input"
|
|
297
|
+
value="#000000"
|
|
298
|
+
/>
|
|
299
|
+
<label for="strokeColor" style="font-size: 12px; color: #6b7280"
|
|
300
|
+
>Stroke Color</label
|
|
301
|
+
>
|
|
302
|
+
<div class="slider-container">
|
|
303
|
+
<label for="strokeWidth">Stroke Width</label>
|
|
304
|
+
<input
|
|
305
|
+
type="range"
|
|
306
|
+
id="strokeWidth"
|
|
307
|
+
min="0"
|
|
308
|
+
max="20"
|
|
309
|
+
value="2"
|
|
310
|
+
step="1"
|
|
311
|
+
/>
|
|
312
|
+
<div class="slider-value" id="strokeWidthValue">2px</div>
|
|
313
|
+
</div>
|
|
314
|
+
</div>
|
|
315
|
+
|
|
316
|
+
<div class="control-group">
|
|
317
|
+
<h3>Corner Radius (Canva-style)</h3>
|
|
318
|
+
<div class="slider-container">
|
|
319
|
+
<label for="cornerRadius">Corner Radius</label>
|
|
320
|
+
<input
|
|
321
|
+
type="range"
|
|
322
|
+
id="cornerRadius"
|
|
323
|
+
min="0"
|
|
324
|
+
max="50"
|
|
325
|
+
value="0"
|
|
326
|
+
step="1"
|
|
327
|
+
/>
|
|
328
|
+
<div class="slider-value" id="cornerRadiusValue">0px</div>
|
|
329
|
+
</div>
|
|
330
|
+
<button onclick="testCornerRadius()">Test All Corner Radius</button>
|
|
331
|
+
<button onclick="testCustomFontBounds()">
|
|
332
|
+
Test Custom Font Bounds
|
|
333
|
+
</button>
|
|
334
|
+
</div>
|
|
335
|
+
<div class="control-group">
|
|
336
|
+
<h3>Line Properties</h3>
|
|
337
|
+
<div style="margin-bottom: 10px">
|
|
338
|
+
<label
|
|
339
|
+
for="roundedEndpoints"
|
|
340
|
+
style="display: flex; align-items: center; cursor: pointer"
|
|
341
|
+
>
|
|
342
|
+
<input
|
|
343
|
+
type="checkbox"
|
|
344
|
+
id="roundedEndpoints"
|
|
345
|
+
style="margin-right: 8px"
|
|
346
|
+
/>
|
|
347
|
+
Rounded Endpoints (Line Caps)
|
|
348
|
+
</label>
|
|
349
|
+
</div>
|
|
350
|
+
</div>
|
|
351
|
+
|
|
352
|
+
<div class="control-group">
|
|
353
|
+
<h3>Drawing Mode</h3>
|
|
354
|
+
<button id="enableDrawing" onclick="enableDrawing()">
|
|
355
|
+
Enable Drawing
|
|
356
|
+
</button>
|
|
357
|
+
<button id="disableDrawing" onclick="disableDrawing()">
|
|
358
|
+
Disable Drawing
|
|
359
|
+
</button>
|
|
360
|
+
<div class="slider-container">
|
|
361
|
+
<label for="brushWidth">Brush Width</label>
|
|
362
|
+
<input
|
|
363
|
+
type="range"
|
|
364
|
+
id="brushWidth"
|
|
365
|
+
min="1"
|
|
366
|
+
max="50"
|
|
367
|
+
value="5"
|
|
368
|
+
step="1"
|
|
369
|
+
/>
|
|
370
|
+
<div class="slider-value" id="brushWidthValue">5px</div>
|
|
371
|
+
</div>
|
|
372
|
+
</div>
|
|
373
|
+
|
|
374
|
+
<div class="control-group">
|
|
375
|
+
<h3>Text Alignment</h3>
|
|
376
|
+
<button onclick="setTextAlign('left')">Align Left</button>
|
|
377
|
+
<button onclick="setTextAlign('center')">Align Center</button>
|
|
378
|
+
<button onclick="setTextAlign('right')">Align Right</button>
|
|
379
|
+
<button onclick="setTextAlign('justify')">Justify</button>
|
|
380
|
+
<button onclick="setTextAlign('justify-left')">Justify Left</button>
|
|
381
|
+
<button onclick="setTextAlign('justify-center')">Justify Center</button>
|
|
382
|
+
<button onclick="setTextAlign('justify-right')">Justify Right</button>
|
|
383
|
+
</div>
|
|
384
|
+
|
|
385
|
+
<div class="control-group">
|
|
386
|
+
<h3>Font Controls</h3>
|
|
387
|
+
<button onclick="toggleBold()">Toggle Bold</button>
|
|
388
|
+
<button onclick="toggleItalic()">Toggle Italic</button>
|
|
389
|
+
<button onclick="changeFontFamily()">Change Font</button>
|
|
390
|
+
</div>
|
|
391
|
+
|
|
392
|
+
<div class="control-group">
|
|
393
|
+
<h3>Object Alignment</h3>
|
|
394
|
+
<button onclick="centerObject()">Center</button>
|
|
395
|
+
<button onclick="centerObjectH()">Center Horizontally</button>
|
|
396
|
+
<button onclick="centerObjectV()">Center Vertically</button>
|
|
397
|
+
<button onclick="alignLeft()">Align Left</button>
|
|
398
|
+
<button onclick="alignRight()">Align Right</button>
|
|
399
|
+
<button onclick="alignTop()">Align Top</button>
|
|
400
|
+
<button onclick="alignBottom()">Align Bottom</button>
|
|
401
|
+
</div>
|
|
402
|
+
|
|
403
|
+
<div class="control-group">
|
|
404
|
+
<h3>Actions</h3>
|
|
405
|
+
<button onclick="deleteSelected()" class="secondary">
|
|
406
|
+
Delete Selected
|
|
407
|
+
</button>
|
|
408
|
+
<button onclick="clearCanvas()" class="secondary">Clear Canvas</button>
|
|
409
|
+
<button onclick="exportJSON()" class="secondary">Export JSON</button>
|
|
410
|
+
<button onclick="importJSON()" class="secondary">Import JSON</button>
|
|
411
|
+
</div>
|
|
412
|
+
|
|
413
|
+
<div class="control-group">
|
|
414
|
+
<h3>Canvas Zoom</h3>
|
|
415
|
+
<div class="slider-container">
|
|
416
|
+
<label for="canvasZoom">Zoom Level</label>
|
|
417
|
+
<input
|
|
418
|
+
type="range"
|
|
419
|
+
id="canvasZoom"
|
|
420
|
+
min="0.1"
|
|
421
|
+
max="3"
|
|
422
|
+
value="1"
|
|
423
|
+
step="0.1"
|
|
424
|
+
/>
|
|
425
|
+
<div class="slider-value" id="canvasZoomValue">100%</div>
|
|
426
|
+
</div>
|
|
427
|
+
<button onclick="resetZoom()">Reset Zoom (100%)</button>
|
|
428
|
+
<button onclick="fitToWindow()">Fit to Window</button>
|
|
429
|
+
</div>
|
|
430
|
+
|
|
431
|
+
<div class="control-group">
|
|
432
|
+
<h3>Debug Visualization</h3>
|
|
433
|
+
<button onclick="toggleDebugMode()">Toggle Debug Mode</button>
|
|
434
|
+
<button onclick="debugSelectedText()">Debug Selected Text</button>
|
|
435
|
+
<button onclick="testArabicFonts()">Test Arabic Fonts</button>
|
|
436
|
+
<button onclick="forceRefreshText()">Force Refresh Text</button>
|
|
437
|
+
<button onclick="testWidthConstraints()">Test Width Constraints</button>
|
|
438
|
+
<button onclick="testForcedNarrowText()">
|
|
439
|
+
Test Forced Narrow Text
|
|
440
|
+
</button>
|
|
441
|
+
<button onclick="testUnlimitedHeight()">Test Unlimited Height</button>
|
|
442
|
+
<button onclick="testWrappingConsistency()">
|
|
443
|
+
Test Wrapping Consistency
|
|
444
|
+
</button>
|
|
445
|
+
<div style="margin: 10px 0">
|
|
446
|
+
<label
|
|
447
|
+
for="debugShowBounds"
|
|
448
|
+
style="display: flex; align-items: center; cursor: pointer"
|
|
449
|
+
>
|
|
450
|
+
<input
|
|
451
|
+
type="checkbox"
|
|
452
|
+
id="debugShowBounds"
|
|
453
|
+
style="margin-right: 8px"
|
|
454
|
+
/>
|
|
455
|
+
Show Bounding Boxes
|
|
456
|
+
</label>
|
|
457
|
+
</div>
|
|
458
|
+
<div style="margin: 10px 0">
|
|
459
|
+
<label
|
|
460
|
+
for="debugShowBaseline"
|
|
461
|
+
style="display: flex; align-items: center; cursor: pointer"
|
|
462
|
+
>
|
|
463
|
+
<input
|
|
464
|
+
type="checkbox"
|
|
465
|
+
id="debugShowBaseline"
|
|
466
|
+
style="margin-right: 8px"
|
|
467
|
+
/>
|
|
468
|
+
Show Baseline
|
|
469
|
+
</label>
|
|
470
|
+
</div>
|
|
471
|
+
<div style="margin: 10px 0">
|
|
472
|
+
<label
|
|
473
|
+
for="debugShowMetrics"
|
|
474
|
+
style="display: flex; align-items: center; cursor: pointer"
|
|
475
|
+
>
|
|
476
|
+
<input
|
|
477
|
+
type="checkbox"
|
|
478
|
+
id="debugShowMetrics"
|
|
479
|
+
style="margin-right: 8px"
|
|
480
|
+
/>
|
|
481
|
+
Show Metrics Info
|
|
482
|
+
</label>
|
|
483
|
+
</div>
|
|
484
|
+
<div style="margin: 10px 0">
|
|
485
|
+
<label
|
|
486
|
+
for="debugShowWrap"
|
|
487
|
+
style="display: flex; align-items: center; cursor: pointer"
|
|
488
|
+
>
|
|
489
|
+
<input
|
|
490
|
+
type="checkbox"
|
|
491
|
+
id="debugShowWrap"
|
|
492
|
+
style="margin-right: 8px"
|
|
493
|
+
/>
|
|
494
|
+
Show Text Wrapping
|
|
495
|
+
</label>
|
|
496
|
+
</div>
|
|
497
|
+
</div>
|
|
498
|
+
|
|
499
|
+
<div class="control-group">
|
|
500
|
+
<h3>Canvas Info</h3>
|
|
501
|
+
<div id="canvasInfo" style="font-size: 12px; color: #6b7280">
|
|
502
|
+
Objects: 0<br />
|
|
503
|
+
Selected: None<br />
|
|
504
|
+
Zoom: 100%
|
|
505
|
+
</div>
|
|
506
|
+
<div
|
|
507
|
+
id="debugInfo"
|
|
508
|
+
style="
|
|
509
|
+
font-size: 11px;
|
|
510
|
+
color: #ef4444;
|
|
511
|
+
margin-top: 10px;
|
|
512
|
+
display: none;
|
|
513
|
+
"
|
|
514
|
+
>
|
|
515
|
+
<!-- Debug info will appear here -->
|
|
516
|
+
</div>
|
|
517
|
+
</div>
|
|
518
|
+
</div>
|
|
519
|
+
|
|
520
|
+
<div class="canvas-container">
|
|
521
|
+
<canvas id="canvas" width="800" height="600"></canvas>
|
|
522
|
+
</div>
|
|
523
|
+
|
|
524
|
+
<script>
|
|
525
|
+
// Test that the new fabric.js measurement system works correctly
|
|
526
|
+
function testBoundingBoxAccuracy(text, fontFamily, fontSize) {
|
|
527
|
+
console.log('🧪 Testing bounding box accuracy:', {
|
|
528
|
+
text,
|
|
529
|
+
fontFamily,
|
|
530
|
+
fontSize,
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
// Check if font is available
|
|
534
|
+
const fontReady = document.fonts
|
|
535
|
+
? document.fonts.check(`${fontSize}px ${fontFamily}`)
|
|
536
|
+
: true;
|
|
537
|
+
console.log('📝 Font ready:', fontReady);
|
|
538
|
+
|
|
539
|
+
return {
|
|
540
|
+
fontReady,
|
|
541
|
+
shouldWaitForFont: !fontReady,
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Initialize Fabric.js canvas
|
|
546
|
+
const canvas = new fabric.Canvas('canvas', {
|
|
547
|
+
backgroundColor: 'white',
|
|
548
|
+
selection: true,
|
|
549
|
+
preserveObjectStacking: true,
|
|
550
|
+
skipOffscreen: false, // Disable offscreen culling to show tall textboxes fully
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
// Create workspace (like your editor) - removed 'clip' name to prevent clipping issues with line dragging
|
|
554
|
+
const workspace = new fabric.Rect({
|
|
555
|
+
left: 100,
|
|
556
|
+
top: 100,
|
|
557
|
+
width: 600,
|
|
558
|
+
height: 400,
|
|
559
|
+
fill: 'white',
|
|
560
|
+
stroke: '#e5e7eb',
|
|
561
|
+
strokeWidth: 2,
|
|
562
|
+
selectable: false,
|
|
563
|
+
evented: false,
|
|
564
|
+
name: 'workspace', // Changed from 'clip' to prevent clipping during line endpoint dragging
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
canvas.add(workspace);
|
|
568
|
+
canvas.centerObject(workspace);
|
|
569
|
+
canvas.renderAll();
|
|
570
|
+
|
|
571
|
+
// Function to fix canvas positioning
|
|
572
|
+
function fixCanvasPositioning() {
|
|
573
|
+
const wrapper = document.querySelector('[data-fabric="wrapper"]');
|
|
574
|
+
if (wrapper) {
|
|
575
|
+
// Force wrapper to stay centered
|
|
576
|
+
wrapper.style.position = 'relative';
|
|
577
|
+
wrapper.style.margin = '0 auto';
|
|
578
|
+
wrapper.style.left = '';
|
|
579
|
+
wrapper.style.right = '';
|
|
580
|
+
wrapper.style.transform = '';
|
|
581
|
+
|
|
582
|
+
// Get both canvas elements
|
|
583
|
+
const lowerCanvas = wrapper.querySelector('[data-fabric="main"]');
|
|
584
|
+
const upperCanvas = wrapper.querySelector('[data-fabric="top"]');
|
|
585
|
+
|
|
586
|
+
if (lowerCanvas && upperCanvas) {
|
|
587
|
+
// Ensure both canvases are positioned identically within wrapper
|
|
588
|
+
lowerCanvas.style.position = 'absolute';
|
|
589
|
+
upperCanvas.style.position = 'absolute';
|
|
590
|
+
lowerCanvas.style.left = '0px';
|
|
591
|
+
lowerCanvas.style.top = '0px';
|
|
592
|
+
upperCanvas.style.left = '0px';
|
|
593
|
+
upperCanvas.style.top = '0px';
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Apply fix multiple times to catch Fabric.js overrides
|
|
599
|
+
setTimeout(fixCanvasPositioning, 0);
|
|
600
|
+
setTimeout(fixCanvasPositioning, 50);
|
|
601
|
+
setTimeout(fixCanvasPositioning, 100);
|
|
602
|
+
setTimeout(fixCanvasPositioning, 200);
|
|
603
|
+
setTimeout(fixCanvasPositioning, 500);
|
|
604
|
+
|
|
605
|
+
// Also fix on canvas events that might trigger repositioning
|
|
606
|
+
canvas.on('after:render', fixCanvasPositioning);
|
|
607
|
+
canvas.on('canvas:cleared', fixCanvasPositioning);
|
|
608
|
+
|
|
609
|
+
// Use MutationObserver to catch any DOM changes to the wrapper
|
|
610
|
+
const observer = new MutationObserver((mutations) => {
|
|
611
|
+
mutations.forEach((mutation) => {
|
|
612
|
+
if (
|
|
613
|
+
mutation.type === 'attributes' &&
|
|
614
|
+
mutation.target.matches('[data-fabric="wrapper"]') &&
|
|
615
|
+
(mutation.attributeName === 'style' ||
|
|
616
|
+
mutation.attributeName === 'class')
|
|
617
|
+
) {
|
|
618
|
+
setTimeout(fixCanvasPositioning, 0);
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
// Start observing after a brief delay
|
|
624
|
+
setTimeout(() => {
|
|
625
|
+
const wrapper = document.querySelector('[data-fabric="wrapper"]');
|
|
626
|
+
if (wrapper) {
|
|
627
|
+
observer.observe(wrapper, {
|
|
628
|
+
attributes: true,
|
|
629
|
+
attributeFilter: ['style', 'class'],
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
}, 100);
|
|
633
|
+
|
|
634
|
+
// Text options are now dynamically set from UI controls
|
|
635
|
+
|
|
636
|
+
// Helper function to auto-detect text direction (same logic as overlay editor)
|
|
637
|
+
function autoDetectTextDirection(text) {
|
|
638
|
+
// Check for RTL characters (Arabic, Hebrew, etc.)
|
|
639
|
+
const rtlChars = /[\u0590-\u08FF\uFB1D-\uFDFF\uFE70-\uFEFF]/;
|
|
640
|
+
const ltrChars = /[A-Za-z]/;
|
|
641
|
+
|
|
642
|
+
const hasRtl = rtlChars.test(text);
|
|
643
|
+
const hasLtr = ltrChars.test(text);
|
|
644
|
+
|
|
645
|
+
if (hasRtl && !hasLtr) return 'rtl';
|
|
646
|
+
if (hasLtr && !hasRtl) return 'ltr';
|
|
647
|
+
if (hasRtl && hasLtr) {
|
|
648
|
+
// Mixed text - determine by first strong character
|
|
649
|
+
for (let char of text) {
|
|
650
|
+
if (rtlChars.test(char)) return 'rtl';
|
|
651
|
+
if (ltrChars.test(char)) return 'ltr';
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
return 'ltr'; // default
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// Helper function to create textbox with forced width override
|
|
658
|
+
function createTextboxWithForcedWidth(text, options, targetWidth) {
|
|
659
|
+
const textbox = new fabric.Textbox(text, options);
|
|
660
|
+
|
|
661
|
+
// Override the dynamic width constraints
|
|
662
|
+
textbox.minWidth = Math.min(targetWidth, 10); // Very small minimum
|
|
663
|
+
textbox.dynamicMinWidth = 0; // Reset dynamic constraint
|
|
664
|
+
textbox.width = targetWidth; // Set desired width
|
|
665
|
+
|
|
666
|
+
// Only force character-based wrapping for extremely narrow boxes (< 60px)
|
|
667
|
+
if (targetWidth < 60) {
|
|
668
|
+
textbox.splitByGrapheme = true;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// Recalculate with new constraints
|
|
672
|
+
textbox.initDimensions();
|
|
673
|
+
|
|
674
|
+
console.log(
|
|
675
|
+
`📏 Created textbox with forced width: ${targetWidth}px (dynamicMinWidth: ${textbox.dynamicMinWidth}px)`,
|
|
676
|
+
);
|
|
677
|
+
|
|
678
|
+
return textbox;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// Helper function to create textbox with unlimited height growth
|
|
682
|
+
function createTextboxWithUnlimitedHeight(text, options, targetWidth) {
|
|
683
|
+
const textbox = new fabric.Textbox(text, options);
|
|
684
|
+
|
|
685
|
+
// Override width constraints
|
|
686
|
+
textbox.minWidth = Math.min(targetWidth, 10);
|
|
687
|
+
textbox.dynamicMinWidth = 0;
|
|
688
|
+
textbox.width = targetWidth;
|
|
689
|
+
|
|
690
|
+
// Only force character-based wrapping for extremely narrow boxes (< 60px)
|
|
691
|
+
if (targetWidth < 60) {
|
|
692
|
+
textbox.splitByGrapheme = true;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// Override the _getAdvancedLayoutOptions to remove height constraint
|
|
696
|
+
const originalGetOptions = textbox._getAdvancedLayoutOptions;
|
|
697
|
+
textbox._getAdvancedLayoutOptions = function () {
|
|
698
|
+
const options = originalGetOptions.call(this);
|
|
699
|
+
// Remove height constraint to allow unlimited growth
|
|
700
|
+
delete options.height;
|
|
701
|
+
|
|
702
|
+
// Force consistent wrapping behavior
|
|
703
|
+
options.wrap = 'word'; // Ensure word wrapping
|
|
704
|
+
return options;
|
|
705
|
+
};
|
|
706
|
+
|
|
707
|
+
// Override text splitting to use consistent logic with debug
|
|
708
|
+
const originalSplitText = textbox._splitTextIntoLines;
|
|
709
|
+
textbox._splitTextIntoLines = function (text) {
|
|
710
|
+
console.log('🔧 TEXTBOX SPLIT: Using consistent wrapping logic');
|
|
711
|
+
|
|
712
|
+
// Force use of advanced layout if available
|
|
713
|
+
if (this.enableAdvancedLayout && this._getAdvancedLayoutOptions) {
|
|
714
|
+
const layoutOptions = this._getAdvancedLayoutOptions();
|
|
715
|
+
|
|
716
|
+
// Debug the layout options being used
|
|
717
|
+
console.log('🔧 Layout options:', {
|
|
718
|
+
width: layoutOptions.width,
|
|
719
|
+
wrap: layoutOptions.wrap,
|
|
720
|
+
direction: layoutOptions.direction,
|
|
721
|
+
fontFamily: layoutOptions.fontFamily,
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
// Let the advanced layout handle the wrapping
|
|
725
|
+
this._updateDimensionsWithAdvancedLayout();
|
|
726
|
+
|
|
727
|
+
// Return existing results if advanced layout worked
|
|
728
|
+
if (this._textLines && this._textLines.length > 0) {
|
|
729
|
+
return {
|
|
730
|
+
lines: this._textLines,
|
|
731
|
+
graphemeLines: this._textLines.map((line) => line.split('')),
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// Fallback to original method
|
|
737
|
+
return originalSplitText.call(this, text);
|
|
738
|
+
};
|
|
739
|
+
|
|
740
|
+
// Recalculate with unlimited height
|
|
741
|
+
textbox.initDimensions();
|
|
742
|
+
|
|
743
|
+
console.log(
|
|
744
|
+
`📏 Created unlimited height textbox: width=${targetWidth}px, height=${textbox.height}px, lines=${textbox._textLines?.length || 0}`,
|
|
745
|
+
);
|
|
746
|
+
|
|
747
|
+
return textbox;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// Add text function with smart measurement system
|
|
751
|
+
function addText() {
|
|
752
|
+
const value =
|
|
753
|
+
document.getElementById('textValue').value || 'Sample Text';
|
|
754
|
+
let direction = document.getElementById('textDirection').value;
|
|
755
|
+
const textAlign = document.getElementById('textAlign').value;
|
|
756
|
+
const fontSize =
|
|
757
|
+
parseInt(document.getElementById('fontSize').value) || 32;
|
|
758
|
+
const fillColor = document.getElementById('fillColor').value;
|
|
759
|
+
|
|
760
|
+
// Get font properties from controls
|
|
761
|
+
const fontWeight =
|
|
762
|
+
document.getElementById('fontWeight').value || 'normal';
|
|
763
|
+
const fontStyle =
|
|
764
|
+
document.getElementById('fontStyle').value || 'normal';
|
|
765
|
+
const fontFamily =
|
|
766
|
+
document.getElementById('fontFamily').value || 'Arial';
|
|
767
|
+
|
|
768
|
+
// Auto-detect direction if user hasn't manually changed it and text looks RTL
|
|
769
|
+
const autoDetectedDirection = autoDetectTextDirection(value);
|
|
770
|
+
if (direction === 'ltr' && autoDetectedDirection === 'rtl') {
|
|
771
|
+
direction = 'rtl';
|
|
772
|
+
// Update the dropdown to reflect the auto-detected direction
|
|
773
|
+
document.getElementById('textDirection').value = 'rtl';
|
|
774
|
+
console.log('🔄 Auto-detected RTL text, switched direction to RTL');
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// Test the improved bounding box system
|
|
778
|
+
const boundingBoxTest = testBoundingBoxAccuracy(
|
|
779
|
+
value,
|
|
780
|
+
fontFamily,
|
|
781
|
+
fontSize,
|
|
782
|
+
);
|
|
783
|
+
|
|
784
|
+
console.log('📐 Creating text with improved measurement system:', {
|
|
785
|
+
text: value,
|
|
786
|
+
font: fontFamily,
|
|
787
|
+
fontSize: fontSize,
|
|
788
|
+
direction: direction,
|
|
789
|
+
fontReady: boundingBoxTest.fontReady,
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
const textbox = new fabric.Textbox(value, {
|
|
793
|
+
left: 200,
|
|
794
|
+
top: 200,
|
|
795
|
+
fill: fillColor,
|
|
796
|
+
fontSize: fontSize,
|
|
797
|
+
fontFamily: fontFamily,
|
|
798
|
+
fontWeight: fontWeight,
|
|
799
|
+
fontStyle: fontStyle,
|
|
800
|
+
|
|
801
|
+
// RTL/LTR configuration (now with auto-detection)
|
|
802
|
+
direction: direction,
|
|
803
|
+
textAlign: textAlign,
|
|
804
|
+
|
|
805
|
+
// ✅ Enable overlay editing (this handles most text editing)
|
|
806
|
+
useOverlayEditing: true,
|
|
807
|
+
|
|
808
|
+
// ✅ Enable advanced layout with improved measurements
|
|
809
|
+
enableAdvancedLayout: false,
|
|
810
|
+
|
|
811
|
+
// ✅ Keep these
|
|
812
|
+
lockMovementX: false,
|
|
813
|
+
lockMovementY: false,
|
|
814
|
+
|
|
815
|
+
// Better spacing for complex scripts
|
|
816
|
+
charSpacing: 0,
|
|
817
|
+
});
|
|
818
|
+
|
|
819
|
+
canvas.add(textbox);
|
|
820
|
+
canvas.setActiveObject(textbox);
|
|
821
|
+
canvas.renderAll();
|
|
822
|
+
updateCanvasInfo();
|
|
823
|
+
|
|
824
|
+
// Log the dynamic width constraint info
|
|
825
|
+
console.log(`📊 Textbox width constraints:`, {
|
|
826
|
+
width: textbox.width,
|
|
827
|
+
minWidth: textbox.minWidth,
|
|
828
|
+
dynamicMinWidth: textbox.dynamicMinWidth,
|
|
829
|
+
canWrap:
|
|
830
|
+
textbox.dynamicMinWidth <= textbox.width
|
|
831
|
+
? 'YES'
|
|
832
|
+
: 'NO (width will auto-expand)',
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// Add shapes functions
|
|
837
|
+
function addRectangle() {
|
|
838
|
+
const fillColor = document.getElementById('fillColor').value;
|
|
839
|
+
const strokeColor = document.getElementById('strokeColor').value;
|
|
840
|
+
const strokeWidth =
|
|
841
|
+
parseInt(document.getElementById('strokeWidth').value) || 2;
|
|
842
|
+
const cornerRadius =
|
|
843
|
+
parseInt(document.getElementById('cornerRadius').value) || 0;
|
|
844
|
+
|
|
845
|
+
const rect = new fabric.Rect({
|
|
846
|
+
left: 150,
|
|
847
|
+
top: 150,
|
|
848
|
+
width: 100,
|
|
849
|
+
height: 100,
|
|
850
|
+
fill: fillColor,
|
|
851
|
+
stroke: strokeColor,
|
|
852
|
+
strokeWidth: strokeWidth,
|
|
853
|
+
rx: cornerRadius,
|
|
854
|
+
ry: cornerRadius,
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
canvas.add(rect);
|
|
858
|
+
canvas.setActiveObject(rect);
|
|
859
|
+
canvas.renderAll();
|
|
860
|
+
updateCanvasInfo();
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
function addCircle() {
|
|
864
|
+
const fillColor = document.getElementById('fillColor').value;
|
|
865
|
+
const strokeColor = document.getElementById('strokeColor').value;
|
|
866
|
+
const strokeWidth =
|
|
867
|
+
parseInt(document.getElementById('strokeWidth').value) || 2;
|
|
868
|
+
|
|
869
|
+
const circle = new fabric.Circle({
|
|
870
|
+
left: 150,
|
|
871
|
+
top: 150,
|
|
872
|
+
radius: 50,
|
|
873
|
+
fill: fillColor,
|
|
874
|
+
stroke: strokeColor,
|
|
875
|
+
strokeWidth: strokeWidth,
|
|
876
|
+
});
|
|
877
|
+
|
|
878
|
+
canvas.add(circle);
|
|
879
|
+
canvas.setActiveObject(circle);
|
|
880
|
+
canvas.renderAll();
|
|
881
|
+
updateCanvasInfo();
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
function addTriangle() {
|
|
885
|
+
const fillColor = document.getElementById('fillColor').value;
|
|
886
|
+
const strokeColor = document.getElementById('strokeColor').value;
|
|
887
|
+
const strokeWidth =
|
|
888
|
+
parseInt(document.getElementById('strokeWidth').value) || 2;
|
|
889
|
+
const cornerRadius =
|
|
890
|
+
parseInt(document.getElementById('cornerRadius').value) || 0;
|
|
891
|
+
|
|
892
|
+
const triangle = new fabric.Triangle({
|
|
893
|
+
left: 150,
|
|
894
|
+
top: 150,
|
|
895
|
+
width: 100,
|
|
896
|
+
height: 100,
|
|
897
|
+
fill: fillColor,
|
|
898
|
+
stroke: strokeColor,
|
|
899
|
+
strokeWidth: strokeWidth,
|
|
900
|
+
cornerRadius: cornerRadius,
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
canvas.add(triangle);
|
|
904
|
+
canvas.setActiveObject(triangle);
|
|
905
|
+
canvas.renderAll();
|
|
906
|
+
updateCanvasInfo();
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
function addPolygon() {
|
|
910
|
+
const fillColor = document.getElementById('fillColor').value;
|
|
911
|
+
const strokeColor = document.getElementById('strokeColor').value;
|
|
912
|
+
const strokeWidth =
|
|
913
|
+
parseInt(document.getElementById('strokeWidth').value) || 2;
|
|
914
|
+
const cornerRadius =
|
|
915
|
+
parseInt(document.getElementById('cornerRadius').value) || 0;
|
|
916
|
+
|
|
917
|
+
// Create a hexagon
|
|
918
|
+
const points = [];
|
|
919
|
+
const sides = 6;
|
|
920
|
+
const radius = 50;
|
|
921
|
+
for (let i = 0; i < sides; i++) {
|
|
922
|
+
const angle = (i * 2 * Math.PI) / sides - Math.PI / 2;
|
|
923
|
+
points.push({
|
|
924
|
+
x: radius * Math.cos(angle),
|
|
925
|
+
y: radius * Math.sin(angle),
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
const polygon = new fabric.Polygon(points, {
|
|
930
|
+
left: 200,
|
|
931
|
+
top: 200,
|
|
932
|
+
fill: fillColor,
|
|
933
|
+
stroke: strokeColor,
|
|
934
|
+
strokeWidth: strokeWidth,
|
|
935
|
+
cornerRadius: cornerRadius,
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
canvas.add(polygon);
|
|
939
|
+
canvas.setActiveObject(polygon);
|
|
940
|
+
canvas.renderAll();
|
|
941
|
+
updateCanvasInfo();
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
function addLine() {
|
|
945
|
+
const strokeColor = document.getElementById('strokeColor').value;
|
|
946
|
+
const strokeWidth =
|
|
947
|
+
parseInt(document.getElementById('strokeWidth').value) || 2;
|
|
948
|
+
const roundedEndpoints =
|
|
949
|
+
document.getElementById('roundedEndpoints').checked;
|
|
950
|
+
|
|
951
|
+
// Create line with absolute coordinates (start and end points)
|
|
952
|
+
const line = new fabric.Line([100, 100, 300, 200], {
|
|
953
|
+
stroke: strokeColor,
|
|
954
|
+
strokeWidth: Math.max(strokeWidth, 10), // Make sure stroke is thick enough to see line caps
|
|
955
|
+
selectable: true,
|
|
956
|
+
evented: true,
|
|
957
|
+
fill: '', // Lines should not have fill
|
|
958
|
+
strokeLineCap: roundedEndpoints ? 'round' : 'butt',
|
|
959
|
+
});
|
|
960
|
+
|
|
961
|
+
canvas.add(line);
|
|
962
|
+
canvas.setActiveObject(line);
|
|
963
|
+
canvas.renderAll();
|
|
964
|
+
updateCanvasInfo();
|
|
965
|
+
|
|
966
|
+
console.log(
|
|
967
|
+
'✏️ Line added with draggable endpoints! Drag the blue circles to adjust.',
|
|
968
|
+
);
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
// Text alignment function
|
|
972
|
+
function setTextAlign(alignment) {
|
|
973
|
+
const activeObject = canvas.getActiveObject();
|
|
974
|
+
if (
|
|
975
|
+
activeObject &&
|
|
976
|
+
(activeObject.type === 'textbox' ||
|
|
977
|
+
activeObject.type === 'text' ||
|
|
978
|
+
activeObject.type === 'i-text')
|
|
979
|
+
) {
|
|
980
|
+
activeObject.set('textAlign', alignment);
|
|
981
|
+
canvas.renderAll();
|
|
982
|
+
updateCanvasInfo();
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
// Font control functions
|
|
987
|
+
function toggleBold() {
|
|
988
|
+
const activeObject = canvas.getActiveObject();
|
|
989
|
+
if (
|
|
990
|
+
activeObject &&
|
|
991
|
+
(activeObject.type === 'textbox' ||
|
|
992
|
+
activeObject.type === 'text' ||
|
|
993
|
+
activeObject.type === 'i-text')
|
|
994
|
+
) {
|
|
995
|
+
const currentWeight = activeObject.fontWeight;
|
|
996
|
+
const newWeight =
|
|
997
|
+
currentWeight === 'bold' || currentWeight === '700'
|
|
998
|
+
? 'normal'
|
|
999
|
+
: 'bold';
|
|
1000
|
+
activeObject.set('fontWeight', newWeight);
|
|
1001
|
+
// Update the dropdown to reflect the change
|
|
1002
|
+
document.getElementById('fontWeight').value = newWeight;
|
|
1003
|
+
canvas.renderAll();
|
|
1004
|
+
updateCanvasInfo();
|
|
1005
|
+
console.log('🔤 Font weight changed to:', newWeight);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
function toggleItalic() {
|
|
1010
|
+
const activeObject = canvas.getActiveObject();
|
|
1011
|
+
if (
|
|
1012
|
+
activeObject &&
|
|
1013
|
+
(activeObject.type === 'textbox' ||
|
|
1014
|
+
activeObject.type === 'text' ||
|
|
1015
|
+
activeObject.type === 'i-text')
|
|
1016
|
+
) {
|
|
1017
|
+
const currentStyle = activeObject.fontStyle;
|
|
1018
|
+
const newStyle = currentStyle === 'italic' ? 'normal' : 'italic';
|
|
1019
|
+
activeObject.set('fontStyle', newStyle);
|
|
1020
|
+
// Update the dropdown to reflect the change
|
|
1021
|
+
document.getElementById('fontStyle').value = newStyle;
|
|
1022
|
+
canvas.renderAll();
|
|
1023
|
+
updateCanvasInfo();
|
|
1024
|
+
console.log('🔤 Font style changed to:', newStyle);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
function changeFontFamily() {
|
|
1029
|
+
const activeObject = canvas.getActiveObject();
|
|
1030
|
+
if (
|
|
1031
|
+
activeObject &&
|
|
1032
|
+
(activeObject.type === 'textbox' ||
|
|
1033
|
+
activeObject.type === 'text' ||
|
|
1034
|
+
activeObject.type === 'i-text')
|
|
1035
|
+
) {
|
|
1036
|
+
const selectedFamily = document.getElementById('fontFamily').value;
|
|
1037
|
+
activeObject.set('fontFamily', selectedFamily);
|
|
1038
|
+
canvas.renderAll();
|
|
1039
|
+
updateCanvasInfo();
|
|
1040
|
+
console.log('🔤 Font family changed to:', selectedFamily);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
// Action functions
|
|
1045
|
+
function deleteSelected() {
|
|
1046
|
+
const activeObjects = canvas.getActiveObjects();
|
|
1047
|
+
if (activeObjects.length) {
|
|
1048
|
+
canvas.remove(...activeObjects);
|
|
1049
|
+
canvas.discardActiveObject();
|
|
1050
|
+
canvas.renderAll();
|
|
1051
|
+
updateCanvasInfo();
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
function clearCanvas() {
|
|
1056
|
+
canvas
|
|
1057
|
+
.getObjects()
|
|
1058
|
+
.filter((obj) => obj.name !== 'workspace')
|
|
1059
|
+
.forEach((obj) => canvas.remove(obj));
|
|
1060
|
+
canvas.discardActiveObject();
|
|
1061
|
+
canvas.renderAll();
|
|
1062
|
+
updateCanvasInfo();
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
function exportJSON() {
|
|
1066
|
+
const json = canvas.toJSON();
|
|
1067
|
+
console.log('Canvas JSON:', json);
|
|
1068
|
+
|
|
1069
|
+
// Create downloadable JSON file
|
|
1070
|
+
const dataStr = JSON.stringify(json, null, 2);
|
|
1071
|
+
const dataBlob = new Blob([dataStr], { type: 'application/json' });
|
|
1072
|
+
const url = URL.createObjectURL(dataBlob);
|
|
1073
|
+
const link = document.createElement('a');
|
|
1074
|
+
link.href = url;
|
|
1075
|
+
link.download = 'canvas-export.json';
|
|
1076
|
+
link.click();
|
|
1077
|
+
URL.revokeObjectURL(url);
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
function importJSON() {
|
|
1081
|
+
// Create file input if it doesn't exist
|
|
1082
|
+
let fileInput = document.getElementById('jsonFileInput');
|
|
1083
|
+
if (!fileInput) {
|
|
1084
|
+
fileInput = document.createElement('input');
|
|
1085
|
+
fileInput.type = 'file';
|
|
1086
|
+
fileInput.id = 'jsonFileInput';
|
|
1087
|
+
fileInput.accept = '.json';
|
|
1088
|
+
fileInput.style.display = 'none';
|
|
1089
|
+
document.body.appendChild(fileInput);
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
// Set up file handler
|
|
1093
|
+
fileInput.onchange = function(event) {
|
|
1094
|
+
const file = event.target.files[0];
|
|
1095
|
+
if (!file) return;
|
|
1096
|
+
|
|
1097
|
+
const reader = new FileReader();
|
|
1098
|
+
reader.onload = function(e) {
|
|
1099
|
+
try {
|
|
1100
|
+
const jsonData = JSON.parse(e.target.result);
|
|
1101
|
+
console.log('Loading JSON:', jsonData);
|
|
1102
|
+
|
|
1103
|
+
// Clear canvas first
|
|
1104
|
+
canvas.clear();
|
|
1105
|
+
|
|
1106
|
+
// Load the JSON data
|
|
1107
|
+
canvas.loadFromJSON(jsonData).then(() => {
|
|
1108
|
+
console.log('✅ JSON loaded successfully');
|
|
1109
|
+
canvas.renderAll();
|
|
1110
|
+
updateCanvasInfo();
|
|
1111
|
+
|
|
1112
|
+
// Check for Arabic text with justify alignment and log info
|
|
1113
|
+
const textObjects = canvas.getObjects().filter(obj => obj.type === 'textbox' || obj.type === 'text');
|
|
1114
|
+
console.log(`🔍 Found ${textObjects.length} text objects after JSON load`);
|
|
1115
|
+
|
|
1116
|
+
textObjects.forEach((obj, index) => {
|
|
1117
|
+
console.log(`📝 ${obj.type} ${index + 1}: "${obj.text.substring(0, 50)}..." - Alignment: ${obj.textAlign}, Font: ${obj.fontFamily}, Advanced: ${obj.enableAdvancedLayout}`);
|
|
1118
|
+
|
|
1119
|
+
// Check if this is using STV font
|
|
1120
|
+
if (obj.fontFamily && obj.fontFamily.toLowerCase().includes('stv')) {
|
|
1121
|
+
console.log(` → 🔤 STV FONT DETECTED: Will ensure proper loading`);
|
|
1122
|
+
|
|
1123
|
+
// Check if STV font is available
|
|
1124
|
+
if (typeof document !== 'undefined' && 'fonts' in document) {
|
|
1125
|
+
const fontSpec = `${obj.fontSize}px ${obj.fontFamily}`;
|
|
1126
|
+
const isReady = document.fonts.check(fontSpec);
|
|
1127
|
+
console.log(` → STV Font status: ${isReady ? '✅ Ready' : '⏳ Loading...'}`);
|
|
1128
|
+
|
|
1129
|
+
if (!isReady) {
|
|
1130
|
+
console.log(` → Loading STV font: ${fontSpec}`);
|
|
1131
|
+
document.fonts.load(fontSpec).then(() => {
|
|
1132
|
+
console.log(` → ✅ STV font loaded successfully`);
|
|
1133
|
+
// Force rerender after font loads
|
|
1134
|
+
setTimeout(() => canvas.renderAll(), 100);
|
|
1135
|
+
}).catch(err => {
|
|
1136
|
+
console.warn(` → ⚠️ STV font loading failed:`, err);
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
// Show debug for ALL text objects, not just justify
|
|
1143
|
+
if (obj.textAlign && obj.textAlign.includes('justify')) {
|
|
1144
|
+
console.log(` → This has JUSTIFY alignment, will debug`);
|
|
1145
|
+
} else {
|
|
1146
|
+
console.log(` → This has NON-JUSTIFY alignment (${obj.textAlign}), will still debug for comparison`);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
// DEBUG ALL TEXT OBJECTS TO SEE WHAT'S HAPPENING
|
|
1150
|
+
|
|
1151
|
+
// Debug function to compare overlay vs fabric text
|
|
1152
|
+
const debugTextComparison = (obj, attempt) => {
|
|
1153
|
+
console.log(`\n🔍 DEBUG COMPARISON - Attempt ${attempt} for ${obj.type} ${index + 1}`);
|
|
1154
|
+
console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
|
|
1155
|
+
|
|
1156
|
+
// Fabric object state
|
|
1157
|
+
console.log(`📊 FABRIC OBJECT STATE:`);
|
|
1158
|
+
console.log(` Text: "${obj.text}"`);
|
|
1159
|
+
console.log(` TextAlign: ${obj.textAlign}`);
|
|
1160
|
+
console.log(` FontFamily: ${obj.fontFamily}`);
|
|
1161
|
+
console.log(` FontSize: ${obj.fontSize}`);
|
|
1162
|
+
console.log(` Width: ${obj.width}`);
|
|
1163
|
+
console.log(` Height: ${obj.height}`);
|
|
1164
|
+
console.log(` Direction: ${obj.direction}`);
|
|
1165
|
+
console.log(` EnableAdvancedLayout: ${obj.enableAdvancedLayout}`);
|
|
1166
|
+
console.log(` Dirty: ${obj.dirty}`);
|
|
1167
|
+
console.log(` Initialized: ${obj.initialized}`);
|
|
1168
|
+
|
|
1169
|
+
// Text lines and bounds with detailed character analysis
|
|
1170
|
+
if (obj._textLines) {
|
|
1171
|
+
console.log(` _textLines count: ${obj._textLines.length}`);
|
|
1172
|
+
obj._textLines.forEach((line, i) => {
|
|
1173
|
+
const lineText = line.join('');
|
|
1174
|
+
console.log(` Line ${i}: [${line.join(', ')}] (${line.length} chars)`);
|
|
1175
|
+
console.log(` Line ${i} as string: "${lineText}"`);
|
|
1176
|
+
|
|
1177
|
+
// Show Unicode code points for analysis
|
|
1178
|
+
const codePoints = Array.from(lineText).map(char =>
|
|
1179
|
+
`${char}(U+${char.charCodeAt(0).toString(16).toUpperCase().padStart(4, '0')})`
|
|
1180
|
+
).join(' ');
|
|
1181
|
+
console.log(` Line ${i} Unicode: ${codePoints}`);
|
|
1182
|
+
});
|
|
1183
|
+
} else {
|
|
1184
|
+
console.log(` _textLines: NOT SET`);
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
// Compare with original text
|
|
1188
|
+
console.log(`\n📝 ORIGINAL TEXT ANALYSIS:`);
|
|
1189
|
+
console.log(` Original: "${obj.text}"`);
|
|
1190
|
+
const originalCodePoints = Array.from(obj.text).map(char =>
|
|
1191
|
+
`${char}(U+${char.charCodeAt(0).toString(16).toUpperCase().padStart(4, '0')})`
|
|
1192
|
+
).join(' ');
|
|
1193
|
+
console.log(` Original Unicode: ${originalCodePoints.substring(0, 200)}...`);
|
|
1194
|
+
|
|
1195
|
+
if (obj.__charBounds) {
|
|
1196
|
+
console.log(` __charBounds count: ${obj.__charBounds.length} lines`);
|
|
1197
|
+
obj.__charBounds.forEach((lineBounds, i) => {
|
|
1198
|
+
if (lineBounds && lineBounds.length > 0) {
|
|
1199
|
+
const lineWidth = lineBounds[lineBounds.length - 1].left + lineBounds[lineBounds.length - 1].width;
|
|
1200
|
+
console.log(` Line ${i}: ${lineBounds.length} chars, total width: ${lineWidth.toFixed(2)}`);
|
|
1201
|
+
|
|
1202
|
+
// Show space character details
|
|
1203
|
+
const spaces = lineBounds.filter((bound, j) => obj._textLines[i] && obj._textLines[i][j] && /\s/.test(obj._textLines[i][j]));
|
|
1204
|
+
if (spaces.length > 0) {
|
|
1205
|
+
console.log(` Spaces: ${spaces.length} found, widths: [${spaces.map(s => s.width.toFixed(2)).join(', ')}]`);
|
|
1206
|
+
}
|
|
1207
|
+
} else {
|
|
1208
|
+
console.log(` Line ${i}: NO BOUNDS`);
|
|
1209
|
+
}
|
|
1210
|
+
});
|
|
1211
|
+
} else {
|
|
1212
|
+
console.log(` __charBounds: NOT SET`);
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
// Font loading status
|
|
1216
|
+
const fontReady = obj._isFontReady ? obj._isFontReady() : 'unknown';
|
|
1217
|
+
console.log(` Font Ready: ${fontReady}`);
|
|
1218
|
+
|
|
1219
|
+
// Text measurements
|
|
1220
|
+
if (obj.calcTextWidth) {
|
|
1221
|
+
const calcWidth = obj.calcTextWidth();
|
|
1222
|
+
console.log(` Calculated Width: ${calcWidth}`);
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
if (obj.calcTextHeight) {
|
|
1226
|
+
const calcHeight = obj.calcTextHeight();
|
|
1227
|
+
console.log(` Calculated Height: ${calcHeight}`);
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
// Textbox-specific
|
|
1231
|
+
if (obj.type === 'textbox') {
|
|
1232
|
+
console.log(` DynamicMinWidth: ${obj.dynamicMinWidth || 'not set'}`);
|
|
1233
|
+
console.log(` MinWidth: ${obj.minWidth}`);
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
console.log(`\n🎭 OVERLAY EDITOR COMPARISON:`);
|
|
1237
|
+
|
|
1238
|
+
// Create a temporary overlay to see what it would look like
|
|
1239
|
+
try {
|
|
1240
|
+
// Create temporary textarea to simulate overlay editor
|
|
1241
|
+
const tempTextarea = document.createElement('textarea');
|
|
1242
|
+
tempTextarea.value = obj.text;
|
|
1243
|
+
tempTextarea.style.position = 'absolute';
|
|
1244
|
+
tempTextarea.style.left = '-9999px';
|
|
1245
|
+
tempTextarea.style.fontSize = `${obj.fontSize}px`;
|
|
1246
|
+
tempTextarea.style.fontFamily = obj.fontFamily;
|
|
1247
|
+
tempTextarea.style.fontWeight = obj.fontWeight || 'normal';
|
|
1248
|
+
tempTextarea.style.fontStyle = obj.fontStyle || 'normal';
|
|
1249
|
+
tempTextarea.style.lineHeight = String(obj.lineHeight || 1.16);
|
|
1250
|
+
tempTextarea.style.width = `${obj.width}px`;
|
|
1251
|
+
tempTextarea.style.direction = obj.direction || 'ltr';
|
|
1252
|
+
tempTextarea.style.textAlign = obj.textAlign.includes('justify') ? 'justify' : obj.textAlign;
|
|
1253
|
+
tempTextarea.style.whiteSpace = 'pre-wrap';
|
|
1254
|
+
tempTextarea.style.wordBreak = 'normal';
|
|
1255
|
+
tempTextarea.style.overflowWrap = 'break-word';
|
|
1256
|
+
|
|
1257
|
+
// Add to DOM temporarily
|
|
1258
|
+
document.body.appendChild(tempTextarea);
|
|
1259
|
+
|
|
1260
|
+
// Get computed styles
|
|
1261
|
+
const computed = window.getComputedStyle(tempTextarea);
|
|
1262
|
+
console.log(` Overlay fontSize: ${computed.fontSize}`);
|
|
1263
|
+
console.log(` Overlay fontFamily: ${computed.fontFamily}`);
|
|
1264
|
+
console.log(` Overlay width: ${computed.width}`);
|
|
1265
|
+
console.log(` Overlay textAlign: ${computed.textAlign}`);
|
|
1266
|
+
console.log(` Overlay direction: ${computed.direction}`);
|
|
1267
|
+
console.log(` Overlay lineHeight: ${computed.lineHeight}`);
|
|
1268
|
+
console.log(` Overlay whiteSpace: ${computed.whiteSpace}`);
|
|
1269
|
+
|
|
1270
|
+
// CRITICAL: Check how overlay editor handles the text
|
|
1271
|
+
console.log(`\n🎭 OVERLAY TEXT ORDERING ANALYSIS:`);
|
|
1272
|
+
console.log(` Overlay value: "${tempTextarea.value}"`);
|
|
1273
|
+
|
|
1274
|
+
// Get first 50 characters for detailed comparison
|
|
1275
|
+
const first50Fabric = obj.text.substring(0, 50);
|
|
1276
|
+
const first50Overlay = tempTextarea.value.substring(0, 50);
|
|
1277
|
+
|
|
1278
|
+
console.log(`\n📊 CHARACTER-BY-CHARACTER COMPARISON (First 50):`);
|
|
1279
|
+
console.log(` Fabric text: "${first50Fabric}"`);
|
|
1280
|
+
console.log(` Overlay text: "${first50Overlay}"`);
|
|
1281
|
+
console.log(` Match: ${first50Fabric === first50Overlay ? '✅ IDENTICAL' : '❌ DIFFERENT'}`);
|
|
1282
|
+
|
|
1283
|
+
if (first50Fabric !== first50Overlay) {
|
|
1284
|
+
console.log(`\n🔍 DETAILED CHAR DIFFERENCE ANALYSIS:`);
|
|
1285
|
+
const maxLen = Math.max(first50Fabric.length, first50Overlay.length);
|
|
1286
|
+
for (let i = 0; i < Math.min(20, maxLen); i++) {
|
|
1287
|
+
const fChar = first50Fabric[i] || '(missing)';
|
|
1288
|
+
const oChar = first50Overlay[i] || '(missing)';
|
|
1289
|
+
const fCode = fChar !== '(missing)' ? `U+${fChar.charCodeAt(0).toString(16).toUpperCase().padStart(4, '0')}` : '';
|
|
1290
|
+
const oCode = oChar !== '(missing)' ? `U+${oChar.charCodeAt(0).toString(16).toUpperCase().padStart(4, '0')}` : '';
|
|
1291
|
+
const match = fChar === oChar ? '✅' : '❌';
|
|
1292
|
+
console.log(` [${i.toString().padStart(2)}] ${match} F:"${fChar}"${fCode} vs O:"${oChar}"${oCode}`);
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
// Compare with Fabric's internal _textLines
|
|
1297
|
+
if (obj._textLines && obj._textLines.length > 0) {
|
|
1298
|
+
const fabricLine0 = obj._textLines[0].join('');
|
|
1299
|
+
const first50FabricLine0 = fabricLine0.substring(0, 50);
|
|
1300
|
+
|
|
1301
|
+
console.log(`\n🔤 FABRIC _textLines[0] COMPARISON:`);
|
|
1302
|
+
console.log(` Original : "${first50Fabric}"`);
|
|
1303
|
+
console.log(` _textLines[0]: "${first50FabricLine0}"`);
|
|
1304
|
+
console.log(` Lines Match: ${first50Fabric === first50FabricLine0 ? '✅ IDENTICAL' : '❌ DIFFERENT'}`);
|
|
1305
|
+
|
|
1306
|
+
if (first50Fabric !== first50FabricLine0) {
|
|
1307
|
+
console.log(` ⚠️ FABRIC INTERNAL MISMATCH: _textLines[0] != original text`);
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
// Test browser's BiDi handling
|
|
1312
|
+
console.log(`\n🧭 BROWSER BiDi DIRECTION TEST:`);
|
|
1313
|
+
tempTextarea.focus();
|
|
1314
|
+
tempTextarea.setSelectionRange(0, 10);
|
|
1315
|
+
const selectedText = tempTextarea.value.substring(0, 10);
|
|
1316
|
+
console.log(` Selected first 10: "${selectedText}"`);
|
|
1317
|
+
|
|
1318
|
+
// Test with different selection ranges to see ordering
|
|
1319
|
+
const selections = [
|
|
1320
|
+
{ start: 0, end: 5, name: 'chars 0-5' },
|
|
1321
|
+
{ start: 5, end: 10, name: 'chars 5-10' },
|
|
1322
|
+
{ start: 0, end: 15, name: 'chars 0-15' }
|
|
1323
|
+
];
|
|
1324
|
+
|
|
1325
|
+
selections.forEach(sel => {
|
|
1326
|
+
if (tempTextarea.value.length >= sel.end) {
|
|
1327
|
+
tempTextarea.setSelectionRange(sel.start, sel.end);
|
|
1328
|
+
const selText = tempTextarea.value.substring(sel.start, sel.end);
|
|
1329
|
+
console.log(` ${sel.name}: "${selText}"`);
|
|
1330
|
+
}
|
|
1331
|
+
});
|
|
1332
|
+
|
|
1333
|
+
// Measure overlay dimensions
|
|
1334
|
+
tempTextarea.style.height = '1px';
|
|
1335
|
+
const overlayScrollHeight = tempTextarea.scrollHeight;
|
|
1336
|
+
const overlayScrollWidth = tempTextarea.scrollWidth;
|
|
1337
|
+
console.log(` Overlay scrollHeight: ${overlayScrollHeight}`);
|
|
1338
|
+
console.log(` Overlay scrollWidth: ${overlayScrollWidth}`);
|
|
1339
|
+
|
|
1340
|
+
// Remove from DOM
|
|
1341
|
+
document.body.removeChild(tempTextarea);
|
|
1342
|
+
|
|
1343
|
+
console.log(`\n⚖️ COMPARISON RESULTS:`);
|
|
1344
|
+
console.log(` Width difference: Fabric(${obj.width}) vs Overlay(${overlayScrollWidth}) = ${(obj.width - overlayScrollWidth).toFixed(2)}px`);
|
|
1345
|
+
console.log(` Height difference: Fabric(${obj.height}) vs Overlay(${overlayScrollHeight}) = ${(obj.height - overlayScrollHeight).toFixed(2)}px`);
|
|
1346
|
+
|
|
1347
|
+
const widthDiffPercent = Math.abs((obj.width - overlayScrollWidth) / obj.width * 100);
|
|
1348
|
+
const heightDiffPercent = Math.abs((obj.height - overlayScrollHeight) / obj.height * 100);
|
|
1349
|
+
|
|
1350
|
+
if (widthDiffPercent > 5) {
|
|
1351
|
+
console.warn(` ⚠️ SIGNIFICANT WIDTH DIFFERENCE: ${widthDiffPercent.toFixed(1)}%`);
|
|
1352
|
+
}
|
|
1353
|
+
if (heightDiffPercent > 5) {
|
|
1354
|
+
console.warn(` ⚠️ SIGNIFICANT HEIGHT DIFFERENCE: ${heightDiffPercent.toFixed(1)}%`);
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
} catch (error) {
|
|
1358
|
+
console.error(` ❌ Overlay comparison failed:`, error);
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`);
|
|
1362
|
+
};
|
|
1363
|
+
|
|
1364
|
+
// Debug comparison and optional additional reinitialization if needed
|
|
1365
|
+
const debugAndCheck = (attempt) => {
|
|
1366
|
+
console.log(`🔍 Attempt ${attempt}: Checking ${obj.type} ${index + 1}`);
|
|
1367
|
+
|
|
1368
|
+
// Run debug comparison
|
|
1369
|
+
debugTextComparison(obj, attempt);
|
|
1370
|
+
|
|
1371
|
+
// Additional check for justify alignment - ensure enlargeSpaces runs
|
|
1372
|
+
if (obj.textAlign && obj.textAlign.includes('justify')) {
|
|
1373
|
+
console.log(` → Checking justify alignment for ${obj.type}`);
|
|
1374
|
+
|
|
1375
|
+
setTimeout(() => {
|
|
1376
|
+
if (obj.enlargeSpaces && obj.__charBounds && obj.__charBounds.length > 0) {
|
|
1377
|
+
console.log(` → Running enlargeSpaces() for justify alignment`);
|
|
1378
|
+
obj.enlargeSpaces();
|
|
1379
|
+
canvas.renderAll();
|
|
1380
|
+
} else {
|
|
1381
|
+
console.log(` → enlargeSpaces not available or __charBounds not ready`);
|
|
1382
|
+
}
|
|
1383
|
+
}, 50);
|
|
1384
|
+
}
|
|
1385
|
+
};
|
|
1386
|
+
|
|
1387
|
+
// Run debug check - the core fix is now in fromObject method
|
|
1388
|
+
setTimeout(() => debugAndCheck(1), 100);
|
|
1389
|
+
});
|
|
1390
|
+
}).catch(error => {
|
|
1391
|
+
console.error('❌ Failed to load JSON:', error);
|
|
1392
|
+
alert('Failed to load JSON file: ' + error.message);
|
|
1393
|
+
});
|
|
1394
|
+
} catch (error) {
|
|
1395
|
+
console.error('❌ Invalid JSON file:', error);
|
|
1396
|
+
alert('Invalid JSON file: ' + error.message);
|
|
1397
|
+
}
|
|
1398
|
+
};
|
|
1399
|
+
|
|
1400
|
+
reader.readAsText(file);
|
|
1401
|
+
// Reset file input for next use
|
|
1402
|
+
event.target.value = '';
|
|
1403
|
+
};
|
|
1404
|
+
|
|
1405
|
+
// Trigger file selection
|
|
1406
|
+
fileInput.click();
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
// Update canvas info
|
|
1410
|
+
function updateCanvasInfo() {
|
|
1411
|
+
const objects = canvas
|
|
1412
|
+
.getObjects()
|
|
1413
|
+
.filter((obj) => obj.name !== 'workspace');
|
|
1414
|
+
const activeObject = canvas.getActiveObject();
|
|
1415
|
+
const zoom = Math.round(canvas.getZoom() * 100);
|
|
1416
|
+
|
|
1417
|
+
let selectedInfo = 'None';
|
|
1418
|
+
let directionInfo = '';
|
|
1419
|
+
|
|
1420
|
+
if (activeObject) {
|
|
1421
|
+
if (activeObject.type === 'activeSelection') {
|
|
1422
|
+
selectedInfo = `${activeObject._objects.length} objects`;
|
|
1423
|
+
} else {
|
|
1424
|
+
selectedInfo = activeObject.type;
|
|
1425
|
+
|
|
1426
|
+
// Show direction info for text objects
|
|
1427
|
+
if (
|
|
1428
|
+
activeObject.type === 'textbox' ||
|
|
1429
|
+
activeObject.type === 'text' ||
|
|
1430
|
+
activeObject.type === 'i-text'
|
|
1431
|
+
) {
|
|
1432
|
+
const direction = activeObject.direction || 'ltr';
|
|
1433
|
+
const textAlign = activeObject.textAlign || 'left';
|
|
1434
|
+
directionInfo = `<br>Direction: ${direction.toUpperCase()}<br>Align: ${textAlign}`;
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
// Sync stroke width slider with selected object
|
|
1438
|
+
if (activeObject.strokeWidth !== undefined) {
|
|
1439
|
+
strokeWidthSlider.value = activeObject.strokeWidth;
|
|
1440
|
+
strokeWidthValue.textContent = activeObject.strokeWidth + 'px';
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
document.getElementById('canvasInfo').innerHTML = `
|
|
1446
|
+
Objects: ${objects.length}<br>
|
|
1447
|
+
Selected: ${selectedInfo}${directionInfo}<br>
|
|
1448
|
+
Zoom: ${zoom}%
|
|
1449
|
+
`;
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
// Zoom functionality
|
|
1453
|
+
const canvasZoomSlider = document.getElementById('canvasZoom');
|
|
1454
|
+
const canvasZoomValue = document.getElementById('canvasZoomValue');
|
|
1455
|
+
|
|
1456
|
+
canvasZoomSlider.addEventListener('input', function () {
|
|
1457
|
+
const zoomLevel = parseFloat(this.value);
|
|
1458
|
+
canvas.setZoom(zoomLevel);
|
|
1459
|
+
canvasZoomValue.textContent = Math.round(zoomLevel * 100) + '%';
|
|
1460
|
+
canvas.renderAll();
|
|
1461
|
+
updateCanvasInfo();
|
|
1462
|
+
});
|
|
1463
|
+
|
|
1464
|
+
function resetZoom() {
|
|
1465
|
+
canvas.setZoom(1);
|
|
1466
|
+
canvasZoomSlider.value = 1;
|
|
1467
|
+
canvasZoomValue.textContent = '100%';
|
|
1468
|
+
canvas.renderAll();
|
|
1469
|
+
updateCanvasInfo();
|
|
1470
|
+
console.log('🔍 Canvas zoom reset to 100%');
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
function fitToWindow() {
|
|
1474
|
+
const container = document.querySelector('.canvas-container');
|
|
1475
|
+
const containerWidth = container.clientWidth - 40; // padding
|
|
1476
|
+
const containerHeight = container.clientHeight - 40; // padding
|
|
1477
|
+
|
|
1478
|
+
const canvasWidth = canvas.getWidth();
|
|
1479
|
+
const canvasHeight = canvas.getHeight();
|
|
1480
|
+
|
|
1481
|
+
const scaleX = containerWidth / canvasWidth;
|
|
1482
|
+
const scaleY = containerHeight / canvasHeight;
|
|
1483
|
+
const scale = Math.min(scaleX, scaleY, 3); // max zoom 300%
|
|
1484
|
+
|
|
1485
|
+
canvas.setZoom(scale);
|
|
1486
|
+
canvasZoomSlider.value = scale;
|
|
1487
|
+
canvasZoomValue.textContent = Math.round(scale * 100) + '%';
|
|
1488
|
+
canvas.renderAll();
|
|
1489
|
+
updateCanvasInfo();
|
|
1490
|
+
console.log(
|
|
1491
|
+
'🔍 Canvas zoom fit to window:',
|
|
1492
|
+
Math.round(scale * 100) + '%',
|
|
1493
|
+
);
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
// Mouse wheel zoom support
|
|
1497
|
+
canvas.on('mouse:wheel', function (opt) {
|
|
1498
|
+
const delta = opt.e.deltaY;
|
|
1499
|
+
let zoom = canvas.getZoom();
|
|
1500
|
+
zoom *= 0.999 ** delta;
|
|
1501
|
+
|
|
1502
|
+
// Clamp zoom between 10% and 300%
|
|
1503
|
+
zoom = Math.max(0.1, Math.min(3, zoom));
|
|
1504
|
+
|
|
1505
|
+
canvas.setZoom(zoom);
|
|
1506
|
+
canvasZoomSlider.value = zoom;
|
|
1507
|
+
canvasZoomValue.textContent = Math.round(zoom * 100) + '%';
|
|
1508
|
+
|
|
1509
|
+
opt.e.preventDefault();
|
|
1510
|
+
opt.e.stopPropagation();
|
|
1511
|
+
|
|
1512
|
+
updateCanvasInfo();
|
|
1513
|
+
console.log('🔍 Mouse wheel zoom:', Math.round(zoom * 100) + '%');
|
|
1514
|
+
});
|
|
1515
|
+
|
|
1516
|
+
// Event listeners for canvas changes
|
|
1517
|
+
canvas.on('object:added', updateCanvasInfo);
|
|
1518
|
+
canvas.on('object:removed', updateCanvasInfo);
|
|
1519
|
+
canvas.on('selection:created', updateCanvasInfo);
|
|
1520
|
+
canvas.on('selection:updated', updateCanvasInfo);
|
|
1521
|
+
canvas.on('selection:cleared', updateCanvasInfo);
|
|
1522
|
+
|
|
1523
|
+
// Initial info update
|
|
1524
|
+
updateCanvasInfo();
|
|
1525
|
+
|
|
1526
|
+
// Stroke width slider functionality
|
|
1527
|
+
const strokeWidthSlider = document.getElementById('strokeWidth');
|
|
1528
|
+
const strokeWidthValue = document.getElementById('strokeWidthValue');
|
|
1529
|
+
|
|
1530
|
+
// Update slider value display and selected object in real-time
|
|
1531
|
+
strokeWidthSlider.addEventListener('input', function () {
|
|
1532
|
+
const value = parseInt(this.value);
|
|
1533
|
+
strokeWidthValue.textContent = value + 'px';
|
|
1534
|
+
|
|
1535
|
+
// Update stroke width of selected object(s) in real-time
|
|
1536
|
+
const activeObjects = canvas.getActiveObjects();
|
|
1537
|
+
if (activeObjects.length > 0) {
|
|
1538
|
+
activeObjects.forEach((obj) => {
|
|
1539
|
+
if (obj.stroke || obj.type === 'line') {
|
|
1540
|
+
obj.set('strokeWidth', value);
|
|
1541
|
+
}
|
|
1542
|
+
});
|
|
1543
|
+
canvas.renderAll();
|
|
1544
|
+
}
|
|
1545
|
+
});
|
|
1546
|
+
|
|
1547
|
+
// Drawing mode functions
|
|
1548
|
+
function enableDrawing() {
|
|
1549
|
+
const strokeColor = document.getElementById('strokeColor').value;
|
|
1550
|
+
const brushWidth =
|
|
1551
|
+
parseInt(document.getElementById('brushWidth').value) || 5;
|
|
1552
|
+
|
|
1553
|
+
console.log('🎨 Enabling drawing mode with:', {
|
|
1554
|
+
strokeColor,
|
|
1555
|
+
brushWidth,
|
|
1556
|
+
});
|
|
1557
|
+
|
|
1558
|
+
// Ensure brush is properly initialized
|
|
1559
|
+
if (!canvas.freeDrawingBrush) {
|
|
1560
|
+
canvas.freeDrawingBrush = new fabric.PencilBrush(canvas);
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
canvas.freeDrawingBrush.width = brushWidth;
|
|
1564
|
+
canvas.freeDrawingBrush.color = strokeColor;
|
|
1565
|
+
canvas.isDrawingMode = true;
|
|
1566
|
+
|
|
1567
|
+
console.log('🎨 Drawing mode enabled');
|
|
1568
|
+
updateCanvasInfo();
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
function disableDrawing() {
|
|
1572
|
+
console.log('🎨 Disabling drawing mode');
|
|
1573
|
+
canvas.isDrawingMode = false;
|
|
1574
|
+
updateCanvasInfo();
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
// Brush width slider functionality
|
|
1578
|
+
const brushWidthSlider = document.getElementById('brushWidth');
|
|
1579
|
+
const brushWidthValue = document.getElementById('brushWidthValue');
|
|
1580
|
+
|
|
1581
|
+
brushWidthSlider.addEventListener('input', function () {
|
|
1582
|
+
const value = parseInt(this.value);
|
|
1583
|
+
brushWidthValue.textContent = value + 'px';
|
|
1584
|
+
|
|
1585
|
+
console.log(
|
|
1586
|
+
'🎨 Brush width changed to:',
|
|
1587
|
+
value,
|
|
1588
|
+
'Canvas objects before:',
|
|
1589
|
+
canvas.getObjects().length,
|
|
1590
|
+
);
|
|
1591
|
+
|
|
1592
|
+
// Update brush width in real-time
|
|
1593
|
+
if (canvas.freeDrawingBrush) {
|
|
1594
|
+
canvas.freeDrawingBrush.width = value;
|
|
1595
|
+
console.log('🎨 Set brush width to:', value);
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
console.log(
|
|
1599
|
+
'🎨 Canvas objects after setting brush width:',
|
|
1600
|
+
canvas.getObjects().length,
|
|
1601
|
+
);
|
|
1602
|
+
});
|
|
1603
|
+
|
|
1604
|
+
// Corner radius slider functionality
|
|
1605
|
+
const cornerRadiusSlider = document.getElementById('cornerRadius');
|
|
1606
|
+
const cornerRadiusValue = document.getElementById('cornerRadiusValue');
|
|
1607
|
+
|
|
1608
|
+
cornerRadiusSlider.addEventListener('input', function () {
|
|
1609
|
+
const value = parseInt(this.value);
|
|
1610
|
+
cornerRadiusValue.textContent = value + 'px';
|
|
1611
|
+
|
|
1612
|
+
// Update corner radius of selected object(s) in real-time
|
|
1613
|
+
const activeObjects = canvas.getActiveObjects();
|
|
1614
|
+
if (activeObjects.length > 0) {
|
|
1615
|
+
activeObjects.forEach((obj) => {
|
|
1616
|
+
if (
|
|
1617
|
+
obj.type === 'triangle' ||
|
|
1618
|
+
obj.type === 'polygon' ||
|
|
1619
|
+
obj.type === 'polyline'
|
|
1620
|
+
) {
|
|
1621
|
+
obj.set('cornerRadius', value);
|
|
1622
|
+
} else if (obj.type === 'rect') {
|
|
1623
|
+
// For rectangles, use rx and ry properties
|
|
1624
|
+
obj.set('rx', value);
|
|
1625
|
+
obj.set('ry', value);
|
|
1626
|
+
}
|
|
1627
|
+
});
|
|
1628
|
+
canvas.renderAll();
|
|
1629
|
+
}
|
|
1630
|
+
});
|
|
1631
|
+
|
|
1632
|
+
// Test function to demonstrate corner radius on all shapes
|
|
1633
|
+
function testCornerRadius() {
|
|
1634
|
+
// Clear canvas first
|
|
1635
|
+
clearCanvas();
|
|
1636
|
+
|
|
1637
|
+
// Create different shapes with corner radius
|
|
1638
|
+
const cornerRadius = 15;
|
|
1639
|
+
const fillColor = '#3b82f6';
|
|
1640
|
+
const strokeColor = '#1d4ed8';
|
|
1641
|
+
const strokeWidth = 2;
|
|
1642
|
+
|
|
1643
|
+
// Rounded Rectangle
|
|
1644
|
+
const rect = new fabric.Rect({
|
|
1645
|
+
left: 150,
|
|
1646
|
+
top: 100,
|
|
1647
|
+
width: 80,
|
|
1648
|
+
height: 60,
|
|
1649
|
+
fill: fillColor,
|
|
1650
|
+
stroke: strokeColor,
|
|
1651
|
+
strokeWidth: strokeWidth,
|
|
1652
|
+
rx: cornerRadius,
|
|
1653
|
+
ry: cornerRadius,
|
|
1654
|
+
});
|
|
1655
|
+
|
|
1656
|
+
// Rounded Triangle
|
|
1657
|
+
const triangle = new fabric.Triangle({
|
|
1658
|
+
left: 150,
|
|
1659
|
+
top: 150,
|
|
1660
|
+
width: 80,
|
|
1661
|
+
height: 80,
|
|
1662
|
+
fill: fillColor,
|
|
1663
|
+
stroke: strokeColor,
|
|
1664
|
+
strokeWidth: strokeWidth,
|
|
1665
|
+
cornerRadius: cornerRadius,
|
|
1666
|
+
});
|
|
1667
|
+
|
|
1668
|
+
// Rounded Hexagon
|
|
1669
|
+
const hexPoints = [];
|
|
1670
|
+
const sides = 6;
|
|
1671
|
+
const radius = 40;
|
|
1672
|
+
for (let i = 0; i < sides; i++) {
|
|
1673
|
+
const angle = (i * 2 * Math.PI) / sides - Math.PI / 2;
|
|
1674
|
+
hexPoints.push({
|
|
1675
|
+
x: radius * Math.cos(angle),
|
|
1676
|
+
y: radius * Math.sin(angle),
|
|
1677
|
+
});
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
const hexagon = new fabric.Polygon(hexPoints, {
|
|
1681
|
+
left: 300,
|
|
1682
|
+
top: 150,
|
|
1683
|
+
fill: '#10b981',
|
|
1684
|
+
stroke: '#047857',
|
|
1685
|
+
strokeWidth: strokeWidth,
|
|
1686
|
+
cornerRadius: cornerRadius,
|
|
1687
|
+
});
|
|
1688
|
+
|
|
1689
|
+
// Rounded Pentagon
|
|
1690
|
+
const pentPoints = [];
|
|
1691
|
+
const pentSides = 5;
|
|
1692
|
+
const pentRadius = 40;
|
|
1693
|
+
for (let i = 0; i < pentSides; i++) {
|
|
1694
|
+
const angle = (i * 2 * Math.PI) / pentSides - Math.PI / 2;
|
|
1695
|
+
pentPoints.push({
|
|
1696
|
+
x: pentRadius * Math.cos(angle),
|
|
1697
|
+
y: pentRadius * Math.sin(angle),
|
|
1698
|
+
});
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
const pentagon = new fabric.Polygon(pentPoints, {
|
|
1702
|
+
left: 450,
|
|
1703
|
+
top: 150,
|
|
1704
|
+
fill: '#f59e0b',
|
|
1705
|
+
stroke: '#d97706',
|
|
1706
|
+
strokeWidth: strokeWidth,
|
|
1707
|
+
cornerRadius: cornerRadius,
|
|
1708
|
+
});
|
|
1709
|
+
|
|
1710
|
+
// Rounded Star
|
|
1711
|
+
const starPoints = [];
|
|
1712
|
+
const outerRadius = 40;
|
|
1713
|
+
const innerRadius = 20;
|
|
1714
|
+
const starSides = 5;
|
|
1715
|
+
for (let i = 0; i < starSides * 2; i++) {
|
|
1716
|
+
const angle = (i * Math.PI) / starSides - Math.PI / 2;
|
|
1717
|
+
const radius = i % 2 === 0 ? outerRadius : innerRadius;
|
|
1718
|
+
starPoints.push({
|
|
1719
|
+
x: radius * Math.cos(angle),
|
|
1720
|
+
y: radius * Math.sin(angle),
|
|
1721
|
+
});
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
const star = new fabric.Polygon(starPoints, {
|
|
1725
|
+
left: 150,
|
|
1726
|
+
top: 300,
|
|
1727
|
+
fill: '#ef4444',
|
|
1728
|
+
stroke: '#dc2626',
|
|
1729
|
+
strokeWidth: strokeWidth,
|
|
1730
|
+
cornerRadius: cornerRadius,
|
|
1731
|
+
});
|
|
1732
|
+
|
|
1733
|
+
// Rounded Arrow
|
|
1734
|
+
const arrowPoints = [
|
|
1735
|
+
{ x: 0, y: -20 },
|
|
1736
|
+
{ x: 30, y: -20 },
|
|
1737
|
+
{ x: 30, y: -40 },
|
|
1738
|
+
{ x: 60, y: 0 },
|
|
1739
|
+
{ x: 30, y: 40 },
|
|
1740
|
+
{ x: 30, y: 20 },
|
|
1741
|
+
{ x: 0, y: 20 },
|
|
1742
|
+
];
|
|
1743
|
+
|
|
1744
|
+
const arrow = new fabric.Polygon(arrowPoints, {
|
|
1745
|
+
left: 350,
|
|
1746
|
+
top: 300,
|
|
1747
|
+
fill: '#8b5cf6',
|
|
1748
|
+
stroke: '#7c3aed',
|
|
1749
|
+
strokeWidth: strokeWidth,
|
|
1750
|
+
cornerRadius: cornerRadius,
|
|
1751
|
+
});
|
|
1752
|
+
|
|
1753
|
+
// Regular Line (lines don't support corner radius)
|
|
1754
|
+
const line = new fabric.Line([50, 50, 150, 50], {
|
|
1755
|
+
left: 450,
|
|
1756
|
+
top: 300,
|
|
1757
|
+
stroke: '#374151',
|
|
1758
|
+
strokeWidth: 4,
|
|
1759
|
+
});
|
|
1760
|
+
|
|
1761
|
+
// Add all shapes to canvas
|
|
1762
|
+
canvas.add(rect, triangle, hexagon, pentagon, star, arrow, line);
|
|
1763
|
+
|
|
1764
|
+
// Add text label
|
|
1765
|
+
const label = new fabric.Textbox(
|
|
1766
|
+
'Corner Radius Demo - All shapes support rounded corners like Canva!',
|
|
1767
|
+
{
|
|
1768
|
+
left: 150,
|
|
1769
|
+
top: 400,
|
|
1770
|
+
fontSize: 16,
|
|
1771
|
+
fontFamily: 'Arial',
|
|
1772
|
+
fill: '#374151',
|
|
1773
|
+
textAlign: 'center',
|
|
1774
|
+
width: 400,
|
|
1775
|
+
},
|
|
1776
|
+
);
|
|
1777
|
+
|
|
1778
|
+
canvas.add(label);
|
|
1779
|
+
canvas.renderAll();
|
|
1780
|
+
updateCanvasInfo();
|
|
1781
|
+
|
|
1782
|
+
console.log(
|
|
1783
|
+
'🎨 Corner radius demo created! All shapes now support corner radius like Canva.',
|
|
1784
|
+
);
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
// Test custom font bounding box improvements
|
|
1788
|
+
function testCustomFontBounds() {
|
|
1789
|
+
clearCanvas();
|
|
1790
|
+
|
|
1791
|
+
console.log('🧪 Testing custom font bounding box improvements...');
|
|
1792
|
+
|
|
1793
|
+
// Test problematic characters that often exceed bounds
|
|
1794
|
+
const testTexts = [
|
|
1795
|
+
{ text: 'ÄÇĞÜİŞ', desc: 'Turkish with diacritics', size: 48 },
|
|
1796
|
+
{ text: 'jgypqÄÇ', desc: 'Mixed ascenders/descenders', size: 32 },
|
|
1797
|
+
{ text: 'Typography', desc: 'Standard text', size: 24 },
|
|
1798
|
+
{ text: 'قال الله تعالى', desc: 'Arabic text', size: 28 },
|
|
1799
|
+
{ text: '🎨✨💫', desc: 'Emoji test', size: 32 },
|
|
1800
|
+
];
|
|
1801
|
+
|
|
1802
|
+
const fonts = ['Arial', 'STV Bold', 'STV Light', 'STV Regular'];
|
|
1803
|
+
let yPos = 120;
|
|
1804
|
+
|
|
1805
|
+
fonts.forEach((font, fontIndex) => {
|
|
1806
|
+
console.log(`🔤 Testing font: ${font}`);
|
|
1807
|
+
|
|
1808
|
+
testTexts.forEach((test, testIndex) => {
|
|
1809
|
+
const xPos = 150 + testIndex * 140;
|
|
1810
|
+
const currentYPos = yPos + fontIndex * 100;
|
|
1811
|
+
|
|
1812
|
+
// Test font readiness
|
|
1813
|
+
const fontTest = testBoundingBoxAccuracy(
|
|
1814
|
+
test.text,
|
|
1815
|
+
font,
|
|
1816
|
+
test.size,
|
|
1817
|
+
);
|
|
1818
|
+
|
|
1819
|
+
// Create text with improved measurement system
|
|
1820
|
+
const textbox = new fabric.Textbox(test.text, {
|
|
1821
|
+
left: xPos,
|
|
1822
|
+
top: currentYPos,
|
|
1823
|
+
fontSize: test.size,
|
|
1824
|
+
fontFamily: font,
|
|
1825
|
+
fill: fontIndex % 2 === 0 ? '#000000' : '#0066cc',
|
|
1826
|
+
// enableAdvancedLayout: true, // Disabled to keep editing working
|
|
1827
|
+
// Add slight background to visualize bounds
|
|
1828
|
+
backgroundColor: 'rgba(255, 255, 0, 0.1)',
|
|
1829
|
+
borderColor: fontTest.fontReady ? 'green' : 'red',
|
|
1830
|
+
borderOpacityWhenMoving: 0.8,
|
|
1831
|
+
width: 120,
|
|
1832
|
+
editable: true,
|
|
1833
|
+
selectable: true,
|
|
1834
|
+
});
|
|
1835
|
+
|
|
1836
|
+
canvas.add(textbox);
|
|
1837
|
+
|
|
1838
|
+
// Add label with more detailed font info
|
|
1839
|
+
const label = new fabric.Text(
|
|
1840
|
+
`${font}\n${test.desc}\nReady: ${fontTest.fontReady ? '✅' : '❌'}`,
|
|
1841
|
+
{
|
|
1842
|
+
left: xPos,
|
|
1843
|
+
top: currentYPos + test.size + 10,
|
|
1844
|
+
fontSize: 10,
|
|
1845
|
+
fontFamily: 'Arial',
|
|
1846
|
+
fill: fontTest.fontReady ? '#22c55e' : '#ef4444',
|
|
1847
|
+
textAlign: 'center',
|
|
1848
|
+
originX: 'center',
|
|
1849
|
+
},
|
|
1850
|
+
);
|
|
1851
|
+
|
|
1852
|
+
canvas.add(label);
|
|
1853
|
+
|
|
1854
|
+
console.log(
|
|
1855
|
+
`✅ Added test: ${test.desc} with ${font} (ready: ${fontTest.fontReady})`,
|
|
1856
|
+
);
|
|
1857
|
+
});
|
|
1858
|
+
});
|
|
1859
|
+
|
|
1860
|
+
// Add main title
|
|
1861
|
+
const title = new fabric.Text(
|
|
1862
|
+
'Custom Font Bounding Box Test\n(Yellow background shows calculated bounds)',
|
|
1863
|
+
{
|
|
1864
|
+
left: 400,
|
|
1865
|
+
top: 50,
|
|
1866
|
+
fontSize: 16,
|
|
1867
|
+
fontFamily: 'Arial',
|
|
1868
|
+
fill: '#333',
|
|
1869
|
+
textAlign: 'center',
|
|
1870
|
+
originX: 'center',
|
|
1871
|
+
},
|
|
1872
|
+
);
|
|
1873
|
+
|
|
1874
|
+
canvas.add(title);
|
|
1875
|
+
canvas.renderAll();
|
|
1876
|
+
|
|
1877
|
+
console.log(
|
|
1878
|
+
'🧪 Custom font bounds test complete! Check if characters stay within yellow bounds.',
|
|
1879
|
+
);
|
|
1880
|
+
updateCanvasInfo();
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
// Font family dropdown real-time update with smart measurement
|
|
1884
|
+
const fontFamilyDropdown = document.getElementById('fontFamily');
|
|
1885
|
+
fontFamilyDropdown.addEventListener('change', function () {
|
|
1886
|
+
const selectedFamily = this.value;
|
|
1887
|
+
const activeObjects = canvas.getActiveObjects();
|
|
1888
|
+
|
|
1889
|
+
if (activeObjects.length > 0) {
|
|
1890
|
+
activeObjects.forEach((obj) => {
|
|
1891
|
+
if (
|
|
1892
|
+
obj.type === 'textbox' ||
|
|
1893
|
+
obj.type === 'text' ||
|
|
1894
|
+
obj.type === 'i-text'
|
|
1895
|
+
) {
|
|
1896
|
+
// Get current text content and properties
|
|
1897
|
+
const currentText = obj.text || '';
|
|
1898
|
+
const currentFontSize = obj.fontSize || 16;
|
|
1899
|
+
const currentDirection = obj.direction || 'ltr';
|
|
1900
|
+
|
|
1901
|
+
// Test font readiness for better bounding box calculation
|
|
1902
|
+
const fontTest = testBoundingBoxAccuracy(
|
|
1903
|
+
currentText,
|
|
1904
|
+
selectedFamily,
|
|
1905
|
+
currentFontSize,
|
|
1906
|
+
);
|
|
1907
|
+
|
|
1908
|
+
console.log('🔤 Font change with improved measurement:', {
|
|
1909
|
+
font: selectedFamily,
|
|
1910
|
+
text: currentText.substring(0, 20) + '...',
|
|
1911
|
+
fontReady: fontTest.fontReady,
|
|
1912
|
+
});
|
|
1913
|
+
|
|
1914
|
+
// Let fabric.js handle the measurement improvements automatically
|
|
1915
|
+
obj.set({
|
|
1916
|
+
fontFamily: selectedFamily,
|
|
1917
|
+
// enableAdvancedLayout: true // Disabled to keep editing working
|
|
1918
|
+
});
|
|
1919
|
+
}
|
|
1920
|
+
});
|
|
1921
|
+
canvas.renderAll();
|
|
1922
|
+
console.log('🔤 Font family changed to:', selectedFamily);
|
|
1923
|
+
updateCanvasInfo();
|
|
1924
|
+
}
|
|
1925
|
+
});
|
|
1926
|
+
|
|
1927
|
+
// Font loading listener to re-render when fonts become available
|
|
1928
|
+
if (document.fonts) {
|
|
1929
|
+
document.fonts.addEventListener('loadingdone', function (event) {
|
|
1930
|
+
console.log('🔤 Font loading completed, re-rendering canvas');
|
|
1931
|
+
|
|
1932
|
+
// Re-render all text objects to use newly loaded fonts
|
|
1933
|
+
const objects = canvas.getObjects();
|
|
1934
|
+
let textObjectsFound = 0;
|
|
1935
|
+
|
|
1936
|
+
objects.forEach((obj) => {
|
|
1937
|
+
if (
|
|
1938
|
+
obj.type === 'textbox' ||
|
|
1939
|
+
obj.type === 'text' ||
|
|
1940
|
+
obj.type === 'i-text'
|
|
1941
|
+
) {
|
|
1942
|
+
textObjectsFound++;
|
|
1943
|
+
|
|
1944
|
+
try {
|
|
1945
|
+
// Force recalculation of text dimensions and measurements
|
|
1946
|
+
obj._clearCache();
|
|
1947
|
+
obj.dirty = true;
|
|
1948
|
+
|
|
1949
|
+
// Safer approach - just force a re-render with new measurements
|
|
1950
|
+
if (obj.type === 'textbox') {
|
|
1951
|
+
// Force the textbox to recalculate its bounds
|
|
1952
|
+
obj.initialized = false;
|
|
1953
|
+
obj.initDimensions();
|
|
1954
|
+
} else {
|
|
1955
|
+
// For regular text, force dimension recalculation
|
|
1956
|
+
obj._dimensionsAffectingProps =
|
|
1957
|
+
obj._dimensionsAffectingProps || {};
|
|
1958
|
+
obj._fontSizeFraction = undefined; // Force recalc
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
// Force height and width recalculation if methods exist
|
|
1962
|
+
if (typeof obj.calcTextHeight === 'function') {
|
|
1963
|
+
obj.calcTextHeight();
|
|
1964
|
+
}
|
|
1965
|
+
if (typeof obj.calcTextWidth === 'function') {
|
|
1966
|
+
obj.calcTextWidth();
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
console.log(
|
|
1970
|
+
`🔤 Updated ${obj.type} with font ${obj.fontFamily}`,
|
|
1971
|
+
);
|
|
1972
|
+
} catch (error) {
|
|
1973
|
+
console.error('🚨 Error updating text object:', error);
|
|
1974
|
+
console.log('🔤 Object details:', {
|
|
1975
|
+
type: obj.type,
|
|
1976
|
+
fontFamily: obj.fontFamily,
|
|
1977
|
+
text: (obj.text || '').substring(0, 20),
|
|
1978
|
+
});
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
});
|
|
1982
|
+
|
|
1983
|
+
if (textObjectsFound > 0) {
|
|
1984
|
+
canvas.renderAll();
|
|
1985
|
+
console.log(
|
|
1986
|
+
`🔤 Re-rendered ${textObjectsFound} text objects after font loading`,
|
|
1987
|
+
);
|
|
1988
|
+
}
|
|
1989
|
+
});
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1992
|
+
// Rounded endpoints checkbox functionality
|
|
1993
|
+
const roundedEndpointsCheckbox =
|
|
1994
|
+
document.getElementById('roundedEndpoints');
|
|
1995
|
+
roundedEndpointsCheckbox.addEventListener('change', function () {
|
|
1996
|
+
const isChecked = this.checked;
|
|
1997
|
+
console.log('🔧 Rounded endpoints checkbox changed:', isChecked);
|
|
1998
|
+
// Update strokeLineCap of selected line object(s) in real-time using Fabric.js built-in property
|
|
1999
|
+
const activeObjects = canvas.getActiveObjects();
|
|
2000
|
+
console.log(
|
|
2001
|
+
'🔧 Active objects:',
|
|
2002
|
+
activeObjects.length,
|
|
2003
|
+
activeObjects.map((o) => o.type),
|
|
2004
|
+
);
|
|
2005
|
+
if (activeObjects.length > 0) {
|
|
2006
|
+
activeObjects.forEach((obj) => {
|
|
2007
|
+
if (obj.type === 'line') {
|
|
2008
|
+
const lineCap = isChecked ? 'round' : 'butt';
|
|
2009
|
+
console.log('🔧 Setting strokeLineCap on line:', lineCap);
|
|
2010
|
+
obj.set('strokeLineCap', lineCap);
|
|
2011
|
+
console.log(
|
|
2012
|
+
'🔧 Line strokeLineCap after set:',
|
|
2013
|
+
obj.strokeLineCap,
|
|
2014
|
+
);
|
|
2015
|
+
}
|
|
2016
|
+
});
|
|
2017
|
+
canvas.renderAll();
|
|
2018
|
+
}
|
|
2019
|
+
});
|
|
2020
|
+
|
|
2021
|
+
// Object alignment functions
|
|
2022
|
+
function centerObject() {
|
|
2023
|
+
const activeObject = canvas.getActiveObject();
|
|
2024
|
+
if (activeObject && activeObject.name !== 'workspace') {
|
|
2025
|
+
canvas.centerObject(activeObject);
|
|
2026
|
+
activeObject.setCoords();
|
|
2027
|
+
canvas.renderAll();
|
|
2028
|
+
console.log('📍 Object centered');
|
|
2029
|
+
updateCanvasInfo();
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
function centerObjectH() {
|
|
2034
|
+
const activeObject = canvas.getActiveObject();
|
|
2035
|
+
if (activeObject && activeObject.name !== 'workspace') {
|
|
2036
|
+
canvas.centerObjectH(activeObject);
|
|
2037
|
+
activeObject.setCoords();
|
|
2038
|
+
canvas.renderAll();
|
|
2039
|
+
console.log('📍 Object centered horizontally');
|
|
2040
|
+
updateCanvasInfo();
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
function centerObjectV() {
|
|
2045
|
+
const activeObject = canvas.getActiveObject();
|
|
2046
|
+
if (activeObject && activeObject.name !== 'workspace') {
|
|
2047
|
+
canvas.centerObjectV(activeObject);
|
|
2048
|
+
activeObject.setCoords();
|
|
2049
|
+
canvas.renderAll();
|
|
2050
|
+
console.log('📍 Object centered vertically');
|
|
2051
|
+
updateCanvasInfo();
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
function alignLeft() {
|
|
2056
|
+
const activeObject = canvas.getActiveObject();
|
|
2057
|
+
if (activeObject && activeObject.name !== 'workspace') {
|
|
2058
|
+
activeObject.set({
|
|
2059
|
+
left: 0,
|
|
2060
|
+
originX: 'left',
|
|
2061
|
+
});
|
|
2062
|
+
activeObject.setCoords();
|
|
2063
|
+
canvas.renderAll();
|
|
2064
|
+
console.log('📍 Object aligned left');
|
|
2065
|
+
updateCanvasInfo();
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
function alignRight() {
|
|
2070
|
+
const activeObject = canvas.getActiveObject();
|
|
2071
|
+
if (activeObject && activeObject.name !== 'workspace') {
|
|
2072
|
+
activeObject.set({
|
|
2073
|
+
left: canvas.width,
|
|
2074
|
+
originX: 'right',
|
|
2075
|
+
});
|
|
2076
|
+
activeObject.setCoords();
|
|
2077
|
+
canvas.renderAll();
|
|
2078
|
+
console.log('📍 Object aligned right');
|
|
2079
|
+
updateCanvasInfo();
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
function alignTop() {
|
|
2084
|
+
const activeObject = canvas.getActiveObject();
|
|
2085
|
+
if (activeObject && activeObject.name !== 'workspace') {
|
|
2086
|
+
activeObject.set({
|
|
2087
|
+
top: 0,
|
|
2088
|
+
originY: 'top',
|
|
2089
|
+
});
|
|
2090
|
+
activeObject.setCoords();
|
|
2091
|
+
canvas.renderAll();
|
|
2092
|
+
console.log('📍 Object aligned top');
|
|
2093
|
+
updateCanvasInfo();
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
function alignBottom() {
|
|
2098
|
+
const activeObject = canvas.getActiveObject();
|
|
2099
|
+
if (activeObject && activeObject.name !== 'workspace') {
|
|
2100
|
+
activeObject.set({
|
|
2101
|
+
top: canvas.height,
|
|
2102
|
+
originY: 'bottom',
|
|
2103
|
+
});
|
|
2104
|
+
activeObject.setCoords();
|
|
2105
|
+
canvas.renderAll();
|
|
2106
|
+
console.log('📍 Object aligned bottom');
|
|
2107
|
+
updateCanvasInfo();
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
// Debug visualization variables
|
|
2112
|
+
let debugMode = false;
|
|
2113
|
+
let debugOverlay = null;
|
|
2114
|
+
|
|
2115
|
+
// Debug helper functions (based on our measurement improvements)
|
|
2116
|
+
function getBestMeasurementCharacter(ctx, fontFamily) {
|
|
2117
|
+
const latinChar = 'M';
|
|
2118
|
+
const arabicChar = 'م';
|
|
2119
|
+
|
|
2120
|
+
if (
|
|
2121
|
+
/arabic|amiri|stv|noto.*arabic|cairo|scheherazade/i.test(fontFamily)
|
|
2122
|
+
) {
|
|
2123
|
+
return arabicChar;
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
const originalFont = ctx.font;
|
|
2127
|
+
ctx.font = ctx.font.replace(/[^,]+$/, 'Arial, sans-serif');
|
|
2128
|
+
const fallbackMetrics = ctx.measureText(latinChar);
|
|
2129
|
+
|
|
2130
|
+
ctx.font = originalFont;
|
|
2131
|
+
const actualMetrics = ctx.measureText(latinChar);
|
|
2132
|
+
|
|
2133
|
+
const widthDiff = Math.abs(actualMetrics.width - fallbackMetrics.width);
|
|
2134
|
+
const hasLatinSupport =
|
|
2135
|
+
widthDiff > Math.max(1.0, fallbackMetrics.width * 0.05);
|
|
2136
|
+
|
|
2137
|
+
return hasLatinSupport ? latinChar : arabicChar;
|
|
2138
|
+
}
|
|
2139
|
+
|
|
2140
|
+
function getDebugTextMetrics(textObj) {
|
|
2141
|
+
// Create a temporary canvas context for measurements
|
|
2142
|
+
const tempCanvas = document.createElement('canvas');
|
|
2143
|
+
const ctx = tempCanvas.getContext('2d');
|
|
2144
|
+
|
|
2145
|
+
// Set font properties
|
|
2146
|
+
const fontSize = textObj.fontSize || 16;
|
|
2147
|
+
const fontFamily = textObj.fontFamily || 'Arial';
|
|
2148
|
+
const fontWeight = textObj.fontWeight || 'normal';
|
|
2149
|
+
const fontStyle = textObj.fontStyle || 'normal';
|
|
2150
|
+
|
|
2151
|
+
ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`;
|
|
2152
|
+
ctx.textBaseline = 'alphabetic';
|
|
2153
|
+
|
|
2154
|
+
const text = textObj.text || '';
|
|
2155
|
+
const textMetrics = ctx.measureText(text);
|
|
2156
|
+
const measurementChar = getBestMeasurementCharacter(ctx, fontFamily);
|
|
2157
|
+
const charMetrics = ctx.measureText(measurementChar);
|
|
2158
|
+
|
|
2159
|
+
return {
|
|
2160
|
+
textMetrics,
|
|
2161
|
+
charMetrics,
|
|
2162
|
+
measurementChar,
|
|
2163
|
+
fontProps: { fontSize, fontFamily, fontWeight, fontStyle },
|
|
2164
|
+
};
|
|
2165
|
+
}
|
|
2166
|
+
|
|
2167
|
+
// Debug visualization functions
|
|
2168
|
+
function toggleDebugMode() {
|
|
2169
|
+
debugMode = !debugMode;
|
|
2170
|
+
const debugInfo = document.getElementById('debugInfo');
|
|
2171
|
+
|
|
2172
|
+
if (debugMode) {
|
|
2173
|
+
debugInfo.style.display = 'block';
|
|
2174
|
+
debugInfo.innerHTML =
|
|
2175
|
+
'<strong>🔍 Debug Mode: ON</strong><br>Select a text object to see debug info';
|
|
2176
|
+
console.log('🔍 Debug mode enabled');
|
|
2177
|
+
} else {
|
|
2178
|
+
debugInfo.style.display = 'none';
|
|
2179
|
+
clearDebugOverlay();
|
|
2180
|
+
console.log('🔍 Debug mode disabled');
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2183
|
+
// Update all text objects
|
|
2184
|
+
canvas.renderAll();
|
|
2185
|
+
}
|
|
2186
|
+
|
|
2187
|
+
function debugSelectedText() {
|
|
2188
|
+
const activeObject = canvas.getActiveObject();
|
|
2189
|
+
if (
|
|
2190
|
+
!activeObject ||
|
|
2191
|
+
(activeObject.type !== 'textbox' &&
|
|
2192
|
+
activeObject.type !== 'text' &&
|
|
2193
|
+
activeObject.type !== 'i-text')
|
|
2194
|
+
) {
|
|
2195
|
+
alert('Please select a text object first');
|
|
2196
|
+
return;
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
const debug = getDebugTextMetrics(activeObject);
|
|
2200
|
+
const debugInfo = document.getElementById('debugInfo');
|
|
2201
|
+
|
|
2202
|
+
debugInfo.style.display = 'block';
|
|
2203
|
+
debugInfo.innerHTML = `
|
|
2204
|
+
<strong>🔍 Debug Info for "${(activeObject.text || '').substring(0, 20)}..."</strong><br>
|
|
2205
|
+
<strong>Font:</strong> ${debug.fontProps.fontFamily} ${debug.fontProps.fontSize}px<br>
|
|
2206
|
+
<strong>Measurement char:</strong> '${debug.measurementChar}'<br>
|
|
2207
|
+
<strong>Text width:</strong> ${debug.textMetrics.width.toFixed(1)}px<br>
|
|
2208
|
+
<strong>Font ascent:</strong> ${debug.charMetrics.fontBoundingBoxAscent?.toFixed(1) || 'N/A'}px<br>
|
|
2209
|
+
<strong>Font descent:</strong> ${debug.charMetrics.fontBoundingBoxDescent?.toFixed(1) || 'N/A'}px<br>
|
|
2210
|
+
<strong>Actual ascent:</strong> ${debug.textMetrics.actualBoundingBoxAscent?.toFixed(1) || 'N/A'}px<br>
|
|
2211
|
+
<strong>Actual descent:</strong> ${debug.textMetrics.actualBoundingBoxDescent?.toFixed(1) || 'N/A'}px<br>
|
|
2212
|
+
<strong>Object bounds:</strong> ${activeObject.width?.toFixed(1) || 'auto'} x ${activeObject.height?.toFixed(1) || 'auto'}px
|
|
2213
|
+
`;
|
|
2214
|
+
|
|
2215
|
+
// Draw debug overlay
|
|
2216
|
+
drawDebugOverlay(activeObject, debug);
|
|
2217
|
+
|
|
2218
|
+
// COMPARISON LOGGING - Fabric.js vs Debug System
|
|
2219
|
+
console.log(
|
|
2220
|
+
'🔍================== MEASUREMENT COMPARISON ==================',
|
|
2221
|
+
);
|
|
2222
|
+
console.log('📊 FABRIC.JS ACTUAL STATE:');
|
|
2223
|
+
console.log(' Text:', activeObject.text);
|
|
2224
|
+
console.log(
|
|
2225
|
+
' Font:',
|
|
2226
|
+
activeObject.fontFamily,
|
|
2227
|
+
activeObject.fontSize + 'px',
|
|
2228
|
+
);
|
|
2229
|
+
console.log(' Width limit:', activeObject.width + 'px');
|
|
2230
|
+
console.log(
|
|
2231
|
+
' Actual height:',
|
|
2232
|
+
activeObject.height?.toFixed(1) + 'px',
|
|
2233
|
+
);
|
|
2234
|
+
console.log(
|
|
2235
|
+
' Lines count:',
|
|
2236
|
+
activeObject._textLines?.length || 'unknown',
|
|
2237
|
+
);
|
|
2238
|
+
console.log(
|
|
2239
|
+
' 🔧 enableAdvancedLayout:',
|
|
2240
|
+
activeObject.enableAdvancedLayout || false,
|
|
2241
|
+
);
|
|
2242
|
+
if (activeObject._textLines) {
|
|
2243
|
+
activeObject._textLines.forEach((line, i) => {
|
|
2244
|
+
console.log(` Line ${i + 1}:`, JSON.stringify(line));
|
|
2245
|
+
});
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
console.log('🎯 OUR DEBUG SYSTEM CALCULATION:');
|
|
2249
|
+
console.log(' Measurement char:', debug.measurementChar);
|
|
2250
|
+
console.log(
|
|
2251
|
+
' Full text width:',
|
|
2252
|
+
debug.textMetrics.width.toFixed(1) + 'px',
|
|
2253
|
+
);
|
|
2254
|
+
console.log(' Should wrap at:', activeObject.width + 'px');
|
|
2255
|
+
console.log(
|
|
2256
|
+
' Should wrap?',
|
|
2257
|
+
debug.textMetrics.width > activeObject.width ? 'YES' : 'NO',
|
|
2258
|
+
);
|
|
2259
|
+
|
|
2260
|
+
// Simulate word-by-word measurement using the SAME method as layout engine
|
|
2261
|
+
const words = activeObject.text.split(' ');
|
|
2262
|
+
|
|
2263
|
+
// Create measurement options like layout engine does
|
|
2264
|
+
const measurementOptions = {
|
|
2265
|
+
fontFamily: activeObject.fontFamily,
|
|
2266
|
+
fontSize: activeObject.fontSize,
|
|
2267
|
+
fontStyle: activeObject.fontStyle,
|
|
2268
|
+
fontWeight: activeObject.fontWeight,
|
|
2269
|
+
letterSpacing: activeObject.letterSpacing,
|
|
2270
|
+
direction:
|
|
2271
|
+
activeObject.direction === 'inherit'
|
|
2272
|
+
? 'ltr'
|
|
2273
|
+
: activeObject.direction || 'ltr',
|
|
2274
|
+
};
|
|
2275
|
+
|
|
2276
|
+
// Use the same grapheme-based measurement as layout engine
|
|
2277
|
+
function measureTextLikeLayoutEngine(text) {
|
|
2278
|
+
const tempCanvas = document.createElement('canvas');
|
|
2279
|
+
const ctx = tempCanvas.getContext('2d');
|
|
2280
|
+
ctx.font = `${measurementOptions.fontStyle || 'normal'} ${measurementOptions.fontWeight || 'normal'} ${measurementOptions.fontSize}px ${measurementOptions.fontFamily}`;
|
|
2281
|
+
|
|
2282
|
+
// Set text direction for proper measurement
|
|
2283
|
+
ctx.direction = measurementOptions.direction || 'ltr';
|
|
2284
|
+
|
|
2285
|
+
// Simple approximation - use the browser's measureText but apply our font-specific adjustments
|
|
2286
|
+
const basicMeasurement = ctx.measureText(text);
|
|
2287
|
+
|
|
2288
|
+
// Apply STV font correction factor based on what we observed
|
|
2289
|
+
// Layout engine: 206.1px, Browser: 236.9px → factor = 206.1/236.9 = 0.87
|
|
2290
|
+
const isSTV = /stv/i.test(measurementOptions.fontFamily);
|
|
2291
|
+
let correctionFactor = isSTV ? 0.87 : 1.0;
|
|
2292
|
+
|
|
2293
|
+
// Additional correction for RTL text if needed
|
|
2294
|
+
if (measurementOptions.direction === 'rtl' && isSTV) {
|
|
2295
|
+
// RTL Arabic text may need slightly different measurement
|
|
2296
|
+
correctionFactor *= 0.95;
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
return basicMeasurement.width * correctionFactor;
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
let currentLine = '';
|
|
2303
|
+
let lineCount = 1;
|
|
2304
|
+
console.log('🔤 WORD-BY-WORD SIMULATION (Layout Engine Method):');
|
|
2305
|
+
for (let i = 0; i < words.length; i++) {
|
|
2306
|
+
const word = words[i];
|
|
2307
|
+
const testLine = currentLine + (currentLine ? ' ' : '') + word;
|
|
2308
|
+
const width = measureTextLikeLayoutEngine(testLine);
|
|
2309
|
+
|
|
2310
|
+
console.log(
|
|
2311
|
+
` Adding "${word}": "${testLine}" = ${width.toFixed(1)}px`,
|
|
2312
|
+
);
|
|
2313
|
+
|
|
2314
|
+
// Use exact width to match overlay editor behavior
|
|
2315
|
+
const browserAdjustedWidth = activeObject.width; // No artificial buffer
|
|
2316
|
+
if (width > browserAdjustedWidth && currentLine !== '') {
|
|
2317
|
+
const currentLineWidth = measureTextLikeLayoutEngine(currentLine);
|
|
2318
|
+
console.log(
|
|
2319
|
+
` ✂️ WRAP! Line ${lineCount}: "${currentLine}" (${currentLineWidth.toFixed(1)}px)`,
|
|
2320
|
+
);
|
|
2321
|
+
currentLine = word;
|
|
2322
|
+
lineCount++;
|
|
2323
|
+
} else {
|
|
2324
|
+
currentLine = testLine;
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
|
|
2328
|
+
if (currentLine) {
|
|
2329
|
+
const finalWidth = measureTextLikeLayoutEngine(currentLine);
|
|
2330
|
+
console.log(
|
|
2331
|
+
` 📝 Final line ${lineCount}: "${currentLine}" (${finalWidth.toFixed(1)}px)`,
|
|
2332
|
+
);
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
console.log('📊 COMPARISON SUMMARY:');
|
|
2336
|
+
console.log(' Expected lines:', lineCount);
|
|
2337
|
+
console.log(
|
|
2338
|
+
' Fabric lines:',
|
|
2339
|
+
activeObject._textLines?.length || 'unknown',
|
|
2340
|
+
);
|
|
2341
|
+
console.log(
|
|
2342
|
+
' Match?',
|
|
2343
|
+
lineCount === (activeObject._textLines?.length || 0)
|
|
2344
|
+
? '✅ YES'
|
|
2345
|
+
: '❌ NO',
|
|
2346
|
+
);
|
|
2347
|
+
console.log('🔍================= END COMPARISON ===================');
|
|
2348
|
+
|
|
2349
|
+
console.log('🔍 Debug info for text object:', debug);
|
|
2350
|
+
}
|
|
2351
|
+
|
|
2352
|
+
function drawDebugOverlay(textObj, debug) {
|
|
2353
|
+
clearDebugOverlay();
|
|
2354
|
+
|
|
2355
|
+
const showBounds = document.getElementById('debugShowBounds').checked;
|
|
2356
|
+
const showBaseline =
|
|
2357
|
+
document.getElementById('debugShowBaseline').checked;
|
|
2358
|
+
const showMetrics = document.getElementById('debugShowMetrics').checked;
|
|
2359
|
+
const showWrap = document.getElementById('debugShowWrap').checked;
|
|
2360
|
+
|
|
2361
|
+
if (!showBounds && !showBaseline && !showMetrics && !showWrap) return;
|
|
2362
|
+
|
|
2363
|
+
// Get object's actual bounding rect - this accounts for Fabric.js transforms
|
|
2364
|
+
const boundingRect = textObj.getBoundingRect();
|
|
2365
|
+
|
|
2366
|
+
// Get text object position accounting for origin and transforms
|
|
2367
|
+
const matrix = textObj.calcTransformMatrix();
|
|
2368
|
+
const objectCenter = fabric.util.transformPoint(
|
|
2369
|
+
{ x: textObj.width / 2, y: textObj.height / 2 },
|
|
2370
|
+
matrix,
|
|
2371
|
+
);
|
|
2372
|
+
|
|
2373
|
+
// Calculate the actual text baseline position within the object
|
|
2374
|
+
const fontSize = textObj.fontSize || 16;
|
|
2375
|
+
const fontFamily = textObj.fontFamily || 'Arial';
|
|
2376
|
+
const direction = textObj.direction || 'ltr';
|
|
2377
|
+
|
|
2378
|
+
// Use the same improved fallback logic as our measurement system
|
|
2379
|
+
const isSTVFont = /stv/i.test(fontFamily);
|
|
2380
|
+
const isArabicFont =
|
|
2381
|
+
/arabic|amiri|stv|noto.*arabic|cairo|scheherazade|droid.*arabic|tahoma/i.test(
|
|
2382
|
+
fontFamily,
|
|
2383
|
+
);
|
|
2384
|
+
|
|
2385
|
+
let ascent, descent;
|
|
2386
|
+
|
|
2387
|
+
if (
|
|
2388
|
+
debug.charMetrics.fontBoundingBoxAscent !== undefined &&
|
|
2389
|
+
debug.charMetrics.fontBoundingBoxDescent !== undefined
|
|
2390
|
+
) {
|
|
2391
|
+
// Use browser-provided metrics if available
|
|
2392
|
+
ascent = debug.charMetrics.fontBoundingBoxAscent;
|
|
2393
|
+
descent = debug.charMetrics.fontBoundingBoxDescent;
|
|
2394
|
+
} else {
|
|
2395
|
+
// Apply the same improved fallbacks with better RTL handling
|
|
2396
|
+
if (isSTVFont) {
|
|
2397
|
+
// STV fonts need special handling for Arabic diacritics
|
|
2398
|
+
ascent = fontSize * 0.75; // Increased for Arabic diacritics
|
|
2399
|
+
descent = fontSize * 0.25; // Increased for Arabic descenders
|
|
2400
|
+
} else if (isArabicFont) {
|
|
2401
|
+
ascent = fontSize * 0.75;
|
|
2402
|
+
descent = fontSize * 0.18;
|
|
2403
|
+
} else {
|
|
2404
|
+
ascent = fontSize * 0.8;
|
|
2405
|
+
descent = fontSize * 0.2;
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
// Improved baseline calculation that accounts for RTL text positioning
|
|
2410
|
+
// For RTL text in Fabric.js, the baseline needs adjustment based on text alignment
|
|
2411
|
+
let baselineY;
|
|
2412
|
+
if (direction === 'rtl' && textObj.textAlign === 'right') {
|
|
2413
|
+
// For RTL right-aligned text, baseline is positioned differently
|
|
2414
|
+
baselineY = boundingRect.top + ascent + fontSize * 0.1; // Slight adjustment for RTL
|
|
2415
|
+
} else {
|
|
2416
|
+
// Standard LTR or center-aligned text
|
|
2417
|
+
baselineY = boundingRect.top + boundingRect.height - descent;
|
|
2418
|
+
}
|
|
2419
|
+
|
|
2420
|
+
const debugShapes = [];
|
|
2421
|
+
|
|
2422
|
+
// Show bounding boxes
|
|
2423
|
+
if (showBounds) {
|
|
2424
|
+
// Font bounding box (red) - based on font metrics from measurement character
|
|
2425
|
+
// For RTL text, position needs to account for text alignment
|
|
2426
|
+
let fontBoundsLeft = boundingRect.left;
|
|
2427
|
+
let fontBoundsWidth = boundingRect.width;
|
|
2428
|
+
|
|
2429
|
+
if (direction === 'rtl' && textObj.textAlign === 'right') {
|
|
2430
|
+
// For RTL right-aligned text, align the debug box to the right
|
|
2431
|
+
const actualTextWidth = debug.textMetrics.width;
|
|
2432
|
+
fontBoundsLeft =
|
|
2433
|
+
boundingRect.left + boundingRect.width - actualTextWidth;
|
|
2434
|
+
fontBoundsWidth = actualTextWidth;
|
|
2435
|
+
}
|
|
2436
|
+
|
|
2437
|
+
const fontBounds = new fabric.Rect({
|
|
2438
|
+
left: fontBoundsLeft,
|
|
2439
|
+
top: baselineY - ascent,
|
|
2440
|
+
width: fontBoundsWidth,
|
|
2441
|
+
height: ascent + descent,
|
|
2442
|
+
fill: 'rgba(255, 0, 0, 0.1)', // Semi-transparent red fill for better visibility
|
|
2443
|
+
stroke: 'red',
|
|
2444
|
+
strokeWidth: 2,
|
|
2445
|
+
strokeDashArray: [5, 5],
|
|
2446
|
+
selectable: false,
|
|
2447
|
+
evented: false,
|
|
2448
|
+
name: 'debug-font-bounds',
|
|
2449
|
+
originX: 'left',
|
|
2450
|
+
originY: 'top',
|
|
2451
|
+
});
|
|
2452
|
+
debugShapes.push(fontBounds);
|
|
2453
|
+
|
|
2454
|
+
// Fabric.js calculated bounds (purple) - what Fabric thinks the bounds are
|
|
2455
|
+
const fabricBounds = new fabric.Rect({
|
|
2456
|
+
left: boundingRect.left,
|
|
2457
|
+
top: boundingRect.top,
|
|
2458
|
+
width: boundingRect.width,
|
|
2459
|
+
height: boundingRect.height,
|
|
2460
|
+
fill: 'rgba(128, 0, 128, 0.05)', // Very light purple fill
|
|
2461
|
+
stroke: 'purple',
|
|
2462
|
+
strokeWidth: 1,
|
|
2463
|
+
strokeDashArray: [2, 2],
|
|
2464
|
+
selectable: false,
|
|
2465
|
+
evented: false,
|
|
2466
|
+
name: 'debug-fabric-bounds',
|
|
2467
|
+
originX: 'left',
|
|
2468
|
+
originY: 'top',
|
|
2469
|
+
});
|
|
2470
|
+
debugShapes.push(fabricBounds);
|
|
2471
|
+
|
|
2472
|
+
// Actual text bounding box (blue) if available
|
|
2473
|
+
if (debug.textMetrics.actualBoundingBoxAscent !== undefined) {
|
|
2474
|
+
let actualLeft =
|
|
2475
|
+
boundingRect.left +
|
|
2476
|
+
(debug.textMetrics.actualBoundingBoxLeft || 0);
|
|
2477
|
+
let actualWidth =
|
|
2478
|
+
(debug.textMetrics.actualBoundingBoxRight ||
|
|
2479
|
+
debug.textMetrics.width) -
|
|
2480
|
+
(debug.textMetrics.actualBoundingBoxLeft || 0);
|
|
2481
|
+
|
|
2482
|
+
// Adjust for RTL text positioning
|
|
2483
|
+
if (direction === 'rtl' && textObj.textAlign === 'right') {
|
|
2484
|
+
actualLeft =
|
|
2485
|
+
boundingRect.left +
|
|
2486
|
+
boundingRect.width -
|
|
2487
|
+
actualWidth -
|
|
2488
|
+
(debug.textMetrics.actualBoundingBoxLeft || 0);
|
|
2489
|
+
}
|
|
2490
|
+
|
|
2491
|
+
const actualBounds = new fabric.Rect({
|
|
2492
|
+
left: actualLeft,
|
|
2493
|
+
top: baselineY - debug.textMetrics.actualBoundingBoxAscent,
|
|
2494
|
+
width: actualWidth,
|
|
2495
|
+
height:
|
|
2496
|
+
debug.textMetrics.actualBoundingBoxAscent +
|
|
2497
|
+
debug.textMetrics.actualBoundingBoxDescent,
|
|
2498
|
+
fill: 'rgba(0, 0, 255, 0.1)', // Semi-transparent blue fill
|
|
2499
|
+
stroke: 'blue',
|
|
2500
|
+
strokeWidth: 2,
|
|
2501
|
+
selectable: false,
|
|
2502
|
+
evented: false,
|
|
2503
|
+
name: 'debug-actual-bounds',
|
|
2504
|
+
originX: 'left',
|
|
2505
|
+
originY: 'top',
|
|
2506
|
+
});
|
|
2507
|
+
debugShapes.push(actualBounds);
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
|
|
2511
|
+
// Show baseline
|
|
2512
|
+
if (showBaseline) {
|
|
2513
|
+
// For RTL text, show baseline across the actual text width, not the full container
|
|
2514
|
+
let baselineLeft = boundingRect.left - 10;
|
|
2515
|
+
let baselineRight = boundingRect.left + boundingRect.width + 10;
|
|
2516
|
+
|
|
2517
|
+
if (direction === 'rtl' && textObj.textAlign === 'right') {
|
|
2518
|
+
const actualTextWidth = debug.textMetrics.width;
|
|
2519
|
+
baselineLeft =
|
|
2520
|
+
boundingRect.left + boundingRect.width - actualTextWidth - 10;
|
|
2521
|
+
baselineRight = boundingRect.left + boundingRect.width + 10;
|
|
2522
|
+
}
|
|
2523
|
+
|
|
2524
|
+
const baseline = new fabric.Line(
|
|
2525
|
+
[baselineLeft, baselineY, baselineRight, baselineY],
|
|
2526
|
+
{
|
|
2527
|
+
stroke: 'green',
|
|
2528
|
+
strokeWidth: 2,
|
|
2529
|
+
strokeDashArray: [3, 3],
|
|
2530
|
+
selectable: false,
|
|
2531
|
+
evented: false,
|
|
2532
|
+
name: 'debug-baseline',
|
|
2533
|
+
},
|
|
2534
|
+
);
|
|
2535
|
+
debugShapes.push(baseline);
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
// Show metrics info
|
|
2539
|
+
if (showMetrics) {
|
|
2540
|
+
// Position metrics info differently for RTL text
|
|
2541
|
+
let metricsLeft = boundingRect.left + boundingRect.width + 15;
|
|
2542
|
+
if (direction === 'rtl') {
|
|
2543
|
+
// For RTL, show metrics on the left side to avoid overlap
|
|
2544
|
+
metricsLeft = Math.max(10, boundingRect.left - 200);
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
const metricsText = new fabric.Text(
|
|
2548
|
+
`Direction: ${direction.toUpperCase()}\n` +
|
|
2549
|
+
`Align: ${textObj.textAlign || 'left'}\n` +
|
|
2550
|
+
`Char: ${debug.measurementChar} (${debug.charMetrics.width?.toFixed(1) || 'N/A'}px)\n` +
|
|
2551
|
+
`Font A: ${ascent.toFixed(1)}px\n` +
|
|
2552
|
+
`Font D: ${descent.toFixed(1)}px\n` +
|
|
2553
|
+
`Fabric: ${boundingRect.width.toFixed(1)}x${boundingRect.height.toFixed(1)}px\n` +
|
|
2554
|
+
`Text W: ${debug.textMetrics.width.toFixed(1)}px\n` +
|
|
2555
|
+
`Actual A: ${debug.textMetrics.actualBoundingBoxAscent?.toFixed(1) || 'N/A'}px`,
|
|
2556
|
+
{
|
|
2557
|
+
left: metricsLeft,
|
|
2558
|
+
top: baselineY - 60,
|
|
2559
|
+
fontSize: 11,
|
|
2560
|
+
fontFamily: 'monospace',
|
|
2561
|
+
fill: '#444',
|
|
2562
|
+
backgroundColor: 'rgba(255, 255, 255, 0.9)',
|
|
2563
|
+
selectable: false,
|
|
2564
|
+
evented: false,
|
|
2565
|
+
name: 'debug-metrics-info',
|
|
2566
|
+
},
|
|
2567
|
+
);
|
|
2568
|
+
debugShapes.push(metricsText);
|
|
2569
|
+
}
|
|
2570
|
+
|
|
2571
|
+
// Show text wrapping visualization
|
|
2572
|
+
if (showWrap && textObj.type === 'textbox') {
|
|
2573
|
+
const wrappingShapes = drawTextWrappingDebug(
|
|
2574
|
+
textObj,
|
|
2575
|
+
boundingRect,
|
|
2576
|
+
baselineY,
|
|
2577
|
+
ascent,
|
|
2578
|
+
descent,
|
|
2579
|
+
);
|
|
2580
|
+
debugShapes.push(...wrappingShapes);
|
|
2581
|
+
}
|
|
2582
|
+
|
|
2583
|
+
// Add all debug shapes to canvas
|
|
2584
|
+
debugShapes.forEach((shape) => canvas.add(shape));
|
|
2585
|
+
canvas.renderAll();
|
|
2586
|
+
|
|
2587
|
+
// Store reference for cleanup
|
|
2588
|
+
debugOverlay = debugShapes;
|
|
2589
|
+
}
|
|
2590
|
+
|
|
2591
|
+
function drawTextWrappingDebug(
|
|
2592
|
+
textObj,
|
|
2593
|
+
boundingRect,
|
|
2594
|
+
baselineY,
|
|
2595
|
+
ascent,
|
|
2596
|
+
descent,
|
|
2597
|
+
) {
|
|
2598
|
+
const wrappingShapes = [];
|
|
2599
|
+
const text = textObj.text || '';
|
|
2600
|
+
const fontSize = textObj.fontSize || 16;
|
|
2601
|
+
const fontFamily = textObj.fontFamily || 'Arial';
|
|
2602
|
+
const direction = textObj.direction || 'ltr';
|
|
2603
|
+
|
|
2604
|
+
if (!text) return wrappingShapes;
|
|
2605
|
+
|
|
2606
|
+
// Create a temporary canvas to measure text wrapping
|
|
2607
|
+
const tempCanvas = document.createElement('canvas');
|
|
2608
|
+
const ctx = tempCanvas.getContext('2d');
|
|
2609
|
+
ctx.font = `${textObj.fontStyle || 'normal'} ${textObj.fontWeight || 'normal'} ${fontSize}px ${fontFamily}`;
|
|
2610
|
+
ctx.direction = direction;
|
|
2611
|
+
ctx.textBaseline = 'alphabetic';
|
|
2612
|
+
|
|
2613
|
+
// Simulate text wrapping logic similar to Fabric.js
|
|
2614
|
+
const words = text.split(' ');
|
|
2615
|
+
const maxWidth = textObj.width || boundingRect.width;
|
|
2616
|
+
|
|
2617
|
+
console.log('🎯 DEBUG WRAP VISUALIZATION:');
|
|
2618
|
+
console.log(' textObj.width:', textObj.width);
|
|
2619
|
+
console.log(' boundingRect.width:', boundingRect.width);
|
|
2620
|
+
console.log(' Using maxWidth:', maxWidth);
|
|
2621
|
+
const lineHeight = fontSize * 1.16; // Fabric.js default line height
|
|
2622
|
+
|
|
2623
|
+
let currentLine = '';
|
|
2624
|
+
let lines = [];
|
|
2625
|
+
let currentY = baselineY;
|
|
2626
|
+
|
|
2627
|
+
// Simple word-wrapping algorithm
|
|
2628
|
+
for (let i = 0; i < words.length; i++) {
|
|
2629
|
+
const word = words[i];
|
|
2630
|
+
const testLine = currentLine + (currentLine ? ' ' : '') + word;
|
|
2631
|
+
const metrics = ctx.measureText(testLine);
|
|
2632
|
+
|
|
2633
|
+
if (metrics.width > maxWidth && currentLine !== '') {
|
|
2634
|
+
// Line is too long, wrap it
|
|
2635
|
+
lines.push({
|
|
2636
|
+
text: currentLine,
|
|
2637
|
+
y: currentY,
|
|
2638
|
+
width: ctx.measureText(currentLine).width,
|
|
2639
|
+
});
|
|
2640
|
+
currentLine = word;
|
|
2641
|
+
currentY += lineHeight;
|
|
2642
|
+
} else {
|
|
2643
|
+
currentLine = testLine;
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2647
|
+
// Add the last line
|
|
2648
|
+
if (currentLine) {
|
|
2649
|
+
lines.push({
|
|
2650
|
+
text: currentLine,
|
|
2651
|
+
y: currentY,
|
|
2652
|
+
width: ctx.measureText(currentLine).width,
|
|
2653
|
+
});
|
|
2654
|
+
}
|
|
2655
|
+
|
|
2656
|
+
// Draw line visualizations
|
|
2657
|
+
lines.forEach((line, index) => {
|
|
2658
|
+
// Line boundary boxes (orange)
|
|
2659
|
+
const lineBox = new fabric.Rect({
|
|
2660
|
+
left: boundingRect.left,
|
|
2661
|
+
top: line.y - ascent,
|
|
2662
|
+
width: maxWidth,
|
|
2663
|
+
height: lineHeight,
|
|
2664
|
+
fill: 'rgba(255, 165, 0, 0.1)',
|
|
2665
|
+
stroke: 'orange',
|
|
2666
|
+
strokeWidth: 1,
|
|
2667
|
+
strokeDashArray: [3, 3],
|
|
2668
|
+
selectable: false,
|
|
2669
|
+
evented: false,
|
|
2670
|
+
name: 'debug-line-box',
|
|
2671
|
+
originX: 'left',
|
|
2672
|
+
originY: 'top',
|
|
2673
|
+
});
|
|
2674
|
+
wrappingShapes.push(lineBox);
|
|
2675
|
+
|
|
2676
|
+
// Text width indicator (yellow) - properly positioned for RTL
|
|
2677
|
+
let textWidthLeft;
|
|
2678
|
+
if (direction === 'rtl') {
|
|
2679
|
+
// For RTL, the text should be aligned to the right side of the container
|
|
2680
|
+
textWidthLeft = boundingRect.left + maxWidth - line.width;
|
|
2681
|
+
} else {
|
|
2682
|
+
// For LTR, align to the left
|
|
2683
|
+
textWidthLeft = boundingRect.left;
|
|
2684
|
+
}
|
|
2685
|
+
|
|
2686
|
+
const textWidthBox = new fabric.Rect({
|
|
2687
|
+
left: textWidthLeft,
|
|
2688
|
+
top: line.y - ascent + 2,
|
|
2689
|
+
width: line.width,
|
|
2690
|
+
height: lineHeight - 4,
|
|
2691
|
+
fill: 'rgba(255, 255, 0, 0.3)', // Slightly more opaque for better visibility
|
|
2692
|
+
stroke: 'gold',
|
|
2693
|
+
strokeWidth: 1,
|
|
2694
|
+
selectable: false,
|
|
2695
|
+
evented: false,
|
|
2696
|
+
name: 'debug-text-width',
|
|
2697
|
+
originX: 'left',
|
|
2698
|
+
originY: 'top',
|
|
2699
|
+
});
|
|
2700
|
+
wrappingShapes.push(textWidthBox);
|
|
2701
|
+
|
|
2702
|
+
// Line number labels
|
|
2703
|
+
const lineLabel = new fabric.Text(`L${index + 1}`, {
|
|
2704
|
+
left: boundingRect.left - 25,
|
|
2705
|
+
top: line.y - 5,
|
|
2706
|
+
fontSize: 10,
|
|
2707
|
+
fontFamily: 'monospace',
|
|
2708
|
+
fill: 'orange',
|
|
2709
|
+
selectable: false,
|
|
2710
|
+
evented: false,
|
|
2711
|
+
name: 'debug-line-label',
|
|
2712
|
+
});
|
|
2713
|
+
wrappingShapes.push(lineLabel);
|
|
2714
|
+
});
|
|
2715
|
+
|
|
2716
|
+
// Add wrap width indicator (vertical line)
|
|
2717
|
+
const wrapIndicator = new fabric.Line(
|
|
2718
|
+
[
|
|
2719
|
+
boundingRect.left + maxWidth,
|
|
2720
|
+
boundingRect.top - 10,
|
|
2721
|
+
boundingRect.left + maxWidth,
|
|
2722
|
+
boundingRect.top + boundingRect.height + 10,
|
|
2723
|
+
],
|
|
2724
|
+
{
|
|
2725
|
+
stroke: 'red',
|
|
2726
|
+
strokeWidth: 2,
|
|
2727
|
+
strokeDashArray: [8, 4],
|
|
2728
|
+
selectable: false,
|
|
2729
|
+
evented: false,
|
|
2730
|
+
name: 'debug-wrap-indicator',
|
|
2731
|
+
},
|
|
2732
|
+
);
|
|
2733
|
+
wrappingShapes.push(wrapIndicator);
|
|
2734
|
+
|
|
2735
|
+
// Wrap width label
|
|
2736
|
+
const wrapLabel = new fabric.Text(`Wrap: ${maxWidth.toFixed(0)}px`, {
|
|
2737
|
+
left: boundingRect.left + maxWidth + 5,
|
|
2738
|
+
top: boundingRect.top - 15,
|
|
2739
|
+
fontSize: 10,
|
|
2740
|
+
fontFamily: 'monospace',
|
|
2741
|
+
fill: 'red',
|
|
2742
|
+
backgroundColor: 'rgba(255, 255, 255, 0.8)',
|
|
2743
|
+
selectable: false,
|
|
2744
|
+
evented: false,
|
|
2745
|
+
name: 'debug-wrap-label',
|
|
2746
|
+
});
|
|
2747
|
+
wrappingShapes.push(wrapLabel);
|
|
2748
|
+
|
|
2749
|
+
return wrappingShapes;
|
|
2750
|
+
}
|
|
2751
|
+
|
|
2752
|
+
function clearDebugOverlay() {
|
|
2753
|
+
if (debugOverlay) {
|
|
2754
|
+
debugOverlay.forEach((shape) => canvas.remove(shape));
|
|
2755
|
+
debugOverlay = null;
|
|
2756
|
+
canvas.renderAll();
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2760
|
+
function forceRefreshText() {
|
|
2761
|
+
const activeObject = canvas.getActiveObject();
|
|
2762
|
+
if (
|
|
2763
|
+
!activeObject ||
|
|
2764
|
+
(activeObject.type !== 'textbox' &&
|
|
2765
|
+
activeObject.type !== 'text' &&
|
|
2766
|
+
activeObject.type !== 'i-text')
|
|
2767
|
+
) {
|
|
2768
|
+
alert('Please select a text object first');
|
|
2769
|
+
return;
|
|
2770
|
+
}
|
|
2771
|
+
|
|
2772
|
+
console.log('🔄 Force refreshing text object...');
|
|
2773
|
+
|
|
2774
|
+
// Clear all caches
|
|
2775
|
+
if (typeof activeObject._clearCache === 'function') {
|
|
2776
|
+
activeObject._clearCache();
|
|
2777
|
+
}
|
|
2778
|
+
|
|
2779
|
+
// Force re-initialization
|
|
2780
|
+
activeObject.dirty = true;
|
|
2781
|
+
activeObject.initialized = false;
|
|
2782
|
+
|
|
2783
|
+
// For textboxes, force recalculation of dimensions
|
|
2784
|
+
if (activeObject.type === 'textbox') {
|
|
2785
|
+
// Store current properties
|
|
2786
|
+
const currentText = activeObject.text;
|
|
2787
|
+
const currentWidth = activeObject.width;
|
|
2788
|
+
|
|
2789
|
+
// Temporarily change text to force re-layout
|
|
2790
|
+
activeObject.set('text', currentText + ' ');
|
|
2791
|
+
canvas.renderAll();
|
|
2792
|
+
|
|
2793
|
+
// Reset to original text
|
|
2794
|
+
activeObject.set('text', currentText);
|
|
2795
|
+
activeObject.set('width', currentWidth);
|
|
2796
|
+
|
|
2797
|
+
// Force layout recalculation
|
|
2798
|
+
if (typeof activeObject.initDimensions === 'function') {
|
|
2799
|
+
activeObject.initDimensions();
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
|
|
2803
|
+
// Force complete re-render
|
|
2804
|
+
canvas.renderAll();
|
|
2805
|
+
|
|
2806
|
+
console.log('🔄 Text object refreshed');
|
|
2807
|
+
|
|
2808
|
+
// Automatically debug the refreshed text
|
|
2809
|
+
if (debugMode) {
|
|
2810
|
+
setTimeout(debugSelectedText, 100);
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
|
|
2814
|
+
function testWidthConstraints() {
|
|
2815
|
+
clearCanvas();
|
|
2816
|
+
|
|
2817
|
+
const arabicText = 'مرحبا بالعالم العربي';
|
|
2818
|
+
console.log(
|
|
2819
|
+
'🧪 Testing width constraints and dynamicMinWidth behavior...',
|
|
2820
|
+
);
|
|
2821
|
+
|
|
2822
|
+
// Test different width scenarios
|
|
2823
|
+
const testScenarios = [
|
|
2824
|
+
{ width: 300, desc: 'Wide (300px) - Should wrap normally' },
|
|
2825
|
+
{ width: 160, desc: 'Medium (160px) - At dynamic limit' },
|
|
2826
|
+
{ width: 120, desc: 'Narrow (120px) - Below dynamic limit' },
|
|
2827
|
+
{ width: 80, desc: 'Very narrow (80px) - Force character wrap' },
|
|
2828
|
+
];
|
|
2829
|
+
|
|
2830
|
+
let yPos = 120;
|
|
2831
|
+
|
|
2832
|
+
testScenarios.forEach((scenario, index) => {
|
|
2833
|
+
const xPos = 150;
|
|
2834
|
+
const currentYPos = yPos + index * 120;
|
|
2835
|
+
|
|
2836
|
+
// Create standard textbox
|
|
2837
|
+
const standardTextbox = new fabric.Textbox(arabicText, {
|
|
2838
|
+
left: xPos,
|
|
2839
|
+
top: currentYPos,
|
|
2840
|
+
fontSize: 20,
|
|
2841
|
+
fontFamily: 'STV Regular',
|
|
2842
|
+
fill: '#333',
|
|
2843
|
+
width: scenario.width,
|
|
2844
|
+
direction: 'rtl',
|
|
2845
|
+
textAlign: 'right',
|
|
2846
|
+
backgroundColor: 'rgba(255, 200, 200, 0.3)', // Light red background
|
|
2847
|
+
});
|
|
2848
|
+
|
|
2849
|
+
canvas.add(standardTextbox);
|
|
2850
|
+
|
|
2851
|
+
// Create forced-width textbox using our helper function
|
|
2852
|
+
const forcedTextbox = createTextboxWithForcedWidth(
|
|
2853
|
+
arabicText,
|
|
2854
|
+
{
|
|
2855
|
+
left: xPos + 350,
|
|
2856
|
+
top: currentYPos,
|
|
2857
|
+
fontSize: 20,
|
|
2858
|
+
fontFamily: 'STV Regular',
|
|
2859
|
+
fill: '#333',
|
|
2860
|
+
direction: 'rtl',
|
|
2861
|
+
textAlign: 'right',
|
|
2862
|
+
backgroundColor: 'rgba(200, 255, 200, 0.3)', // Light green background
|
|
2863
|
+
},
|
|
2864
|
+
scenario.width,
|
|
2865
|
+
);
|
|
2866
|
+
|
|
2867
|
+
canvas.add(forcedTextbox);
|
|
2868
|
+
|
|
2869
|
+
// Add labels
|
|
2870
|
+
const standardLabel = new fabric.Text(
|
|
2871
|
+
`Standard\n${scenario.desc}\nAuto-expansion: ${standardTextbox.dynamicMinWidth > scenario.width ? 'YES' : 'NO'}`,
|
|
2872
|
+
{
|
|
2873
|
+
left: xPos,
|
|
2874
|
+
top: currentYPos - 50,
|
|
2875
|
+
fontSize: 10,
|
|
2876
|
+
fontFamily: 'Arial',
|
|
2877
|
+
fill: '#666',
|
|
2878
|
+
textAlign: 'center',
|
|
2879
|
+
originX: 'center',
|
|
2880
|
+
},
|
|
2881
|
+
);
|
|
2882
|
+
|
|
2883
|
+
const forcedLabel = new fabric.Text(
|
|
2884
|
+
`Forced Width\n${scenario.desc}\nOverride: YES`,
|
|
2885
|
+
{
|
|
2886
|
+
left: xPos + 350,
|
|
2887
|
+
top: currentYPos - 50,
|
|
2888
|
+
fontSize: 10,
|
|
2889
|
+
fontFamily: 'Arial',
|
|
2890
|
+
fill: '#666',
|
|
2891
|
+
textAlign: 'center',
|
|
2892
|
+
originX: 'center',
|
|
2893
|
+
},
|
|
2894
|
+
);
|
|
2895
|
+
|
|
2896
|
+
canvas.add(standardLabel);
|
|
2897
|
+
canvas.add(forcedLabel);
|
|
2898
|
+
|
|
2899
|
+
console.log(`📊 ${scenario.desc}:`, {
|
|
2900
|
+
targetWidth: scenario.width,
|
|
2901
|
+
standardActualWidth: standardTextbox.width,
|
|
2902
|
+
standardDynamicMin: standardTextbox.dynamicMinWidth,
|
|
2903
|
+
forcedActualWidth: forcedTextbox.width,
|
|
2904
|
+
forcedDynamicMin: forcedTextbox.dynamicMinWidth,
|
|
2905
|
+
});
|
|
2906
|
+
});
|
|
2907
|
+
|
|
2908
|
+
// Add main title
|
|
2909
|
+
const title = new fabric.Text(
|
|
2910
|
+
'Width Constraint Test\nRed = Standard (auto-expansion) | Green = Forced Width Override',
|
|
2911
|
+
{
|
|
2912
|
+
left: 400,
|
|
2913
|
+
top: 50,
|
|
2914
|
+
fontSize: 14,
|
|
2915
|
+
fontFamily: 'Arial',
|
|
2916
|
+
fill: '#333',
|
|
2917
|
+
textAlign: 'center',
|
|
2918
|
+
originX: 'center',
|
|
2919
|
+
},
|
|
2920
|
+
);
|
|
2921
|
+
|
|
2922
|
+
canvas.add(title);
|
|
2923
|
+
canvas.renderAll();
|
|
2924
|
+
updateCanvasInfo();
|
|
2925
|
+
|
|
2926
|
+
console.log('🧪 Width constraints test complete!');
|
|
2927
|
+
console.log(
|
|
2928
|
+
'📋 Compare the red (standard) vs green (forced) textboxes',
|
|
2929
|
+
);
|
|
2930
|
+
console.log(
|
|
2931
|
+
'💡 Notice how standard textboxes auto-expand when text is wider than container',
|
|
2932
|
+
);
|
|
2933
|
+
}
|
|
2934
|
+
|
|
2935
|
+
function testForcedNarrowText() {
|
|
2936
|
+
clearCanvas();
|
|
2937
|
+
|
|
2938
|
+
const texts = [
|
|
2939
|
+
'مرحبا بالعالم العربي',
|
|
2940
|
+
'This is a long English text that should wrap',
|
|
2941
|
+
'Mixed: Hello مرحبا World عالم!',
|
|
2942
|
+
];
|
|
2943
|
+
|
|
2944
|
+
console.log('🧪 Testing forced narrow text wrapping...');
|
|
2945
|
+
|
|
2946
|
+
texts.forEach((text, index) => {
|
|
2947
|
+
const yPos = 150 + index * 150;
|
|
2948
|
+
|
|
2949
|
+
// Create very narrow textboxes (80px) with different methods
|
|
2950
|
+
const methods = [
|
|
2951
|
+
{ name: 'Standard', bg: 'rgba(255, 200, 200, 0.3)' },
|
|
2952
|
+
{ name: 'Forced + Character Wrap', bg: 'rgba(200, 255, 200, 0.3)' },
|
|
2953
|
+
{ name: 'Forced + Word Wrap', bg: 'rgba(200, 200, 255, 0.3)' },
|
|
2954
|
+
];
|
|
2955
|
+
|
|
2956
|
+
methods.forEach((method, methodIndex) => {
|
|
2957
|
+
const xPos = 150 + methodIndex * 200;
|
|
2958
|
+
let textbox;
|
|
2959
|
+
|
|
2960
|
+
if (methodIndex === 0) {
|
|
2961
|
+
// Standard textbox
|
|
2962
|
+
textbox = new fabric.Textbox(text, {
|
|
2963
|
+
left: xPos,
|
|
2964
|
+
top: yPos,
|
|
2965
|
+
fontSize: 16,
|
|
2966
|
+
fontFamily: 'Arial',
|
|
2967
|
+
fill: '#333',
|
|
2968
|
+
width: 80,
|
|
2969
|
+
direction: text.includes('مرحبا') ? 'rtl' : 'ltr',
|
|
2970
|
+
textAlign: text.includes('مرحبا') ? 'right' : 'left',
|
|
2971
|
+
backgroundColor: method.bg,
|
|
2972
|
+
});
|
|
2973
|
+
} else if (methodIndex === 1) {
|
|
2974
|
+
// Forced width with character wrapping
|
|
2975
|
+
textbox = createTextboxWithForcedWidth(
|
|
2976
|
+
text,
|
|
2977
|
+
{
|
|
2978
|
+
left: xPos,
|
|
2979
|
+
top: yPos,
|
|
2980
|
+
fontSize: 16,
|
|
2981
|
+
fontFamily: 'Arial',
|
|
2982
|
+
fill: '#333',
|
|
2983
|
+
direction: text.includes('مرحبا') ? 'rtl' : 'ltr',
|
|
2984
|
+
textAlign: text.includes('مرحبا') ? 'right' : 'left',
|
|
2985
|
+
backgroundColor: method.bg,
|
|
2986
|
+
splitByGrapheme: true, // Character-based wrapping
|
|
2987
|
+
},
|
|
2988
|
+
80,
|
|
2989
|
+
);
|
|
2990
|
+
} else {
|
|
2991
|
+
// Forced width with word wrapping
|
|
2992
|
+
textbox = createTextboxWithForcedWidth(
|
|
2993
|
+
text,
|
|
2994
|
+
{
|
|
2995
|
+
left: xPos,
|
|
2996
|
+
top: yPos,
|
|
2997
|
+
fontSize: 16,
|
|
2998
|
+
fontFamily: 'Arial',
|
|
2999
|
+
fill: '#333',
|
|
3000
|
+
direction: text.includes('مرحبا') ? 'rtl' : 'ltr',
|
|
3001
|
+
textAlign: text.includes('مرحبا') ? 'right' : 'left',
|
|
3002
|
+
backgroundColor: method.bg,
|
|
3003
|
+
splitByGrapheme: false, // Word-based wrapping
|
|
3004
|
+
},
|
|
3005
|
+
80,
|
|
3006
|
+
);
|
|
3007
|
+
}
|
|
3008
|
+
|
|
3009
|
+
canvas.add(textbox);
|
|
3010
|
+
|
|
3011
|
+
// Add method label
|
|
3012
|
+
const label = new fabric.Text(
|
|
3013
|
+
`${method.name}\nWidth: ${textbox.width}px\nDynMin: ${textbox.dynamicMinWidth}px`,
|
|
3014
|
+
{
|
|
3015
|
+
left: xPos,
|
|
3016
|
+
top: yPos - 40,
|
|
3017
|
+
fontSize: 9,
|
|
3018
|
+
fontFamily: 'Arial',
|
|
3019
|
+
fill: '#666',
|
|
3020
|
+
textAlign: 'center',
|
|
3021
|
+
originX: 'center',
|
|
3022
|
+
},
|
|
3023
|
+
);
|
|
3024
|
+
|
|
3025
|
+
canvas.add(label);
|
|
3026
|
+
});
|
|
3027
|
+
|
|
3028
|
+
// Add text sample label
|
|
3029
|
+
const sampleLabel = new fabric.Text(
|
|
3030
|
+
`Text ${index + 1}: "${text.substring(0, 20)}..."`,
|
|
3031
|
+
{
|
|
3032
|
+
left: 50,
|
|
3033
|
+
top: yPos + 20,
|
|
3034
|
+
fontSize: 10,
|
|
3035
|
+
fontFamily: 'Arial',
|
|
3036
|
+
fill: '#999',
|
|
3037
|
+
textAlign: 'left',
|
|
3038
|
+
},
|
|
3039
|
+
);
|
|
3040
|
+
|
|
3041
|
+
canvas.add(sampleLabel);
|
|
3042
|
+
});
|
|
3043
|
+
|
|
3044
|
+
// Add main title
|
|
3045
|
+
const title = new fabric.Text(
|
|
3046
|
+
'Forced Narrow Text Test (80px width)\nRed = Standard | Green = Forced + Char Wrap | Blue = Forced + Word Wrap',
|
|
3047
|
+
{
|
|
3048
|
+
left: 400,
|
|
3049
|
+
top: 50,
|
|
3050
|
+
fontSize: 14,
|
|
3051
|
+
fontFamily: 'Arial',
|
|
3052
|
+
fill: '#333',
|
|
3053
|
+
textAlign: 'center',
|
|
3054
|
+
originX: 'center',
|
|
3055
|
+
},
|
|
3056
|
+
);
|
|
3057
|
+
|
|
3058
|
+
canvas.add(title);
|
|
3059
|
+
canvas.renderAll();
|
|
3060
|
+
updateCanvasInfo();
|
|
3061
|
+
|
|
3062
|
+
console.log('🧪 Forced narrow text test complete!');
|
|
3063
|
+
console.log(
|
|
3064
|
+
'📋 Compare how different methods handle very narrow containers',
|
|
3065
|
+
);
|
|
3066
|
+
}
|
|
3067
|
+
|
|
3068
|
+
function testUnlimitedHeight() {
|
|
3069
|
+
clearCanvas();
|
|
3070
|
+
|
|
3071
|
+
// Create a very long Arabic text that should create many lines
|
|
3072
|
+
const veryLongArabicText =
|
|
3073
|
+
'مرحبا بالعالم العربي هذا نص عربي طويل جداً يجب أن يتم تقسيمه على عدة أسطر كثيرة جداً لاختبار نظام التفاف النص والارتفاع غير المحدود في النصوص العربية من اليمين إلى اليسار ونريد أن نرى كم سطر يمكن أن نحصل عليه مع النص الطويل جداً هذا وهل سيتوقف عند حد معين أم سيستمر في النمو حسب النص المتاح وهذا مهم جداً لفهم طريقة عمل النظام';
|
|
3074
|
+
|
|
3075
|
+
console.log(
|
|
3076
|
+
'🧪 Testing unlimited height growth vs height-constrained textboxes with STV Regular font...',
|
|
3077
|
+
);
|
|
3078
|
+
|
|
3079
|
+
const testWidth = 120; // Very narrow to force many wrapping lines
|
|
3080
|
+
|
|
3081
|
+
// Test with standard textbox (height constrained)
|
|
3082
|
+
const standardTextbox = new fabric.Textbox(veryLongArabicText, {
|
|
3083
|
+
left: 150,
|
|
3084
|
+
top: 100,
|
|
3085
|
+
fontSize: 18,
|
|
3086
|
+
fontFamily: 'STV Regular',
|
|
3087
|
+
fill: '#333',
|
|
3088
|
+
width: testWidth,
|
|
3089
|
+
direction: 'rtl',
|
|
3090
|
+
textAlign: 'right',
|
|
3091
|
+
backgroundColor: 'rgba(255, 200, 200, 0.3)', // Light red
|
|
3092
|
+
enableAdvancedLayout: false, // Use consistent layout system
|
|
3093
|
+
wrap: 'word', // Explicit word wrapping
|
|
3094
|
+
});
|
|
3095
|
+
|
|
3096
|
+
// Override width constraints but keep height constraint
|
|
3097
|
+
standardTextbox.minWidth = 10;
|
|
3098
|
+
standardTextbox.dynamicMinWidth = 0;
|
|
3099
|
+
standardTextbox.initDimensions();
|
|
3100
|
+
|
|
3101
|
+
canvas.add(standardTextbox);
|
|
3102
|
+
|
|
3103
|
+
// Test with unlimited height textbox
|
|
3104
|
+
const unlimitedTextbox = createTextboxWithUnlimitedHeight(
|
|
3105
|
+
veryLongArabicText,
|
|
3106
|
+
{
|
|
3107
|
+
left: 300,
|
|
3108
|
+
top: 100,
|
|
3109
|
+
fontSize: 18,
|
|
3110
|
+
fontFamily: 'STV Regular',
|
|
3111
|
+
fill: '#333',
|
|
3112
|
+
direction: 'rtl',
|
|
3113
|
+
textAlign: 'right',
|
|
3114
|
+
backgroundColor: 'rgba(200, 255, 200, 0.3)', // Light green
|
|
3115
|
+
enableAdvancedLayout: false, // Use consistent layout system
|
|
3116
|
+
wrap: 'word', // Explicit word wrapping
|
|
3117
|
+
},
|
|
3118
|
+
testWidth,
|
|
3119
|
+
);
|
|
3120
|
+
|
|
3121
|
+
canvas.add(unlimitedTextbox);
|
|
3122
|
+
|
|
3123
|
+
// Add labels
|
|
3124
|
+
const standardLabel = new fabric.Text(
|
|
3125
|
+
`Standard Textbox\nHeight Constrained\nWidth: ${standardTextbox.width}px\nHeight: ${standardTextbox.height.toFixed(1)}px\nLines: ${standardTextbox._textLines?.length || 0}`,
|
|
3126
|
+
{
|
|
3127
|
+
left: 150,
|
|
3128
|
+
top: 50,
|
|
3129
|
+
fontSize: 10,
|
|
3130
|
+
fontFamily: 'Arial',
|
|
3131
|
+
fill: '#666',
|
|
3132
|
+
textAlign: 'center',
|
|
3133
|
+
originX: 'center',
|
|
3134
|
+
},
|
|
3135
|
+
);
|
|
3136
|
+
|
|
3137
|
+
const unlimitedLabel = new fabric.Text(
|
|
3138
|
+
`Unlimited Height Textbox\nNo Height Constraint\nWidth: ${unlimitedTextbox.width}px\nHeight: ${unlimitedTextbox.height.toFixed(1)}px\nLines: ${unlimitedTextbox._textLines?.length || 0}`,
|
|
3139
|
+
{
|
|
3140
|
+
left: 300,
|
|
3141
|
+
top: 50,
|
|
3142
|
+
fontSize: 10,
|
|
3143
|
+
fontFamily: 'Arial',
|
|
3144
|
+
fill: '#666',
|
|
3145
|
+
textAlign: 'center',
|
|
3146
|
+
originX: 'center',
|
|
3147
|
+
},
|
|
3148
|
+
);
|
|
3149
|
+
|
|
3150
|
+
canvas.add(standardLabel);
|
|
3151
|
+
canvas.add(unlimitedLabel);
|
|
3152
|
+
|
|
3153
|
+
// Add main title
|
|
3154
|
+
const title = new fabric.Text(
|
|
3155
|
+
'Unlimited Height Test with STV Regular Font\nRed = Standard (height constrained) | Green = Unlimited Height Growth',
|
|
3156
|
+
{
|
|
3157
|
+
left: 400,
|
|
3158
|
+
top: 20,
|
|
3159
|
+
fontSize: 14,
|
|
3160
|
+
fontFamily: 'Arial',
|
|
3161
|
+
fill: '#333',
|
|
3162
|
+
textAlign: 'center',
|
|
3163
|
+
originX: 'center',
|
|
3164
|
+
},
|
|
3165
|
+
);
|
|
3166
|
+
|
|
3167
|
+
canvas.add(title);
|
|
3168
|
+
canvas.renderAll();
|
|
3169
|
+
updateCanvasInfo();
|
|
3170
|
+
|
|
3171
|
+
console.log('📊 Height constraint comparison:', {
|
|
3172
|
+
standardHeight: standardTextbox.height.toFixed(1) + 'px',
|
|
3173
|
+
standardLines: standardTextbox._textLines?.length || 0,
|
|
3174
|
+
unlimitedHeight: unlimitedTextbox.height.toFixed(1) + 'px',
|
|
3175
|
+
unlimitedLines: unlimitedTextbox._textLines?.length || 0,
|
|
3176
|
+
difference: `${(((unlimitedTextbox.height - standardTextbox.height) / standardTextbox.height) * 100).toFixed(1)}% taller`,
|
|
3177
|
+
});
|
|
3178
|
+
|
|
3179
|
+
// Enable debug mode to see wrapping visualization
|
|
3180
|
+
if (!debugMode) {
|
|
3181
|
+
toggleDebugMode();
|
|
3182
|
+
document.getElementById('debugShowWrap').checked = true;
|
|
3183
|
+
}
|
|
3184
|
+
|
|
3185
|
+
// Auto-select the unlimited textbox for debug comparison
|
|
3186
|
+
setTimeout(() => {
|
|
3187
|
+
canvas.setActiveObject(unlimitedTextbox);
|
|
3188
|
+
debugSelectedText();
|
|
3189
|
+
}, 500);
|
|
3190
|
+
|
|
3191
|
+
console.log('🧪 Unlimited height test complete!');
|
|
3192
|
+
console.log(
|
|
3193
|
+
'💡 Compare red (height constrained) vs green (unlimited height) textboxes',
|
|
3194
|
+
);
|
|
3195
|
+
console.log(
|
|
3196
|
+
'📏 The green box should show many more lines than the red box',
|
|
3197
|
+
);
|
|
3198
|
+
console.log(
|
|
3199
|
+
'🔍 Debug visualization enabled - compare wrapping visualization with actual text',
|
|
3200
|
+
);
|
|
3201
|
+
}
|
|
3202
|
+
|
|
3203
|
+
function testWrappingConsistency() {
|
|
3204
|
+
clearCanvas();
|
|
3205
|
+
|
|
3206
|
+
const testText = 'مرحبا بالعالم العربي هذا نص طويل';
|
|
3207
|
+
console.log(
|
|
3208
|
+
'🧪 Testing wrapping consistency between debug visualization and actual text...',
|
|
3209
|
+
);
|
|
3210
|
+
|
|
3211
|
+
// Create textbox with STV Regular font
|
|
3212
|
+
const textbox = createTextboxWithUnlimitedHeight(
|
|
3213
|
+
testText,
|
|
3214
|
+
{
|
|
3215
|
+
left: 300,
|
|
3216
|
+
top: 150,
|
|
3217
|
+
fontSize: 20,
|
|
3218
|
+
fontFamily: 'STV Regular',
|
|
3219
|
+
fill: '#333',
|
|
3220
|
+
direction: 'rtl',
|
|
3221
|
+
textAlign: 'right',
|
|
3222
|
+
backgroundColor: 'rgba(255, 255, 0, 0.1)', // Light yellow background
|
|
3223
|
+
enableAdvancedLayout: false,
|
|
3224
|
+
wrap: 'word',
|
|
3225
|
+
},
|
|
3226
|
+
120,
|
|
3227
|
+
);
|
|
3228
|
+
|
|
3229
|
+
canvas.add(textbox);
|
|
3230
|
+
|
|
3231
|
+
// Add title
|
|
3232
|
+
const title = new fabric.Text(
|
|
3233
|
+
'Wrapping Consistency Test\nCompare actual text vs debug wrapping visualization',
|
|
3234
|
+
{
|
|
3235
|
+
left: 400,
|
|
3236
|
+
top: 50,
|
|
3237
|
+
fontSize: 14,
|
|
3238
|
+
fontFamily: 'Arial',
|
|
3239
|
+
fill: '#333',
|
|
3240
|
+
textAlign: 'center',
|
|
3241
|
+
originX: 'center',
|
|
3242
|
+
},
|
|
3243
|
+
);
|
|
3244
|
+
|
|
3245
|
+
canvas.add(title);
|
|
3246
|
+
|
|
3247
|
+
// Enable debug mode automatically
|
|
3248
|
+
if (!debugMode) {
|
|
3249
|
+
toggleDebugMode();
|
|
3250
|
+
}
|
|
3251
|
+
|
|
3252
|
+
// Enable all debug options
|
|
3253
|
+
document.getElementById('debugShowBounds').checked = true;
|
|
3254
|
+
document.getElementById('debugShowBaseline').checked = true;
|
|
3255
|
+
document.getElementById('debugShowMetrics').checked = true;
|
|
3256
|
+
document.getElementById('debugShowWrap').checked = true;
|
|
3257
|
+
|
|
3258
|
+
// Select textbox and run debug
|
|
3259
|
+
setTimeout(() => {
|
|
3260
|
+
canvas.setActiveObject(textbox);
|
|
3261
|
+
debugSelectedText();
|
|
3262
|
+
|
|
3263
|
+
// Log detailed comparison
|
|
3264
|
+
console.log('🔍 WRAPPING ANALYSIS:');
|
|
3265
|
+
console.log('📊 Textbox details:', {
|
|
3266
|
+
text: textbox.text,
|
|
3267
|
+
width: textbox.width + 'px',
|
|
3268
|
+
height: textbox.height + 'px',
|
|
3269
|
+
lines: textbox._textLines?.length || 0,
|
|
3270
|
+
actualLines: textbox._textLines || [],
|
|
3271
|
+
});
|
|
3272
|
+
|
|
3273
|
+
// Compare with what debug system predicts
|
|
3274
|
+
const debug = getDebugTextMetrics(textbox);
|
|
3275
|
+
console.log('🎯 Debug prediction vs Reality:');
|
|
3276
|
+
console.log(
|
|
3277
|
+
' Fabric lines:',
|
|
3278
|
+
textbox._textLines?.map((line, i) => `${i + 1}: "${line}"`),
|
|
3279
|
+
);
|
|
3280
|
+
}, 1000);
|
|
3281
|
+
|
|
3282
|
+
canvas.renderAll();
|
|
3283
|
+
updateCanvasInfo();
|
|
3284
|
+
|
|
3285
|
+
console.log('🧪 Wrapping consistency test setup complete!');
|
|
3286
|
+
console.log('📋 Look at the yellow textbox and compare:');
|
|
3287
|
+
console.log(' • Orange boxes: Debug predicted line boundaries');
|
|
3288
|
+
console.log(' • Yellow boxes: Debug predicted text width per line');
|
|
3289
|
+
console.log(
|
|
3290
|
+
' • Actual text: How Fabric.js actually wrapped the text',
|
|
3291
|
+
);
|
|
3292
|
+
}
|
|
3293
|
+
|
|
3294
|
+
function testArabicFonts() {
|
|
3295
|
+
clearCanvas();
|
|
3296
|
+
|
|
3297
|
+
const arabicText = 'مرحبا بالعالم العربي';
|
|
3298
|
+
const mixedText = 'Hello مرحبا World';
|
|
3299
|
+
const longArabicText =
|
|
3300
|
+
'هذا نص عربي طويل جداً يجب أن يتم تقسيمه على عدة أسطر لاختبار نظام التفاف النص في النصوص من اليمين إلى اليسار';
|
|
3301
|
+
|
|
3302
|
+
const testCases = [
|
|
3303
|
+
{ text: arabicText, desc: 'Short Arabic' },
|
|
3304
|
+
{ text: mixedText, desc: 'Mixed LTR/RTL' },
|
|
3305
|
+
{ text: longArabicText, desc: 'Long wrapping Arabic' },
|
|
3306
|
+
];
|
|
3307
|
+
|
|
3308
|
+
const fonts = ['Arial', 'STV Regular', 'STV Bold', 'STV Light'];
|
|
3309
|
+
let yPos = 120;
|
|
3310
|
+
|
|
3311
|
+
console.log(
|
|
3312
|
+
'🧪 Testing Arabic fonts with improved debug visualization...',
|
|
3313
|
+
);
|
|
3314
|
+
|
|
3315
|
+
fonts.forEach((font, fontIndex) => {
|
|
3316
|
+
testCases.forEach((testCase, caseIndex) => {
|
|
3317
|
+
const xPos = 150 + caseIndex * 250;
|
|
3318
|
+
const currentYPos = yPos + fontIndex * 140;
|
|
3319
|
+
|
|
3320
|
+
const textObj = new fabric.Textbox(testCase.text, {
|
|
3321
|
+
left: xPos,
|
|
3322
|
+
top: currentYPos,
|
|
3323
|
+
fontSize: 24,
|
|
3324
|
+
fontFamily: font,
|
|
3325
|
+
fill: '#333',
|
|
3326
|
+
width: 200,
|
|
3327
|
+
direction: 'rtl',
|
|
3328
|
+
textAlign: 'right',
|
|
3329
|
+
enableAdvancedLayout: false, // Enable new measurement system!
|
|
3330
|
+
// Add slight background to make debug overlay more visible
|
|
3331
|
+
backgroundColor: 'rgba(255, 255, 255, 0.8)',
|
|
3332
|
+
});
|
|
3333
|
+
|
|
3334
|
+
canvas.add(textObj);
|
|
3335
|
+
|
|
3336
|
+
// Add descriptive labels
|
|
3337
|
+
const label = new fabric.Text(`${font}\n${testCase.desc}`, {
|
|
3338
|
+
left: xPos,
|
|
3339
|
+
top: currentYPos - 30,
|
|
3340
|
+
fontSize: 10,
|
|
3341
|
+
fontFamily: 'Arial',
|
|
3342
|
+
fill: '#666',
|
|
3343
|
+
textAlign: 'center',
|
|
3344
|
+
originX: 'center',
|
|
3345
|
+
});
|
|
3346
|
+
|
|
3347
|
+
canvas.add(label);
|
|
3348
|
+
|
|
3349
|
+
console.log(`🔤 Added test case: ${font} - ${testCase.desc}`);
|
|
3350
|
+
});
|
|
3351
|
+
});
|
|
3352
|
+
|
|
3353
|
+
canvas.renderAll();
|
|
3354
|
+
updateCanvasInfo();
|
|
3355
|
+
|
|
3356
|
+
// Automatically enable debug mode and show all debug options
|
|
3357
|
+
if (!debugMode) {
|
|
3358
|
+
toggleDebugMode();
|
|
3359
|
+
}
|
|
3360
|
+
|
|
3361
|
+
// Enable all debug visualizations by default
|
|
3362
|
+
document.getElementById('debugShowBounds').checked = true;
|
|
3363
|
+
document.getElementById('debugShowBaseline').checked = true;
|
|
3364
|
+
document.getElementById('debugShowMetrics').checked = true;
|
|
3365
|
+
document.getElementById('debugShowWrap').checked = true;
|
|
3366
|
+
|
|
3367
|
+
console.log(
|
|
3368
|
+
'🧪 Arabic font test complete with improved RTL debug visualization!',
|
|
3369
|
+
);
|
|
3370
|
+
console.log('📋 Debug features enabled:');
|
|
3371
|
+
console.log(' • Red boxes: Font metrics bounds');
|
|
3372
|
+
console.log(' • Blue boxes: Actual text bounds');
|
|
3373
|
+
console.log(' • Purple boxes: Fabric.js calculated bounds');
|
|
3374
|
+
console.log(' • Green lines: Text baseline');
|
|
3375
|
+
console.log(' • Yellow boxes: Text wrapping visualization');
|
|
3376
|
+
console.log(' • Metrics info: Positioned appropriately for RTL text');
|
|
3377
|
+
console.log(
|
|
3378
|
+
'🔍 Click on any text object to see detailed debug information',
|
|
3379
|
+
);
|
|
3380
|
+
}
|
|
3381
|
+
|
|
3382
|
+
// Event listeners for debug checkboxes
|
|
3383
|
+
document
|
|
3384
|
+
.getElementById('debugShowBounds')
|
|
3385
|
+
.addEventListener('change', function () {
|
|
3386
|
+
if (debugMode) {
|
|
3387
|
+
const activeObject = canvas.getActiveObject();
|
|
3388
|
+
if (
|
|
3389
|
+
activeObject &&
|
|
3390
|
+
(activeObject.type === 'textbox' ||
|
|
3391
|
+
activeObject.type === 'text' ||
|
|
3392
|
+
activeObject.type === 'i-text')
|
|
3393
|
+
) {
|
|
3394
|
+
debugSelectedText();
|
|
3395
|
+
}
|
|
3396
|
+
}
|
|
3397
|
+
});
|
|
3398
|
+
|
|
3399
|
+
document
|
|
3400
|
+
.getElementById('debugShowBaseline')
|
|
3401
|
+
.addEventListener('change', function () {
|
|
3402
|
+
if (debugMode) {
|
|
3403
|
+
const activeObject = canvas.getActiveObject();
|
|
3404
|
+
if (
|
|
3405
|
+
activeObject &&
|
|
3406
|
+
(activeObject.type === 'textbox' ||
|
|
3407
|
+
activeObject.type === 'text' ||
|
|
3408
|
+
activeObject.type === 'i-text')
|
|
3409
|
+
) {
|
|
3410
|
+
debugSelectedText();
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
});
|
|
3414
|
+
|
|
3415
|
+
document
|
|
3416
|
+
.getElementById('debugShowMetrics')
|
|
3417
|
+
.addEventListener('change', function () {
|
|
3418
|
+
if (debugMode) {
|
|
3419
|
+
const activeObject = canvas.getActiveObject();
|
|
3420
|
+
if (
|
|
3421
|
+
activeObject &&
|
|
3422
|
+
(activeObject.type === 'textbox' ||
|
|
3423
|
+
activeObject.type === 'text' ||
|
|
3424
|
+
activeObject.type === 'i-text')
|
|
3425
|
+
) {
|
|
3426
|
+
debugSelectedText();
|
|
3427
|
+
}
|
|
3428
|
+
}
|
|
3429
|
+
});
|
|
3430
|
+
|
|
3431
|
+
document
|
|
3432
|
+
.getElementById('debugShowWrap')
|
|
3433
|
+
.addEventListener('change', function () {
|
|
3434
|
+
if (debugMode) {
|
|
3435
|
+
const activeObject = canvas.getActiveObject();
|
|
3436
|
+
if (
|
|
3437
|
+
activeObject &&
|
|
3438
|
+
(activeObject.type === 'textbox' ||
|
|
3439
|
+
activeObject.type === 'text' ||
|
|
3440
|
+
activeObject.type === 'i-text')
|
|
3441
|
+
) {
|
|
3442
|
+
debugSelectedText();
|
|
3443
|
+
}
|
|
3444
|
+
}
|
|
3445
|
+
});
|
|
3446
|
+
|
|
3447
|
+
// Auto-debug on selection change
|
|
3448
|
+
canvas.on('selection:created', function () {
|
|
3449
|
+
if (debugMode) {
|
|
3450
|
+
setTimeout(debugSelectedText, 100); // Small delay to ensure selection is ready
|
|
3451
|
+
}
|
|
3452
|
+
});
|
|
3453
|
+
|
|
3454
|
+
canvas.on('selection:updated', function () {
|
|
3455
|
+
if (debugMode) {
|
|
3456
|
+
setTimeout(debugSelectedText, 100);
|
|
3457
|
+
}
|
|
3458
|
+
});
|
|
3459
|
+
|
|
3460
|
+
canvas.on('selection:cleared', function () {
|
|
3461
|
+
clearDebugOverlay();
|
|
3462
|
+
if (debugMode) {
|
|
3463
|
+
const debugInfo = document.getElementById('debugInfo');
|
|
3464
|
+
debugInfo.innerHTML =
|
|
3465
|
+
'<strong>🔍 Debug Mode: ON</strong><br>Select a text object to see debug info';
|
|
3466
|
+
}
|
|
3467
|
+
});
|
|
3468
|
+
|
|
3469
|
+
// Live debug updates when resizing/moving textbox
|
|
3470
|
+
canvas.on('object:scaling', function (e) {
|
|
3471
|
+
if (
|
|
3472
|
+
debugMode &&
|
|
3473
|
+
e.target &&
|
|
3474
|
+
(e.target.type === 'textbox' ||
|
|
3475
|
+
e.target.type === 'text' ||
|
|
3476
|
+
e.target.type === 'i-text')
|
|
3477
|
+
) {
|
|
3478
|
+
setTimeout(() => {
|
|
3479
|
+
console.log('📏 LIVE RESIZE UPDATE:');
|
|
3480
|
+
debugSelectedText();
|
|
3481
|
+
}, 50);
|
|
3482
|
+
}
|
|
3483
|
+
});
|
|
3484
|
+
|
|
3485
|
+
canvas.on('object:resizing', function (e) {
|
|
3486
|
+
if (
|
|
3487
|
+
debugMode &&
|
|
3488
|
+
e.target &&
|
|
3489
|
+
(e.target.type === 'textbox' ||
|
|
3490
|
+
e.target.type === 'text' ||
|
|
3491
|
+
e.target.type === 'i-text')
|
|
3492
|
+
) {
|
|
3493
|
+
setTimeout(() => {
|
|
3494
|
+
console.log('📏 LIVE RESIZE UPDATE:');
|
|
3495
|
+
debugSelectedText();
|
|
3496
|
+
}, 50);
|
|
3497
|
+
}
|
|
3498
|
+
});
|
|
3499
|
+
|
|
3500
|
+
canvas.on('object:modified', function (e) {
|
|
3501
|
+
if (
|
|
3502
|
+
debugMode &&
|
|
3503
|
+
e.target &&
|
|
3504
|
+
(e.target.type === 'textbox' ||
|
|
3505
|
+
e.target.type === 'text' ||
|
|
3506
|
+
e.target.type === 'i-text')
|
|
3507
|
+
) {
|
|
3508
|
+
setTimeout(() => {
|
|
3509
|
+
console.log('📏 TEXTBOX MODIFIED:');
|
|
3510
|
+
debugSelectedText();
|
|
3511
|
+
}, 100);
|
|
3512
|
+
}
|
|
3513
|
+
});
|
|
3514
|
+
|
|
3515
|
+
// Also update during dragging for immediate feedback
|
|
3516
|
+
canvas.on('object:moving', function (e) {
|
|
3517
|
+
if (
|
|
3518
|
+
debugMode &&
|
|
3519
|
+
e.target &&
|
|
3520
|
+
(e.target.type === 'textbox' ||
|
|
3521
|
+
e.target.type === 'text' ||
|
|
3522
|
+
e.target.type === 'i-text')
|
|
3523
|
+
) {
|
|
3524
|
+
// Only update debug overlay, not full console logs (too noisy during drag)
|
|
3525
|
+
setTimeout(() => {
|
|
3526
|
+
const debug = getDebugTextMetrics(e.target);
|
|
3527
|
+
drawDebugOverlay(e.target, debug);
|
|
3528
|
+
}, 10);
|
|
3529
|
+
}
|
|
3530
|
+
});
|
|
3531
|
+
|
|
3532
|
+
// Log Fabric.js version
|
|
3533
|
+
console.log('Fabric.js version:', fabric.version);
|
|
3534
|
+
console.log('Canvas initialized with useOverlayEditing support');
|
|
3535
|
+
console.log(
|
|
3536
|
+
'🔍 Debug visualization enabled - use Debug controls to analyze text metrics',
|
|
3537
|
+
);
|
|
3538
|
+
console.log(
|
|
3539
|
+
'🎨 Corner radius support added for all shapes (Triangle, Polygon, etc.)',
|
|
3540
|
+
);
|
|
3541
|
+
|
|
3542
|
+
// Test overlay editing on double-click
|
|
3543
|
+
canvas.on('mouse:dblclick', function (options) {
|
|
3544
|
+
if (options.target && options.target.type === 'textbox') {
|
|
3545
|
+
console.log(
|
|
3546
|
+
'Double-clicked textbox - overlay editing should activate',
|
|
3547
|
+
);
|
|
3548
|
+
}
|
|
3549
|
+
});
|
|
3550
|
+
</script>
|
|
3551
|
+
</body>
|
|
3552
|
+
</html>
|