@nasser-sw/fabric 7.0.1-beta16 → 7.0.1-beta17
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/.claude/settings.local.json +7 -0
- package/dist/index.js +1982 -649
- 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 +1982 -649
- package/dist/index.mjs.map +1 -1
- package/dist/index.node.cjs +1982 -649
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.mjs +1982 -649
- 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/IText/IText.d.ts +31 -6
- package/dist/src/shapes/IText/IText.d.ts.map +1 -1
- package/dist/src/shapes/IText/IText.min.mjs +1 -1
- package/dist/src/shapes/IText/IText.min.mjs.map +1 -1
- package/dist/src/shapes/IText/IText.mjs +495 -126
- package/dist/src/shapes/IText/IText.mjs.map +1 -1
- package/dist/src/shapes/IText/ITextBehavior.d.ts +12 -0
- package/dist/src/shapes/IText/ITextBehavior.d.ts.map +1 -1
- package/dist/src/shapes/IText/ITextBehavior.min.mjs +1 -1
- package/dist/src/shapes/IText/ITextBehavior.min.mjs.map +1 -1
- package/dist/src/shapes/IText/ITextBehavior.mjs +127 -36
- package/dist/src/shapes/IText/ITextBehavior.mjs.map +1 -1
- package/dist/src/shapes/IText/ITextClickBehavior.d.ts.map +1 -1
- package/dist/src/shapes/IText/ITextClickBehavior.min.mjs +1 -1
- package/dist/src/shapes/IText/ITextClickBehavior.min.mjs.map +1 -1
- package/dist/src/shapes/IText/ITextClickBehavior.mjs +21 -4
- package/dist/src/shapes/IText/ITextClickBehavior.mjs.map +1 -1
- package/dist/src/shapes/IText/ITextKeyBehavior.min.mjs +1 -1
- package/dist/src/shapes/IText/ITextKeyBehavior.min.mjs.map +1 -1
- package/dist/src/shapes/IText/ITextKeyBehavior.mjs +17 -21
- package/dist/src/shapes/IText/ITextKeyBehavior.mjs.map +1 -1
- package/dist/src/shapes/Text/Text.d.ts +69 -1
- package/dist/src/shapes/Text/Text.d.ts.map +1 -1
- package/dist/src/shapes/Text/Text.min.mjs +1 -1
- package/dist/src/shapes/Text/Text.min.mjs.map +1 -1
- package/dist/src/shapes/Text/Text.mjs +374 -60
- package/dist/src/shapes/Text/Text.mjs.map +1 -1
- package/dist/src/shapes/Text/constants.d.ts.map +1 -1
- package/dist/src/shapes/Text/constants.min.mjs +1 -1
- package/dist/src/shapes/Text/constants.min.mjs.map +1 -1
- package/dist/src/shapes/Text/constants.mjs +2 -1
- package/dist/src/shapes/Text/constants.mjs.map +1 -1
- package/dist/src/shapes/Textbox.d.ts +8 -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 +406 -63
- package/dist/src/shapes/Textbox.mjs.map +1 -1
- package/dist/src/text/hitTest.min.mjs +1 -1
- package/dist/src/text/hitTest.min.mjs.map +1 -1
- package/dist/src/text/hitTest.mjs +1 -198
- package/dist/src/text/hitTest.mjs.map +1 -1
- package/dist/src/text/layout.min.mjs +1 -1
- package/dist/src/text/layout.min.mjs.map +1 -1
- package/dist/src/text/layout.mjs +122 -5
- package/dist/src/text/layout.mjs.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 +132 -142
- package/dist/src/text/overlayEditor.mjs.map +1 -1
- package/dist/src/text/unicode.d.ts +28 -0
- package/dist/src/text/unicode.d.ts.map +1 -1
- package/dist/src/text/unicode.min.mjs +1 -1
- package/dist/src/text/unicode.min.mjs.map +1 -1
- package/dist/src/text/unicode.mjs +294 -1
- package/dist/src/text/unicode.mjs.map +1 -1
- package/dist-extensions/src/shapes/IText/IText.d.ts +31 -6
- package/dist-extensions/src/shapes/IText/IText.d.ts.map +1 -1
- package/dist-extensions/src/shapes/IText/ITextBehavior.d.ts +12 -0
- package/dist-extensions/src/shapes/IText/ITextBehavior.d.ts.map +1 -1
- package/dist-extensions/src/shapes/IText/ITextClickBehavior.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Text/Text.d.ts +69 -1
- package/dist-extensions/src/shapes/Text/Text.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Text/constants.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Textbox.d.ts +8 -1
- package/dist-extensions/src/shapes/Textbox.d.ts.map +1 -1
- package/dist-extensions/src/text/unicode.d.ts +28 -0
- package/dist-extensions/src/text/unicode.d.ts.map +1 -1
- package/package.json +164 -164
- package/rtl-debug.html +358 -200
- package/src/shapes/IText/IText.ts +524 -110
- package/src/shapes/IText/ITextBehavior.ts +174 -80
- package/src/shapes/IText/ITextClickBehavior.ts +20 -6
- package/src/shapes/IText/ITextKeyBehavior.ts +15 -15
- package/src/shapes/Text/Text.ts +488 -107
- package/src/shapes/Text/constants.ts +4 -2
- package/src/shapes/Textbox.ts +414 -65
- package/src/text/layout.ts +150 -23
- package/src/text/overlayEditor.ts +148 -148
- package/src/text/unicode.ts +177 -2
package/rtl-debug.html
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html>
|
|
3
|
+
|
|
3
4
|
<head>
|
|
4
5
|
<title>RTL Text Debug - Visual Bounding Box and Text Position</title>
|
|
5
6
|
<style>
|
|
@@ -10,7 +11,7 @@
|
|
|
10
11
|
font-style: normal;
|
|
11
12
|
font-display: swap;
|
|
12
13
|
}
|
|
13
|
-
|
|
14
|
+
|
|
14
15
|
@font-face {
|
|
15
16
|
font-family: 'STV';
|
|
16
17
|
src: url('./fonts/STV Bold.ttf') format('truetype');
|
|
@@ -18,7 +19,7 @@
|
|
|
18
19
|
font-style: normal;
|
|
19
20
|
font-display: swap;
|
|
20
21
|
}
|
|
21
|
-
|
|
22
|
+
|
|
22
23
|
@font-face {
|
|
23
24
|
font-family: 'STV';
|
|
24
25
|
src: url('./fonts/STV Light.ttf') format('truetype');
|
|
@@ -26,29 +27,35 @@
|
|
|
26
27
|
font-style: normal;
|
|
27
28
|
font-display: swap;
|
|
28
29
|
}
|
|
29
|
-
|
|
30
|
-
body {
|
|
31
|
-
font-family: Arial, sans-serif;
|
|
30
|
+
|
|
31
|
+
body {
|
|
32
|
+
font-family: Arial, sans-serif;
|
|
32
33
|
margin: 20px;
|
|
33
34
|
background: #f5f5f5;
|
|
34
35
|
}
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
|
|
37
|
+
canvas {
|
|
38
|
+
border: 1px solid #ccc;
|
|
37
39
|
margin: 10px;
|
|
38
40
|
background: white;
|
|
39
41
|
}
|
|
42
|
+
|
|
40
43
|
.canvas-container {
|
|
41
44
|
position: relative;
|
|
42
45
|
}
|
|
46
|
+
|
|
43
47
|
.canvas-container .upper-canvas {
|
|
44
48
|
pointer-events: auto;
|
|
45
49
|
}
|
|
50
|
+
|
|
46
51
|
.lower-canvas {
|
|
47
52
|
z-index: 1;
|
|
48
53
|
}
|
|
54
|
+
|
|
49
55
|
.upper-canvas {
|
|
50
56
|
z-index: 2;
|
|
51
57
|
}
|
|
58
|
+
|
|
52
59
|
.debug-info {
|
|
53
60
|
font-family: monospace;
|
|
54
61
|
background: #333;
|
|
@@ -58,13 +65,15 @@
|
|
|
58
65
|
border-radius: 4px;
|
|
59
66
|
white-space: pre-wrap;
|
|
60
67
|
}
|
|
68
|
+
|
|
61
69
|
.controls {
|
|
62
70
|
background: white;
|
|
63
71
|
padding: 15px;
|
|
64
72
|
border-radius: 4px;
|
|
65
73
|
margin: 10px 0;
|
|
66
|
-
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
74
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
67
75
|
}
|
|
76
|
+
|
|
68
77
|
button {
|
|
69
78
|
padding: 8px 16px;
|
|
70
79
|
margin: 5px;
|
|
@@ -74,7 +83,11 @@
|
|
|
74
83
|
border-radius: 4px;
|
|
75
84
|
cursor: pointer;
|
|
76
85
|
}
|
|
77
|
-
|
|
86
|
+
|
|
87
|
+
button:hover {
|
|
88
|
+
background: #0056b3;
|
|
89
|
+
}
|
|
90
|
+
|
|
78
91
|
.text-sample {
|
|
79
92
|
margin: 5px 0;
|
|
80
93
|
padding: 5px;
|
|
@@ -83,74 +96,96 @@
|
|
|
83
96
|
}
|
|
84
97
|
</style>
|
|
85
98
|
</head>
|
|
99
|
+
|
|
86
100
|
<body>
|
|
87
101
|
<h1>RTL Text Debug - Visual Bounding Box and Text Position</h1>
|
|
88
|
-
|
|
102
|
+
|
|
89
103
|
<div class="controls">
|
|
90
104
|
<h3>Test Cases:</h3>
|
|
91
105
|
<div class="text-sample">Arabic: مرحبا بك في العالم</div>
|
|
92
106
|
<div class="text-sample">Hebrew: שלום עולם</div>
|
|
93
|
-
<div class="text-sample">Mixed: Hello مرحبا World</div>
|
|
94
|
-
<div class="text-sample">English LTR: Hello World</div>
|
|
95
|
-
|
|
96
|
-
<br>
|
|
97
|
-
<button onclick="showRTLText()">Show RTL Arabic Text</button>
|
|
98
|
-
<button onclick="showLTRText()">Show LTR English Text</button>
|
|
99
|
-
<button onclick="showMixedText()">Show Mixed Text</button>
|
|
100
|
-
<button onclick="showSimpleTest()">Simple Test</button>
|
|
101
|
-
<button onclick="showSTVFont()">Show STV Regular</button>
|
|
102
|
-
<button onclick="showSTVBold()">Show STV Bold</button>
|
|
103
|
-
<button onclick="showSTVLight()">Show STV Light</button>
|
|
104
|
-
<button onclick="clearCanvas()">Clear Canvas</button>
|
|
105
|
-
<button onclick="toggleDebugMode()">Toggle Debug Mode</button>
|
|
106
|
-
<button onclick="exportJSON()">Export JSON</button>
|
|
107
|
-
<input type="file" id="jsonFile" accept=".json" style="display: none;" onchange="loadJSONFile(event)">
|
|
108
|
-
<button onclick="document.getElementById('jsonFile').click()">Import JSON</button>
|
|
109
|
-
<div style="margin-top:8px; display:flex; gap:6px; flex-wrap:wrap;">
|
|
110
|
-
<button onclick="setAlign('left')">Align Left</button>
|
|
111
|
-
<button onclick="setAlign('center')">Align Center</button>
|
|
112
|
-
<button onclick="setAlign('right')">Align Right</button>
|
|
113
|
-
<button onclick="setAlign('justify')">Justify</button>
|
|
114
|
-
<button onclick="setAlign('justify-left')">Justify-Left</button>
|
|
115
|
-
<button onclick="setAlign('justify-center')">Justify-Center</button>
|
|
116
|
-
<button onclick="setAlign('justify-right')">Justify-Right</button>
|
|
117
|
-
</div>
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
107
|
+
<div class="text-sample">Mixed: Hello مرحبا World</div>
|
|
108
|
+
<div class="text-sample">English LTR: Hello World</div>
|
|
109
|
+
|
|
110
|
+
<br>
|
|
111
|
+
<button onclick="showRTLText()">Show RTL Arabic Text</button>
|
|
112
|
+
<button onclick="showLTRText()">Show LTR English Text</button>
|
|
113
|
+
<button onclick="showMixedText()">Show Mixed Text</button>
|
|
114
|
+
<button onclick="showSimpleTest()">Simple Test</button>
|
|
115
|
+
<button onclick="showSTVFont()">Show STV Regular</button>
|
|
116
|
+
<button onclick="showSTVBold()">Show STV Bold</button>
|
|
117
|
+
<button onclick="showSTVLight()">Show STV Light</button>
|
|
118
|
+
<button onclick="clearCanvas()">Clear Canvas</button>
|
|
119
|
+
<button onclick="toggleDebugMode()">Toggle Debug Mode</button>
|
|
120
|
+
<button onclick="exportJSON()">Export JSON</button>
|
|
121
|
+
<input type="file" id="jsonFile" accept=".json" style="display: none;" onchange="loadJSONFile(event)">
|
|
122
|
+
<button onclick="document.getElementById('jsonFile').click()">Import JSON</button>
|
|
123
|
+
<div style="margin-top:8px; display:flex; gap:6px; flex-wrap:wrap;">
|
|
124
|
+
<button onclick="setAlign('left')">Align Left</button>
|
|
125
|
+
<button onclick="setAlign('center')">Align Center</button>
|
|
126
|
+
<button onclick="setAlign('right')">Align Right</button>
|
|
127
|
+
<button onclick="setAlign('justify')">Justify</button>
|
|
128
|
+
<button onclick="setAlign('justify-left')">Justify-Left</button>
|
|
129
|
+
<button onclick="setAlign('justify-center')">Justify-Center</button>
|
|
130
|
+
<button onclick="setAlign('justify-right')">Justify-Right</button>
|
|
131
|
+
</div>
|
|
132
|
+
<div style="margin-top:8px;">
|
|
133
|
+
<strong>Kashida Test (Arabic Justification):</strong>
|
|
134
|
+
</div>
|
|
135
|
+
<div style="margin-top:4px; display:flex; gap:6px; flex-wrap:wrap;">
|
|
136
|
+
<button onclick="showKashidaTest('none')" style="background:#6c757d;">Kashida: None</button>
|
|
137
|
+
<button onclick="showKashidaTest('short')" style="background:#17a2b8;">Kashida: Short (25%)</button>
|
|
138
|
+
<button onclick="showKashidaTest('medium')" style="background:#28a745;">Kashida: Medium (50%)</button>
|
|
139
|
+
<button onclick="showKashidaTest('long')" style="background:#ffc107; color:#333;">Kashida: Long
|
|
140
|
+
(75%)</button>
|
|
141
|
+
<button onclick="showKashidaTest('stylistic')" style="background:#dc3545;">Kashida: Stylistic
|
|
142
|
+
(100%)</button>
|
|
143
|
+
</div>
|
|
144
|
+
<div style="margin-top:4px; display:flex; gap:6px; flex-wrap:wrap;">
|
|
145
|
+
<button onclick="setKashida('none')">Set None</button>
|
|
146
|
+
<button onclick="setKashida('short')">Set Short</button>
|
|
147
|
+
<button onclick="setKashida('medium')">Set Medium</button>
|
|
148
|
+
<button onclick="setKashida('long')">Set Long</button>
|
|
149
|
+
<button onclick="setKashida('stylistic')">Set Stylistic</button>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
<canvas id="canvas" width="1080" height="1080"></canvas>
|
|
154
|
+
|
|
155
|
+
<div id="debug-info" class="debug-info">Debug information will appear here...</div>
|
|
123
156
|
<div id="click-info" class="debug-info">Click on text to see selection coordinates...</div>
|
|
124
157
|
|
|
125
|
-
<script src="./dist/index.js"></script>
|
|
158
|
+
<script src="./dist/index.js?v=34"></script>
|
|
126
159
|
<script>
|
|
160
|
+
console.log("DEBUG: Updated rtl-debug.html loaded");
|
|
161
|
+
// alert("DEBUG: Updated RTL Page Loaded - Please check console for 'Hit Testing Debug' when clicking");
|
|
127
162
|
// Check if fabric.js loaded
|
|
128
163
|
console.log('fabric object:', typeof fabric);
|
|
129
164
|
console.log('fabric.Canvas:', typeof fabric.Canvas);
|
|
130
165
|
console.log('fabric.Text:', typeof fabric.Text);
|
|
131
166
|
console.log('fabric.IText:', typeof fabric.IText);
|
|
132
|
-
|
|
167
|
+
|
|
133
168
|
if (typeof fabric === 'undefined') {
|
|
134
169
|
document.getElementById('debug-info').textContent = 'ERROR: fabric.js not loaded! Check the script path.';
|
|
135
170
|
}
|
|
136
|
-
|
|
171
|
+
|
|
137
172
|
const canvas = new fabric.Canvas('canvas', {
|
|
138
173
|
backgroundColor: 'white',
|
|
139
174
|
selection: true,
|
|
140
175
|
preserveObjectStacking: true
|
|
141
176
|
});
|
|
142
|
-
|
|
177
|
+
|
|
143
178
|
// Ensure upper canvas is transparent
|
|
144
179
|
canvas.upperCanvasEl.style.backgroundColor = 'transparent';
|
|
145
180
|
let debugMode = true;
|
|
146
181
|
let currentText = null;
|
|
147
182
|
let debugInfo = document.getElementById('debug-info');
|
|
148
183
|
let clickInfo = document.getElementById('click-info');
|
|
149
|
-
|
|
184
|
+
|
|
150
185
|
// Test canvas rendering immediately
|
|
151
186
|
console.log('Canvas created:', canvas);
|
|
152
187
|
console.log('Canvas element:', canvas.getElement());
|
|
153
|
-
|
|
188
|
+
|
|
154
189
|
// Add immediate visual test
|
|
155
190
|
const testCircle = new fabric.Circle({
|
|
156
191
|
left: 10,
|
|
@@ -177,6 +212,8 @@ Text Properties:
|
|
|
177
212
|
├─ Text: "${text.text}"
|
|
178
213
|
├─ Direction: ${text.direction}
|
|
179
214
|
├─ TextAlign: ${text.textAlign}
|
|
215
|
+
├─ Kashida: ${text.kashida || 'none'}
|
|
216
|
+
├─ enableAdvancedLayout: ${text.enableAdvancedLayout}
|
|
180
217
|
├─ Width: ${text.width.toFixed(2)}
|
|
181
218
|
├─ Height: ${text.height.toFixed(2)}
|
|
182
219
|
├─ Left: ${text.left.toFixed(2)}
|
|
@@ -202,11 +239,18 @@ Line Left Offsets:
|
|
|
202
239
|
${text._textLines.map((line, i) => `├─ Line ${i}: ${text._getLineLeftOffset(i).toFixed(2)}`).join('\n')}
|
|
203
240
|
|
|
204
241
|
Character Bounds (first line):
|
|
205
|
-
${text.__charBounds && text.__charBounds[0] ?
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
242
|
+
${text.__charBounds && text.__charBounds[0] ?
|
|
243
|
+
text.__charBounds[0].map((char, i) =>
|
|
244
|
+
`├─ Char ${i}: left=${char.left.toFixed(2)}, width=${char.width.toFixed(2)}, kernedWidth=${char.kernedWidth.toFixed(2)}`
|
|
245
|
+
).join('\n')
|
|
246
|
+
: '├─ No character bounds available'}
|
|
247
|
+
|
|
248
|
+
Kashida Points (first line):
|
|
249
|
+
${text.__kashidaInfo && text.__kashidaInfo[0] && text.__kashidaInfo[0].length > 0 ?
|
|
250
|
+
text.__kashidaInfo[0].map((k, i) =>
|
|
251
|
+
`├─ Point ${i}: charIndex=${k.charIndex}, width=${k.width.toFixed(2)}`
|
|
252
|
+
).join('\n')
|
|
253
|
+
: '├─ No kashida points (kashida: ' + (text.kashida || 'none') + ')'}
|
|
210
254
|
`;
|
|
211
255
|
debugInfo.textContent = info;
|
|
212
256
|
}
|
|
@@ -215,7 +259,7 @@ ${text.__charBounds && text.__charBounds[0] ?
|
|
|
215
259
|
if (!debugMode || !text) return;
|
|
216
260
|
|
|
217
261
|
const bbox = text.getBoundingRect();
|
|
218
|
-
|
|
262
|
+
|
|
219
263
|
// Draw bounding box rectangle
|
|
220
264
|
const bboxRect = new fabric.Rect({
|
|
221
265
|
left: bbox.left,
|
|
@@ -246,9 +290,9 @@ ${text.__charBounds && text.__charBounds[0] ?
|
|
|
246
290
|
|
|
247
291
|
// Draw text width visualization
|
|
248
292
|
const textWidthLine = new fabric.Line([
|
|
249
|
-
text.left - text.width/2,
|
|
293
|
+
text.left - text.width / 2,
|
|
250
294
|
text.top,
|
|
251
|
-
text.left + text.width/2,
|
|
295
|
+
text.left + text.width / 2,
|
|
252
296
|
text.top
|
|
253
297
|
], {
|
|
254
298
|
stroke: 'green',
|
|
@@ -296,19 +340,21 @@ ${text.__charBounds && text.__charBounds[0] ?
|
|
|
296
340
|
currentText = new fabric.Textbox('تجــــربه مرحبا بك في العالم الرقمي', {
|
|
297
341
|
left: 400,
|
|
298
342
|
top: 150,
|
|
299
|
-
width:
|
|
343
|
+
width: 600,
|
|
300
344
|
fontFamily: 'Arial',
|
|
301
345
|
fontSize: 24,
|
|
302
346
|
direction: 'rtl',
|
|
303
347
|
textAlign: 'right',
|
|
304
348
|
fill: '#333',
|
|
305
349
|
editable: true,
|
|
306
|
-
selectable: true
|
|
350
|
+
selectable: true,
|
|
351
|
+
enableAdvancedLayout: true,
|
|
352
|
+
splitByGrapheme: true
|
|
307
353
|
});
|
|
308
|
-
|
|
354
|
+
|
|
309
355
|
canvas.add(currentText);
|
|
310
356
|
canvas.renderAll();
|
|
311
|
-
|
|
357
|
+
|
|
312
358
|
setTimeout(() => {
|
|
313
359
|
drawBoundingBoxOverlay(currentText);
|
|
314
360
|
updateDebugInfo(currentText, 'RTL Arabic Text');
|
|
@@ -329,10 +375,10 @@ ${text.__charBounds && text.__charBounds[0] ?
|
|
|
329
375
|
editable: true,
|
|
330
376
|
selectable: true
|
|
331
377
|
});
|
|
332
|
-
|
|
378
|
+
|
|
333
379
|
canvas.add(currentText);
|
|
334
380
|
canvas.renderAll();
|
|
335
|
-
|
|
381
|
+
|
|
336
382
|
setTimeout(() => {
|
|
337
383
|
drawBoundingBoxOverlay(currentText);
|
|
338
384
|
updateDebugInfo(currentText, 'LTR English Text');
|
|
@@ -351,12 +397,13 @@ ${text.__charBounds && text.__charBounds[0] ?
|
|
|
351
397
|
textAlign: 'center',
|
|
352
398
|
fill: '#333',
|
|
353
399
|
editable: true,
|
|
354
|
-
selectable: true
|
|
400
|
+
selectable: true,
|
|
401
|
+
enableAdvancedLayout: true
|
|
355
402
|
});
|
|
356
|
-
|
|
403
|
+
|
|
357
404
|
canvas.add(currentText);
|
|
358
405
|
canvas.renderAll();
|
|
359
|
-
|
|
406
|
+
|
|
360
407
|
setTimeout(() => {
|
|
361
408
|
drawBoundingBoxOverlay(currentText);
|
|
362
409
|
updateDebugInfo(currentText, 'Mixed RTL/LTR Text');
|
|
@@ -365,7 +412,7 @@ ${text.__charBounds && text.__charBounds[0] ?
|
|
|
365
412
|
|
|
366
413
|
function showSimpleTest() {
|
|
367
414
|
clearCanvas();
|
|
368
|
-
|
|
415
|
+
|
|
369
416
|
// Add a simple visible rectangle first to test canvas
|
|
370
417
|
const rect = new fabric.Rect({
|
|
371
418
|
left: 100,
|
|
@@ -375,7 +422,7 @@ ${text.__charBounds && text.__charBounds[0] ?
|
|
|
375
422
|
fill: 'red'
|
|
376
423
|
});
|
|
377
424
|
canvas.add(rect);
|
|
378
|
-
|
|
425
|
+
|
|
379
426
|
// Test different text approaches
|
|
380
427
|
console.log('Creating basic fabric.Text...');
|
|
381
428
|
const basicText = new fabric.Text('BASIC TEXT', {
|
|
@@ -387,7 +434,7 @@ ${text.__charBounds && text.__charBounds[0] ?
|
|
|
387
434
|
});
|
|
388
435
|
canvas.add(basicText);
|
|
389
436
|
console.log('Basic text added:', basicText.getBoundingRect());
|
|
390
|
-
|
|
437
|
+
|
|
391
438
|
console.log('Creating fabric.IText...');
|
|
392
439
|
currentText = new fabric.IText('ITEXT HERE', {
|
|
393
440
|
left: 50,
|
|
@@ -399,7 +446,7 @@ ${text.__charBounds && text.__charBounds[0] ?
|
|
|
399
446
|
});
|
|
400
447
|
canvas.add(currentText);
|
|
401
448
|
console.log('IText added:', currentText.getBoundingRect());
|
|
402
|
-
|
|
449
|
+
|
|
403
450
|
console.log('Creating RTL text...');
|
|
404
451
|
const rtlText = new fabric.IText('مرحبا', {
|
|
405
452
|
left: 50,
|
|
@@ -412,16 +459,16 @@ ${text.__charBounds && text.__charBounds[0] ?
|
|
|
412
459
|
});
|
|
413
460
|
canvas.add(rtlText);
|
|
414
461
|
console.log('RTL text added:', rtlText.getBoundingRect());
|
|
415
|
-
|
|
462
|
+
|
|
416
463
|
// Force canvas to render and log all objects
|
|
417
464
|
canvas.renderAll();
|
|
418
465
|
console.log('Canvas objects:', canvas.getObjects().length);
|
|
419
466
|
console.log('Canvas dimensions:', canvas.width, 'x', canvas.height);
|
|
420
|
-
|
|
467
|
+
|
|
421
468
|
// Try to zoom out to see if text is outside viewport
|
|
422
469
|
canvas.setZoom(0.5);
|
|
423
470
|
canvas.renderAll();
|
|
424
|
-
|
|
471
|
+
|
|
425
472
|
updateDebugInfo(currentText, 'Simple Test - Check Console');
|
|
426
473
|
}
|
|
427
474
|
|
|
@@ -446,14 +493,14 @@ ${text.__charBounds && text.__charBounds[0] ?
|
|
|
446
493
|
}
|
|
447
494
|
|
|
448
495
|
// Handle canvas clicks to show selection information
|
|
449
|
-
canvas.on('mouse:down', function(options) {
|
|
496
|
+
canvas.on('mouse:down', function (options) {
|
|
450
497
|
if (!options.target || !options.target.type || !options.target.type.includes('text')) return;
|
|
451
|
-
|
|
498
|
+
|
|
452
499
|
const text = options.target;
|
|
453
500
|
const pointer = canvas.getScenePoint(options.e);
|
|
454
501
|
const selection = text.getSelectionStartFromPointer(options.e);
|
|
455
502
|
const cursorBounds = text._getCursorBoundaries(selection);
|
|
456
|
-
|
|
503
|
+
|
|
457
504
|
const clickDebugInfo = `
|
|
458
505
|
Click Debug Information:
|
|
459
506
|
════════════════════════════════════════════════
|
|
@@ -478,15 +525,15 @@ Text Direction: ${text.direction}
|
|
|
478
525
|
Text Align: ${text.textAlign}
|
|
479
526
|
`;
|
|
480
527
|
clickInfo.textContent = clickDebugInfo;
|
|
481
|
-
|
|
528
|
+
|
|
482
529
|
// Draw cursor position and character bounds
|
|
483
530
|
clearDebugOverlays();
|
|
484
|
-
drawBoundingBoxOverlay(text);
|
|
485
|
-
drawCharacterBounds(text);
|
|
486
|
-
|
|
487
|
-
const cursorX = cursorBounds.left + cursorBounds.leftOffset;
|
|
488
|
-
const cursorY = cursorBounds.top + cursorBounds.topOffset;
|
|
489
|
-
|
|
531
|
+
drawBoundingBoxOverlay(text);
|
|
532
|
+
drawCharacterBounds(text);
|
|
533
|
+
|
|
534
|
+
const cursorX = cursorBounds.left + cursorBounds.leftOffset;
|
|
535
|
+
const cursorY = cursorBounds.top + cursorBounds.topOffset;
|
|
536
|
+
|
|
490
537
|
const cursorLine = new fabric.Line([cursorX, cursorY - 10, cursorX, cursorY + 20], {
|
|
491
538
|
stroke: 'orange',
|
|
492
539
|
strokeWidth: 3,
|
|
@@ -494,7 +541,7 @@ Text Align: ${text.textAlign}
|
|
|
494
541
|
evented: false,
|
|
495
542
|
excludeFromExport: true
|
|
496
543
|
});
|
|
497
|
-
|
|
544
|
+
|
|
498
545
|
const cursorLabel = new fabric.Text(`Cursor: (${cursorX.toFixed(0)},${cursorY.toFixed(0)})`, {
|
|
499
546
|
left: cursorX + 5,
|
|
500
547
|
top: cursorY - 25,
|
|
@@ -504,117 +551,161 @@ Text Align: ${text.textAlign}
|
|
|
504
551
|
evented: false,
|
|
505
552
|
excludeFromExport: true
|
|
506
553
|
});
|
|
507
|
-
|
|
508
|
-
canvas.add(cursorLine, cursorLabel);
|
|
509
|
-
});
|
|
510
|
-
|
|
511
|
-
// Alignment helper
|
|
512
|
-
function setAlign(align) {
|
|
513
|
-
if (!currentText) {
|
|
514
|
-
alert('Add a text object first.');
|
|
515
|
-
return;
|
|
516
|
-
}
|
|
517
|
-
currentText.set({ textAlign: align });
|
|
518
|
-
canvas.requestRenderAll();
|
|
519
|
-
clearDebugOverlays();
|
|
520
|
-
drawBoundingBoxOverlay(currentText);
|
|
521
|
-
updateDebugInfo(currentText, `Align: ${align}`);
|
|
522
|
-
}
|
|
523
|
-
|
|
554
|
+
|
|
555
|
+
canvas.add(cursorLine, cursorLabel);
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
// Alignment helper
|
|
559
|
+
function setAlign(align) {
|
|
560
|
+
if (!currentText) {
|
|
561
|
+
alert('Add a text object first.');
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
currentText.set({ textAlign: align });
|
|
565
|
+
canvas.requestRenderAll();
|
|
566
|
+
clearDebugOverlays();
|
|
567
|
+
drawBoundingBoxOverlay(currentText);
|
|
568
|
+
updateDebugInfo(currentText, `Align: ${align}`);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// BiDi direction detection
|
|
572
|
+
function getBidiCharDir(char, baseDir) {
|
|
573
|
+
if (/[\u0590-\u05FF\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]/.test(char)) {
|
|
574
|
+
return 'rtl';
|
|
575
|
+
}
|
|
576
|
+
if (/[A-Za-z\u00C0-\u024F\u0370-\u03FF\u0400-\u04FF]/.test(char)) {
|
|
577
|
+
return 'ltr';
|
|
578
|
+
}
|
|
579
|
+
return baseDir; // Neutral inherits base
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// Analyze BiDi runs
|
|
583
|
+
function getBiDiRuns(text, baseDir) {
|
|
584
|
+
if (!text) return [];
|
|
585
|
+
const runs = [];
|
|
586
|
+
let current = null;
|
|
587
|
+
for (let i = 0; i < text.length; i++) {
|
|
588
|
+
const dir = getBidiCharDir(text[i], baseDir);
|
|
589
|
+
if (!current || current.direction !== dir) {
|
|
590
|
+
if (current) runs.push(current);
|
|
591
|
+
current = { direction: dir, start: i, end: i + 1, text: text[i] };
|
|
592
|
+
} else {
|
|
593
|
+
current.end = i + 1;
|
|
594
|
+
current.text += text[i];
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
if (current) runs.push(current);
|
|
598
|
+
return runs;
|
|
599
|
+
}
|
|
600
|
+
|
|
524
601
|
function drawCharacterBounds(text) {
|
|
525
602
|
if (!debugMode || !text || !text.__charBounds || !text.__charBounds[0]) return;
|
|
526
|
-
|
|
527
|
-
console.log('=== Drawing character bounds ===');
|
|
528
|
-
console.log('Text object:', text);
|
|
529
|
-
console.log('Text position:', text.left, text.top);
|
|
530
|
-
console.log('Text width/height:', text.width, text.height);
|
|
531
|
-
console.log('Text direction:', text.direction);
|
|
532
|
-
console.log('Text align:', text.textAlign);
|
|
533
|
-
console.log('Font family:', text.fontFamily);
|
|
534
|
-
|
|
603
|
+
|
|
604
|
+
console.log('=== Drawing character bounds (BiDi) ===');
|
|
535
605
|
const bbox = text.getBoundingRect();
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
const
|
|
539
|
-
const textTop = text._getTopOffset() + text.top;
|
|
606
|
+
const baseY = bbox.top;
|
|
607
|
+
const isRtl = text.direction === 'rtl';
|
|
608
|
+
const chars = text.__charBounds[0];
|
|
540
609
|
const lineLeftOffset = text._getLineLeftOffset(0);
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
console.log('
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
610
|
+
const lineText = text._textLines[0] ? text._textLines[0].join('') : text.text;
|
|
611
|
+
|
|
612
|
+
console.log('Direction:', text.direction, 'Text:', lineText);
|
|
613
|
+
|
|
614
|
+
// For LTR, use simple positions
|
|
615
|
+
if (!isRtl) {
|
|
616
|
+
chars.forEach((charBound, i) => {
|
|
617
|
+
const charText = text._textLines[0]?.[i] || '';
|
|
618
|
+
const charX = bbox.left + lineLeftOffset + charBound.left;
|
|
619
|
+
const charWidth = charBound.kernedWidth || charBound.width;
|
|
620
|
+
drawCharBox(charX, baseY, charWidth, text.fontSize, i, charText);
|
|
621
|
+
});
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// For RTL: analyze BiDi runs and calculate visual positions
|
|
626
|
+
const runs = getBiDiRuns(lineText, 'rtl');
|
|
627
|
+
console.log('BiDi runs:', runs);
|
|
628
|
+
|
|
629
|
+
// Build char info
|
|
630
|
+
const charInfos = chars.map((c, i) => ({
|
|
631
|
+
charIndex: i,
|
|
632
|
+
width: c.kernedWidth || c.width,
|
|
633
|
+
grapheme: text._textLines[0]?.[i] || ''
|
|
634
|
+
}));
|
|
635
|
+
|
|
636
|
+
// Group chars by runs
|
|
637
|
+
const runInfos = [];
|
|
638
|
+
for (const run of runs) {
|
|
639
|
+
const runChars = [];
|
|
640
|
+
let totalWidth = 0;
|
|
641
|
+
let strPos = 0;
|
|
642
|
+
for (let i = 0; i < charInfos.length; i++) {
|
|
643
|
+
const gLen = charInfos[i].grapheme.length || 1;
|
|
644
|
+
if (strPos >= run.start && strPos < run.end) {
|
|
645
|
+
runChars.push(charInfos[i]);
|
|
646
|
+
totalWidth += charInfos[i].width;
|
|
647
|
+
}
|
|
648
|
+
strPos += gLen;
|
|
649
|
+
}
|
|
650
|
+
if (runChars.length > 0) {
|
|
651
|
+
runInfos.push({ direction: run.direction, chars: runChars, totalWidth });
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// Reverse runs for RTL base
|
|
656
|
+
runInfos.reverse();
|
|
657
|
+
|
|
658
|
+
// Calculate visual positions
|
|
659
|
+
const visualPositions = new Map();
|
|
660
|
+
let visualX = 0;
|
|
661
|
+
for (const runInfo of runInfos) {
|
|
662
|
+
if (runInfo.direction === 'rtl') {
|
|
663
|
+
let runX = visualX + runInfo.totalWidth;
|
|
664
|
+
for (const c of runInfo.chars) {
|
|
665
|
+
runX -= c.width;
|
|
666
|
+
visualPositions.set(c.charIndex, { x: runX, width: c.width });
|
|
667
|
+
}
|
|
567
668
|
} else {
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
669
|
+
let runX = visualX;
|
|
670
|
+
for (const c of runInfo.chars) {
|
|
671
|
+
visualPositions.set(c.charIndex, { x: runX, width: c.width });
|
|
672
|
+
runX += c.width;
|
|
673
|
+
}
|
|
571
674
|
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
const
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
const
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
fontFamily: 'Arial', // Use Arial for labels to ensure readability
|
|
600
|
-
selectable: false,
|
|
601
|
-
evented: false,
|
|
602
|
-
excludeFromExport: true
|
|
603
|
-
});
|
|
604
|
-
|
|
605
|
-
canvas.add(charRect, charLabel);
|
|
606
|
-
|
|
607
|
-
console.log(`${isSTVFont ? 'STV ' : ''}Char ${i} "${char}":`, {
|
|
608
|
-
boundLeft: charBound.left,
|
|
609
|
-
kernedWidth: charBound.kernedWidth,
|
|
610
|
-
calculatedX: charX,
|
|
611
|
-
width: charWidth
|
|
612
|
-
});
|
|
675
|
+
visualX += runInfo.totalWidth;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Draw character boxes
|
|
679
|
+
chars.forEach((charBound, i) => {
|
|
680
|
+
const charText = charInfos[i]?.grapheme || '';
|
|
681
|
+
const pos = visualPositions.get(i);
|
|
682
|
+
if (!pos) return;
|
|
683
|
+
const charX = bbox.left + lineLeftOffset + pos.x;
|
|
684
|
+
drawCharBox(charX, baseY, pos.width, text.fontSize, i, charText);
|
|
685
|
+
console.log(`Char ${i} "${charText}": visualX=${pos.x.toFixed(2)}, canvasX=${charX.toFixed(2)}`);
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
function drawCharBox(x, y, width, height, index, charText) {
|
|
690
|
+
const fillColor = index % 2 === 0 ? 'rgba(255, 0, 255, 0.2)' : 'rgba(0, 255, 255, 0.2)';
|
|
691
|
+
const strokeColor = index % 2 === 0 ? 'magenta' : 'cyan';
|
|
692
|
+
|
|
693
|
+
const charRect = new fabric.Rect({
|
|
694
|
+
left: x, top: y, width: width, height: height,
|
|
695
|
+
fill: fillColor, stroke: strokeColor, strokeWidth: 1,
|
|
696
|
+
selectable: false, evented: false, excludeFromExport: true
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
const charLabel = new fabric.Text(`${index}:"${charText}"`, {
|
|
700
|
+
left: x, top: y - 15, fontSize: 10, fill: 'purple',
|
|
701
|
+
fontFamily: 'Arial', selectable: false, evented: false, excludeFromExport: true
|
|
613
702
|
});
|
|
703
|
+
|
|
704
|
+
canvas.add(charRect, charLabel);
|
|
614
705
|
}
|
|
615
706
|
|
|
616
707
|
// Update debug info when text properties change
|
|
617
|
-
canvas.on('object:modified', function(options) {
|
|
708
|
+
canvas.on('object:modified', function (options) {
|
|
618
709
|
if (options.target && options.target.type.includes('text')) {
|
|
619
710
|
clearDebugOverlays();
|
|
620
711
|
drawBoundingBoxOverlay(options.target);
|
|
@@ -638,16 +729,16 @@ Text Align: ${text.textAlign}
|
|
|
638
729
|
editable: true,
|
|
639
730
|
selectable: true
|
|
640
731
|
});
|
|
641
|
-
|
|
732
|
+
|
|
642
733
|
canvas.add(currentText);
|
|
643
734
|
canvas.renderAll();
|
|
644
|
-
|
|
735
|
+
|
|
645
736
|
setTimeout(() => {
|
|
646
737
|
drawBoundingBoxOverlay(currentText);
|
|
647
738
|
updateDebugInfo(currentText, 'STV Regular Font Arabic Text');
|
|
648
739
|
}, 300);
|
|
649
740
|
}
|
|
650
|
-
|
|
741
|
+
|
|
651
742
|
function showSTVBold() {
|
|
652
743
|
clearCanvas();
|
|
653
744
|
currentText = new fabric.Textbox('هذا نص تجريبي بخط STV الغامق للاختبار', {
|
|
@@ -663,16 +754,16 @@ Text Align: ${text.textAlign}
|
|
|
663
754
|
editable: true,
|
|
664
755
|
selectable: true
|
|
665
756
|
});
|
|
666
|
-
|
|
757
|
+
|
|
667
758
|
canvas.add(currentText);
|
|
668
759
|
canvas.renderAll();
|
|
669
|
-
|
|
760
|
+
|
|
670
761
|
setTimeout(() => {
|
|
671
762
|
drawBoundingBoxOverlay(currentText);
|
|
672
763
|
updateDebugInfo(currentText, 'STV Bold Font Arabic Text');
|
|
673
764
|
}, 300);
|
|
674
765
|
}
|
|
675
|
-
|
|
766
|
+
|
|
676
767
|
function showSTVLight() {
|
|
677
768
|
clearCanvas();
|
|
678
769
|
currentText = new fabric.Textbox('هذا نص تجريبي بخط STV الخفيف للاختبار', {
|
|
@@ -688,10 +779,10 @@ Text Align: ${text.textAlign}
|
|
|
688
779
|
editable: true,
|
|
689
780
|
selectable: true
|
|
690
781
|
});
|
|
691
|
-
|
|
782
|
+
|
|
692
783
|
canvas.add(currentText);
|
|
693
784
|
canvas.renderAll();
|
|
694
|
-
|
|
785
|
+
|
|
695
786
|
setTimeout(() => {
|
|
696
787
|
drawBoundingBoxOverlay(currentText);
|
|
697
788
|
updateDebugInfo(currentText, 'STV Light Font Arabic Text');
|
|
@@ -702,17 +793,17 @@ Text Align: ${text.textAlign}
|
|
|
702
793
|
function exportJSON() {
|
|
703
794
|
const json = JSON.stringify(canvas.toJSON(['fontFamily', 'direction', 'textAlign']));
|
|
704
795
|
console.log('Canvas JSON:', json);
|
|
705
|
-
|
|
796
|
+
|
|
706
797
|
// Create download link
|
|
707
798
|
const link = document.createElement('a');
|
|
708
799
|
link.download = `rtl-debug-canvas-${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.json`;
|
|
709
800
|
link.href = 'data:text/json;charset=utf-8,' + encodeURIComponent(json);
|
|
710
|
-
|
|
801
|
+
|
|
711
802
|
// Trigger download
|
|
712
803
|
document.body.appendChild(link);
|
|
713
804
|
link.click();
|
|
714
805
|
document.body.removeChild(link);
|
|
715
|
-
|
|
806
|
+
|
|
716
807
|
debugInfo.textContent = 'Canvas exported as JSON file';
|
|
717
808
|
return json;
|
|
718
809
|
}
|
|
@@ -723,7 +814,7 @@ Text Align: ${text.textAlign}
|
|
|
723
814
|
if (!file) return;
|
|
724
815
|
|
|
725
816
|
const reader = new FileReader();
|
|
726
|
-
reader.onload = function(e) {
|
|
817
|
+
reader.onload = function (e) {
|
|
727
818
|
importJSON(e.target.result);
|
|
728
819
|
};
|
|
729
820
|
reader.readAsText(file);
|
|
@@ -733,15 +824,15 @@ Text Align: ${text.textAlign}
|
|
|
733
824
|
try {
|
|
734
825
|
console.log('Loading JSON into canvas...');
|
|
735
826
|
const parsed = JSON.parse(jsonString);
|
|
736
|
-
|
|
737
|
-
canvas.loadFromJSON(parsed, function() {
|
|
827
|
+
|
|
828
|
+
canvas.loadFromJSON(parsed, function () {
|
|
738
829
|
canvas.requestRenderAll();
|
|
739
830
|
console.log('JSON loaded successfully');
|
|
740
|
-
|
|
831
|
+
|
|
741
832
|
// Find the first text object
|
|
742
833
|
const objects = canvas.getObjects();
|
|
743
834
|
currentText = objects.find(obj => obj.type === 'textbox' || obj.type === 'text' || obj.type === 'i-text');
|
|
744
|
-
|
|
835
|
+
|
|
745
836
|
if (currentText) {
|
|
746
837
|
console.log('Found text object:', currentText);
|
|
747
838
|
setTimeout(() => {
|
|
@@ -751,7 +842,7 @@ Text Align: ${text.textAlign}
|
|
|
751
842
|
updateDebugInfo(currentText, 'Loaded from JSON');
|
|
752
843
|
}, 300); // Extra time for font loading
|
|
753
844
|
}
|
|
754
|
-
|
|
845
|
+
|
|
755
846
|
debugInfo.textContent = `JSON loaded successfully. Found ${objects.length} objects.`;
|
|
756
847
|
});
|
|
757
848
|
} catch (error) {
|
|
@@ -760,8 +851,75 @@ Text Align: ${text.textAlign}
|
|
|
760
851
|
}
|
|
761
852
|
}
|
|
762
853
|
|
|
854
|
+
// Kashida test function
|
|
855
|
+
function showKashidaTest(kashidaLevel) {
|
|
856
|
+
clearCanvas();
|
|
857
|
+
|
|
858
|
+
// Create multiple lines of Arabic text to test justify with kashida
|
|
859
|
+
const arabicText = 'هذا نص تجريبي طويل لاختبار خاصية الكاشيدة في المحاذاة المتساوية للنصوص العربية وهو يحتوي على عدة كلمات';
|
|
860
|
+
|
|
861
|
+
currentText = new fabric.Textbox(arabicText, {
|
|
862
|
+
left: 400,
|
|
863
|
+
top: 150,
|
|
864
|
+
width: 350,
|
|
865
|
+
fontFamily: 'Arial',
|
|
866
|
+
fontSize: 24,
|
|
867
|
+
direction: 'rtl',
|
|
868
|
+
textAlign: 'justify',
|
|
869
|
+
fill: '#333',
|
|
870
|
+
editable: true,
|
|
871
|
+
selectable: true,
|
|
872
|
+
kashida: kashidaLevel,
|
|
873
|
+
enableAdvancedLayout: true
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
canvas.add(currentText);
|
|
877
|
+
canvas.renderAll();
|
|
878
|
+
|
|
879
|
+
setTimeout(() => {
|
|
880
|
+
drawBoundingBoxOverlay(currentText);
|
|
881
|
+
updateDebugInfo(currentText, `Kashida: ${kashidaLevel}`);
|
|
882
|
+
|
|
883
|
+
// Show kashida info in debug
|
|
884
|
+
if (currentText.__kashidaInfo) {
|
|
885
|
+
console.log('Kashida Info:', currentText.__kashidaInfo);
|
|
886
|
+
}
|
|
887
|
+
}, 100);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// Set kashida on current text
|
|
891
|
+
function setKashida(level) {
|
|
892
|
+
if (!currentText) {
|
|
893
|
+
alert('Add a text object first.');
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// Kashida only works with justify alignment
|
|
898
|
+
if (!currentText.textAlign.includes('justify')) {
|
|
899
|
+
currentText.set({ textAlign: 'justify' });
|
|
900
|
+
console.log('Changed textAlign to justify (required for kashida)');
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
currentText.set({ kashida: level });
|
|
904
|
+
|
|
905
|
+
// Force clear cache and reset dimension state to force recalculation
|
|
906
|
+
currentText._clearCache();
|
|
907
|
+
currentText._lastDimensionState = null; // Force initDimensions to recalculate
|
|
908
|
+
currentText.initDimensions();
|
|
909
|
+
currentText.setCoords();
|
|
910
|
+
|
|
911
|
+
canvas.requestRenderAll();
|
|
912
|
+
clearDebugOverlays();
|
|
913
|
+
drawBoundingBoxOverlay(currentText);
|
|
914
|
+
updateDebugInfo(currentText, `Kashida: ${level}`);
|
|
915
|
+
|
|
916
|
+
console.log('Kashida set to:', level);
|
|
917
|
+
console.log('Kashida Info:', currentText.__kashidaInfo);
|
|
918
|
+
}
|
|
919
|
+
|
|
763
920
|
// Initialize with RTL text
|
|
764
921
|
showRTLText();
|
|
765
922
|
</script>
|
|
766
923
|
</body>
|
|
767
|
-
|
|
924
|
+
|
|
925
|
+
</html>
|