@nasser-sw/fabric 7.0.1-beta8 → 7.0.1-beta9
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/debug/konva-master/CHANGELOG.md +1475 -0
- package/debug/konva-master/LICENSE +22 -0
- package/debug/konva-master/README.md +209 -0
- package/debug/konva-master/gulpfile.mjs +110 -0
- package/debug/konva-master/package.json +139 -0
- package/debug/konva-master/release.sh +62 -0
- package/debug/konva-master/resources/doc-includes/ContainerParams.txt +6 -0
- package/debug/konva-master/resources/doc-includes/NodeParams.txt +20 -0
- package/debug/konva-master/resources/doc-includes/ShapeParams.txt +53 -0
- package/debug/konva-master/resources/jsdoc.conf.json +28 -0
- package/debug/konva-master/rollup.config.mjs +32 -0
- package/debug/konva-master/src/Animation.ts +237 -0
- package/debug/konva-master/src/BezierFunctions.ts +826 -0
- package/debug/konva-master/src/Canvas.ts +230 -0
- package/debug/konva-master/src/Container.ts +649 -0
- package/debug/konva-master/src/Context.ts +1017 -0
- package/debug/konva-master/src/Core.ts +5 -0
- package/debug/konva-master/src/DragAndDrop.ts +173 -0
- package/debug/konva-master/src/Factory.ts +246 -0
- package/debug/konva-master/src/FastLayer.ts +29 -0
- package/debug/konva-master/src/Global.ts +210 -0
- package/debug/konva-master/src/Group.ts +31 -0
- package/debug/konva-master/src/Layer.ts +546 -0
- package/debug/konva-master/src/Node.ts +3477 -0
- package/debug/konva-master/src/PointerEvents.ts +67 -0
- package/debug/konva-master/src/Shape.ts +2081 -0
- package/debug/konva-master/src/Stage.ts +1000 -0
- package/debug/konva-master/src/Tween.ts +811 -0
- package/debug/konva-master/src/Util.ts +1123 -0
- package/debug/konva-master/src/Validators.ts +210 -0
- package/debug/konva-master/src/_CoreInternals.ts +85 -0
- package/debug/konva-master/src/_FullInternals.ts +171 -0
- package/debug/konva-master/src/canvas-backend.ts +36 -0
- package/debug/konva-master/src/filters/Blur.ts +388 -0
- package/debug/konva-master/src/filters/Brighten.ts +48 -0
- package/debug/konva-master/src/filters/Brightness.ts +30 -0
- package/debug/konva-master/src/filters/Contrast.ts +75 -0
- package/debug/konva-master/src/filters/Emboss.ts +207 -0
- package/debug/konva-master/src/filters/Enhance.ts +154 -0
- package/debug/konva-master/src/filters/Grayscale.ts +25 -0
- package/debug/konva-master/src/filters/HSL.ts +108 -0
- package/debug/konva-master/src/filters/HSV.ts +106 -0
- package/debug/konva-master/src/filters/Invert.ts +23 -0
- package/debug/konva-master/src/filters/Kaleidoscope.ts +274 -0
- package/debug/konva-master/src/filters/Mask.ts +220 -0
- package/debug/konva-master/src/filters/Noise.ts +44 -0
- package/debug/konva-master/src/filters/Pixelate.ts +107 -0
- package/debug/konva-master/src/filters/Posterize.ts +46 -0
- package/debug/konva-master/src/filters/RGB.ts +82 -0
- package/debug/konva-master/src/filters/RGBA.ts +103 -0
- package/debug/konva-master/src/filters/Sepia.ts +27 -0
- package/debug/konva-master/src/filters/Solarize.ts +29 -0
- package/debug/konva-master/src/filters/Threshold.ts +44 -0
- package/debug/konva-master/src/index.ts +3 -0
- package/debug/konva-master/src/shapes/Arc.ts +176 -0
- package/debug/konva-master/src/shapes/Arrow.ts +231 -0
- package/debug/konva-master/src/shapes/Circle.ts +76 -0
- package/debug/konva-master/src/shapes/Ellipse.ts +121 -0
- package/debug/konva-master/src/shapes/Image.ts +319 -0
- package/debug/konva-master/src/shapes/Label.ts +386 -0
- package/debug/konva-master/src/shapes/Line.ts +364 -0
- package/debug/konva-master/src/shapes/Path.ts +1013 -0
- package/debug/konva-master/src/shapes/Rect.ts +79 -0
- package/debug/konva-master/src/shapes/RegularPolygon.ts +167 -0
- package/debug/konva-master/src/shapes/Ring.ts +94 -0
- package/debug/konva-master/src/shapes/Sprite.ts +370 -0
- package/debug/konva-master/src/shapes/Star.ts +125 -0
- package/debug/konva-master/src/shapes/Text.ts +1065 -0
- package/debug/konva-master/src/shapes/TextPath.ts +583 -0
- package/debug/konva-master/src/shapes/Transformer.ts +1889 -0
- package/debug/konva-master/src/shapes/Wedge.ts +129 -0
- package/debug/konva-master/src/skia-backend.ts +35 -0
- package/debug/konva-master/src/types.ts +84 -0
- package/debug/konva-master/tsconfig.json +31 -0
- package/debug/konva-master/tsconfig.test.json +7 -0
- package/dist/index.js +915 -23
- 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 +915 -23
- package/dist/index.mjs.map +1 -1
- package/dist/index.node.cjs +915 -23
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.mjs +915 -23
- 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/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 +238 -4
- package/dist/src/shapes/Text/Text.mjs.map +1 -1
- package/dist/src/shapes/Textbox.d.ts +38 -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 +497 -15
- package/dist/src/shapes/Textbox.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 +7 -0
- 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-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 +38 -1
- package/dist-extensions/src/shapes/Textbox.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/fabric-test-editor.html +2401 -46
- package/fonts/STV Bold.ttf +0 -0
- package/fonts/STV Light.ttf +0 -0
- package/fonts/STV Regular.ttf +0 -0
- package/package.json +1 -1
- package/src/shapes/Text/Text.ts +238 -5
- package/src/shapes/Textbox.ts +521 -11
- package/src/text/measure.ts +200 -50
- package/src/text/overlayEditor.ts +7 -0
package/fabric-test-editor.html
CHANGED
|
@@ -6,6 +6,27 @@
|
|
|
6
6
|
<title>Fabric.js 7 Test Editor</title>
|
|
7
7
|
<script src="./dist/index.js"></script>
|
|
8
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
|
+
}
|
|
9
30
|
* {
|
|
10
31
|
margin: 0;
|
|
11
32
|
padding: 0;
|
|
@@ -252,6 +273,9 @@
|
|
|
252
273
|
<option value="Courier New">Courier New</option>
|
|
253
274
|
<option value="Helvetica">Helvetica</option>
|
|
254
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>
|
|
255
279
|
</select>
|
|
256
280
|
</div>
|
|
257
281
|
|
|
@@ -304,15 +328,21 @@
|
|
|
304
328
|
<div class="slider-value" id="cornerRadiusValue">0px</div>
|
|
305
329
|
</div>
|
|
306
330
|
<button onclick="testCornerRadius()">Test All Corner Radius</button>
|
|
331
|
+
<button onclick="testCustomFontBounds()">
|
|
332
|
+
Test Custom Font Bounds
|
|
333
|
+
</button>
|
|
307
334
|
</div>
|
|
308
335
|
<div class="control-group">
|
|
309
336
|
<h3>Line Properties</h3>
|
|
310
|
-
<div style="margin-bottom: 10px
|
|
311
|
-
<label
|
|
337
|
+
<div style="margin-bottom: 10px">
|
|
338
|
+
<label
|
|
339
|
+
for="roundedEndpoints"
|
|
340
|
+
style="display: flex; align-items: center; cursor: pointer"
|
|
341
|
+
>
|
|
312
342
|
<input
|
|
313
343
|
type="checkbox"
|
|
314
344
|
id="roundedEndpoints"
|
|
315
|
-
style="margin-right: 8px
|
|
345
|
+
style="margin-right: 8px"
|
|
316
346
|
/>
|
|
317
347
|
Rounded Endpoints (Line Caps)
|
|
318
348
|
</label>
|
|
@@ -321,8 +351,12 @@
|
|
|
321
351
|
|
|
322
352
|
<div class="control-group">
|
|
323
353
|
<h3>Drawing Mode</h3>
|
|
324
|
-
<button id="enableDrawing" onclick="enableDrawing()">
|
|
325
|
-
|
|
354
|
+
<button id="enableDrawing" onclick="enableDrawing()">
|
|
355
|
+
Enable Drawing
|
|
356
|
+
</button>
|
|
357
|
+
<button id="disableDrawing" onclick="disableDrawing()">
|
|
358
|
+
Disable Drawing
|
|
359
|
+
</button>
|
|
326
360
|
<div class="slider-container">
|
|
327
361
|
<label for="brushWidth">Brush Width</label>
|
|
328
362
|
<input
|
|
@@ -355,6 +389,17 @@
|
|
|
355
389
|
<button onclick="changeFontFamily()">Change Font</button>
|
|
356
390
|
</div>
|
|
357
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
|
+
|
|
358
403
|
<div class="control-group">
|
|
359
404
|
<h3>Actions</h3>
|
|
360
405
|
<button onclick="deleteSelected()" class="secondary">
|
|
@@ -362,6 +407,7 @@
|
|
|
362
407
|
</button>
|
|
363
408
|
<button onclick="clearCanvas()" class="secondary">Clear Canvas</button>
|
|
364
409
|
<button onclick="exportJSON()" class="secondary">Export JSON</button>
|
|
410
|
+
<button onclick="importJSON()" class="secondary">Import JSON</button>
|
|
365
411
|
</div>
|
|
366
412
|
|
|
367
413
|
<div class="control-group">
|
|
@@ -382,6 +428,74 @@
|
|
|
382
428
|
<button onclick="fitToWindow()">Fit to Window</button>
|
|
383
429
|
</div>
|
|
384
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
|
+
|
|
385
499
|
<div class="control-group">
|
|
386
500
|
<h3>Canvas Info</h3>
|
|
387
501
|
<div id="canvasInfo" style="font-size: 12px; color: #6b7280">
|
|
@@ -389,6 +503,17 @@
|
|
|
389
503
|
Selected: None<br />
|
|
390
504
|
Zoom: 100%
|
|
391
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>
|
|
392
517
|
</div>
|
|
393
518
|
</div>
|
|
394
519
|
|
|
@@ -397,11 +522,32 @@
|
|
|
397
522
|
</div>
|
|
398
523
|
|
|
399
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
|
+
|
|
400
545
|
// Initialize Fabric.js canvas
|
|
401
546
|
const canvas = new fabric.Canvas('canvas', {
|
|
402
547
|
backgroundColor: 'white',
|
|
403
548
|
selection: true,
|
|
404
549
|
preserveObjectStacking: true,
|
|
550
|
+
skipOffscreen: false, // Disable offscreen culling to show tall textboxes fully
|
|
405
551
|
});
|
|
406
552
|
|
|
407
553
|
// Create workspace (like your editor) - removed 'clip' name to prevent clipping issues with line dragging
|
|
@@ -508,7 +654,100 @@
|
|
|
508
654
|
return 'ltr'; // default
|
|
509
655
|
}
|
|
510
656
|
|
|
511
|
-
//
|
|
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
|
|
512
751
|
function addText() {
|
|
513
752
|
const value =
|
|
514
753
|
document.getElementById('textValue').value || 'Sample Text';
|
|
@@ -535,6 +774,21 @@
|
|
|
535
774
|
console.log('🔄 Auto-detected RTL text, switched direction to RTL');
|
|
536
775
|
}
|
|
537
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
|
+
|
|
538
792
|
const textbox = new fabric.Textbox(value, {
|
|
539
793
|
left: 200,
|
|
540
794
|
top: 200,
|
|
@@ -543,7 +797,6 @@
|
|
|
543
797
|
fontFamily: fontFamily,
|
|
544
798
|
fontWeight: fontWeight,
|
|
545
799
|
fontStyle: fontStyle,
|
|
546
|
-
lineHeight: 1.2,
|
|
547
800
|
|
|
548
801
|
// RTL/LTR configuration (now with auto-detection)
|
|
549
802
|
direction: direction,
|
|
@@ -552,15 +805,32 @@
|
|
|
552
805
|
// ✅ Enable overlay editing (this handles most text editing)
|
|
553
806
|
useOverlayEditing: true,
|
|
554
807
|
|
|
808
|
+
// ✅ Enable advanced layout with improved measurements
|
|
809
|
+
enableAdvancedLayout: false,
|
|
810
|
+
|
|
555
811
|
// ✅ Keep these
|
|
556
812
|
lockMovementX: false,
|
|
557
813
|
lockMovementY: false,
|
|
814
|
+
|
|
815
|
+
// Better spacing for complex scripts
|
|
816
|
+
charSpacing: 0,
|
|
558
817
|
});
|
|
559
818
|
|
|
560
819
|
canvas.add(textbox);
|
|
561
820
|
canvas.setActiveObject(textbox);
|
|
562
821
|
canvas.renderAll();
|
|
563
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
|
+
});
|
|
564
834
|
}
|
|
565
835
|
|
|
566
836
|
// Add shapes functions
|
|
@@ -675,7 +945,8 @@
|
|
|
675
945
|
const strokeColor = document.getElementById('strokeColor').value;
|
|
676
946
|
const strokeWidth =
|
|
677
947
|
parseInt(document.getElementById('strokeWidth').value) || 2;
|
|
678
|
-
const roundedEndpoints =
|
|
948
|
+
const roundedEndpoints =
|
|
949
|
+
document.getElementById('roundedEndpoints').checked;
|
|
679
950
|
|
|
680
951
|
// Create line with absolute coordinates (start and end points)
|
|
681
952
|
const line = new fabric.Line([100, 100, 300, 200], {
|
|
@@ -692,7 +963,9 @@
|
|
|
692
963
|
canvas.renderAll();
|
|
693
964
|
updateCanvasInfo();
|
|
694
965
|
|
|
695
|
-
console.log(
|
|
966
|
+
console.log(
|
|
967
|
+
'✏️ Line added with draggable endpoints! Drag the blue circles to adjust.',
|
|
968
|
+
);
|
|
696
969
|
}
|
|
697
970
|
|
|
698
971
|
// Text alignment function
|
|
@@ -804,6 +1077,335 @@
|
|
|
804
1077
|
URL.revokeObjectURL(url);
|
|
805
1078
|
}
|
|
806
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
|
+
|
|
807
1409
|
// Update canvas info
|
|
808
1410
|
function updateCanvasInfo() {
|
|
809
1411
|
const objects = canvas
|
|
@@ -851,7 +1453,7 @@
|
|
|
851
1453
|
const canvasZoomSlider = document.getElementById('canvasZoom');
|
|
852
1454
|
const canvasZoomValue = document.getElementById('canvasZoomValue');
|
|
853
1455
|
|
|
854
|
-
canvasZoomSlider.addEventListener('input', function() {
|
|
1456
|
+
canvasZoomSlider.addEventListener('input', function () {
|
|
855
1457
|
const zoomLevel = parseFloat(this.value);
|
|
856
1458
|
canvas.setZoom(zoomLevel);
|
|
857
1459
|
canvasZoomValue.textContent = Math.round(zoomLevel * 100) + '%';
|
|
@@ -872,38 +1474,41 @@
|
|
|
872
1474
|
const container = document.querySelector('.canvas-container');
|
|
873
1475
|
const containerWidth = container.clientWidth - 40; // padding
|
|
874
1476
|
const containerHeight = container.clientHeight - 40; // padding
|
|
875
|
-
|
|
1477
|
+
|
|
876
1478
|
const canvasWidth = canvas.getWidth();
|
|
877
1479
|
const canvasHeight = canvas.getHeight();
|
|
878
|
-
|
|
1480
|
+
|
|
879
1481
|
const scaleX = containerWidth / canvasWidth;
|
|
880
1482
|
const scaleY = containerHeight / canvasHeight;
|
|
881
1483
|
const scale = Math.min(scaleX, scaleY, 3); // max zoom 300%
|
|
882
|
-
|
|
1484
|
+
|
|
883
1485
|
canvas.setZoom(scale);
|
|
884
1486
|
canvasZoomSlider.value = scale;
|
|
885
1487
|
canvasZoomValue.textContent = Math.round(scale * 100) + '%';
|
|
886
1488
|
canvas.renderAll();
|
|
887
1489
|
updateCanvasInfo();
|
|
888
|
-
console.log(
|
|
1490
|
+
console.log(
|
|
1491
|
+
'🔍 Canvas zoom fit to window:',
|
|
1492
|
+
Math.round(scale * 100) + '%',
|
|
1493
|
+
);
|
|
889
1494
|
}
|
|
890
1495
|
|
|
891
1496
|
// Mouse wheel zoom support
|
|
892
|
-
canvas.on('mouse:wheel', function(opt) {
|
|
1497
|
+
canvas.on('mouse:wheel', function (opt) {
|
|
893
1498
|
const delta = opt.e.deltaY;
|
|
894
1499
|
let zoom = canvas.getZoom();
|
|
895
1500
|
zoom *= 0.999 ** delta;
|
|
896
|
-
|
|
1501
|
+
|
|
897
1502
|
// Clamp zoom between 10% and 300%
|
|
898
1503
|
zoom = Math.max(0.1, Math.min(3, zoom));
|
|
899
|
-
|
|
1504
|
+
|
|
900
1505
|
canvas.setZoom(zoom);
|
|
901
1506
|
canvasZoomSlider.value = zoom;
|
|
902
1507
|
canvasZoomValue.textContent = Math.round(zoom * 100) + '%';
|
|
903
|
-
|
|
1508
|
+
|
|
904
1509
|
opt.e.preventDefault();
|
|
905
1510
|
opt.e.stopPropagation();
|
|
906
|
-
|
|
1511
|
+
|
|
907
1512
|
updateCanvasInfo();
|
|
908
1513
|
console.log('🔍 Mouse wheel zoom:', Math.round(zoom * 100) + '%');
|
|
909
1514
|
});
|
|
@@ -942,19 +1547,23 @@
|
|
|
942
1547
|
// Drawing mode functions
|
|
943
1548
|
function enableDrawing() {
|
|
944
1549
|
const strokeColor = document.getElementById('strokeColor').value;
|
|
945
|
-
const brushWidth =
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
1550
|
+
const brushWidth =
|
|
1551
|
+
parseInt(document.getElementById('brushWidth').value) || 5;
|
|
1552
|
+
|
|
1553
|
+
console.log('🎨 Enabling drawing mode with:', {
|
|
1554
|
+
strokeColor,
|
|
1555
|
+
brushWidth,
|
|
1556
|
+
});
|
|
1557
|
+
|
|
949
1558
|
// Ensure brush is properly initialized
|
|
950
1559
|
if (!canvas.freeDrawingBrush) {
|
|
951
1560
|
canvas.freeDrawingBrush = new fabric.PencilBrush(canvas);
|
|
952
1561
|
}
|
|
953
|
-
|
|
1562
|
+
|
|
954
1563
|
canvas.freeDrawingBrush.width = brushWidth;
|
|
955
1564
|
canvas.freeDrawingBrush.color = strokeColor;
|
|
956
1565
|
canvas.isDrawingMode = true;
|
|
957
|
-
|
|
1566
|
+
|
|
958
1567
|
console.log('🎨 Drawing mode enabled');
|
|
959
1568
|
updateCanvasInfo();
|
|
960
1569
|
}
|
|
@@ -968,20 +1577,28 @@
|
|
|
968
1577
|
// Brush width slider functionality
|
|
969
1578
|
const brushWidthSlider = document.getElementById('brushWidth');
|
|
970
1579
|
const brushWidthValue = document.getElementById('brushWidthValue');
|
|
971
|
-
|
|
1580
|
+
|
|
972
1581
|
brushWidthSlider.addEventListener('input', function () {
|
|
973
1582
|
const value = parseInt(this.value);
|
|
974
1583
|
brushWidthValue.textContent = value + 'px';
|
|
975
|
-
|
|
976
|
-
console.log(
|
|
977
|
-
|
|
1584
|
+
|
|
1585
|
+
console.log(
|
|
1586
|
+
'🎨 Brush width changed to:',
|
|
1587
|
+
value,
|
|
1588
|
+
'Canvas objects before:',
|
|
1589
|
+
canvas.getObjects().length,
|
|
1590
|
+
);
|
|
1591
|
+
|
|
978
1592
|
// Update brush width in real-time
|
|
979
1593
|
if (canvas.freeDrawingBrush) {
|
|
980
1594
|
canvas.freeDrawingBrush.width = value;
|
|
981
1595
|
console.log('🎨 Set brush width to:', value);
|
|
982
1596
|
}
|
|
983
|
-
|
|
984
|
-
console.log(
|
|
1597
|
+
|
|
1598
|
+
console.log(
|
|
1599
|
+
'🎨 Canvas objects after setting brush width:',
|
|
1600
|
+
canvas.getObjects().length,
|
|
1601
|
+
);
|
|
985
1602
|
});
|
|
986
1603
|
|
|
987
1604
|
// Corner radius slider functionality
|
|
@@ -996,7 +1613,11 @@
|
|
|
996
1613
|
const activeObjects = canvas.getActiveObjects();
|
|
997
1614
|
if (activeObjects.length > 0) {
|
|
998
1615
|
activeObjects.forEach((obj) => {
|
|
999
|
-
if (
|
|
1616
|
+
if (
|
|
1617
|
+
obj.type === 'triangle' ||
|
|
1618
|
+
obj.type === 'polygon' ||
|
|
1619
|
+
obj.type === 'polyline'
|
|
1620
|
+
) {
|
|
1000
1621
|
obj.set('cornerRadius', value);
|
|
1001
1622
|
} else if (obj.type === 'rect') {
|
|
1002
1623
|
// For rectangles, use rx and ry properties
|
|
@@ -1141,48 +1762,1782 @@
|
|
|
1141
1762
|
canvas.add(rect, triangle, hexagon, pentagon, star, arrow, line);
|
|
1142
1763
|
|
|
1143
1764
|
// Add text label
|
|
1144
|
-
const label = new fabric.Textbox(
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
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
|
+
);
|
|
1153
1777
|
|
|
1154
1778
|
canvas.add(label);
|
|
1155
1779
|
canvas.renderAll();
|
|
1156
1780
|
updateCanvasInfo();
|
|
1157
1781
|
|
|
1158
|
-
console.log(
|
|
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
|
+
});
|
|
1159
1990
|
}
|
|
1160
1991
|
|
|
1161
1992
|
// Rounded endpoints checkbox functionality
|
|
1162
|
-
const roundedEndpointsCheckbox =
|
|
1993
|
+
const roundedEndpointsCheckbox =
|
|
1994
|
+
document.getElementById('roundedEndpoints');
|
|
1163
1995
|
roundedEndpointsCheckbox.addEventListener('change', function () {
|
|
1164
1996
|
const isChecked = this.checked;
|
|
1165
1997
|
console.log('🔧 Rounded endpoints checkbox changed:', isChecked);
|
|
1166
1998
|
// Update strokeLineCap of selected line object(s) in real-time using Fabric.js built-in property
|
|
1167
1999
|
const activeObjects = canvas.getActiveObjects();
|
|
1168
|
-
console.log(
|
|
2000
|
+
console.log(
|
|
2001
|
+
'🔧 Active objects:',
|
|
2002
|
+
activeObjects.length,
|
|
2003
|
+
activeObjects.map((o) => o.type),
|
|
2004
|
+
);
|
|
1169
2005
|
if (activeObjects.length > 0) {
|
|
1170
2006
|
activeObjects.forEach((obj) => {
|
|
1171
2007
|
if (obj.type === 'line') {
|
|
1172
2008
|
const lineCap = isChecked ? 'round' : 'butt';
|
|
1173
2009
|
console.log('🔧 Setting strokeLineCap on line:', lineCap);
|
|
1174
2010
|
obj.set('strokeLineCap', lineCap);
|
|
1175
|
-
console.log(
|
|
2011
|
+
console.log(
|
|
2012
|
+
'🔧 Line strokeLineCap after set:',
|
|
2013
|
+
obj.strokeLineCap,
|
|
2014
|
+
);
|
|
1176
2015
|
}
|
|
1177
2016
|
});
|
|
1178
2017
|
canvas.renderAll();
|
|
1179
2018
|
}
|
|
1180
2019
|
});
|
|
1181
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
|
+
|
|
1182
3532
|
// Log Fabric.js version
|
|
1183
3533
|
console.log('Fabric.js version:', fabric.version);
|
|
1184
3534
|
console.log('Canvas initialized with useOverlayEditing support');
|
|
1185
|
-
console.log(
|
|
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
|
+
);
|
|
1186
3541
|
|
|
1187
3542
|
// Test overlay editing on double-click
|
|
1188
3543
|
canvas.on('mouse:dblclick', function (options) {
|