@nasser-sw/fabric 7.0.1-beta16 → 7.0.1-beta17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +7 -0
- package/dist/index.js +1982 -649
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/index.min.mjs +1 -1
- package/dist/index.min.mjs.map +1 -1
- package/dist/index.mjs +1982 -649
- package/dist/index.mjs.map +1 -1
- package/dist/index.node.cjs +1982 -649
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.mjs +1982 -649
- package/dist/index.node.mjs.map +1 -1
- package/dist/package.json.min.mjs +1 -1
- package/dist/package.json.mjs +1 -1
- package/dist/src/shapes/IText/IText.d.ts +31 -6
- package/dist/src/shapes/IText/IText.d.ts.map +1 -1
- package/dist/src/shapes/IText/IText.min.mjs +1 -1
- package/dist/src/shapes/IText/IText.min.mjs.map +1 -1
- package/dist/src/shapes/IText/IText.mjs +495 -126
- package/dist/src/shapes/IText/IText.mjs.map +1 -1
- package/dist/src/shapes/IText/ITextBehavior.d.ts +12 -0
- package/dist/src/shapes/IText/ITextBehavior.d.ts.map +1 -1
- package/dist/src/shapes/IText/ITextBehavior.min.mjs +1 -1
- package/dist/src/shapes/IText/ITextBehavior.min.mjs.map +1 -1
- package/dist/src/shapes/IText/ITextBehavior.mjs +127 -36
- package/dist/src/shapes/IText/ITextBehavior.mjs.map +1 -1
- package/dist/src/shapes/IText/ITextClickBehavior.d.ts.map +1 -1
- package/dist/src/shapes/IText/ITextClickBehavior.min.mjs +1 -1
- package/dist/src/shapes/IText/ITextClickBehavior.min.mjs.map +1 -1
- package/dist/src/shapes/IText/ITextClickBehavior.mjs +21 -4
- package/dist/src/shapes/IText/ITextClickBehavior.mjs.map +1 -1
- package/dist/src/shapes/IText/ITextKeyBehavior.min.mjs +1 -1
- package/dist/src/shapes/IText/ITextKeyBehavior.min.mjs.map +1 -1
- package/dist/src/shapes/IText/ITextKeyBehavior.mjs +17 -21
- package/dist/src/shapes/IText/ITextKeyBehavior.mjs.map +1 -1
- package/dist/src/shapes/Text/Text.d.ts +69 -1
- package/dist/src/shapes/Text/Text.d.ts.map +1 -1
- package/dist/src/shapes/Text/Text.min.mjs +1 -1
- package/dist/src/shapes/Text/Text.min.mjs.map +1 -1
- package/dist/src/shapes/Text/Text.mjs +374 -60
- package/dist/src/shapes/Text/Text.mjs.map +1 -1
- package/dist/src/shapes/Text/constants.d.ts.map +1 -1
- package/dist/src/shapes/Text/constants.min.mjs +1 -1
- package/dist/src/shapes/Text/constants.min.mjs.map +1 -1
- package/dist/src/shapes/Text/constants.mjs +2 -1
- package/dist/src/shapes/Text/constants.mjs.map +1 -1
- package/dist/src/shapes/Textbox.d.ts +8 -1
- package/dist/src/shapes/Textbox.d.ts.map +1 -1
- package/dist/src/shapes/Textbox.min.mjs +1 -1
- package/dist/src/shapes/Textbox.min.mjs.map +1 -1
- package/dist/src/shapes/Textbox.mjs +406 -63
- package/dist/src/shapes/Textbox.mjs.map +1 -1
- package/dist/src/text/hitTest.min.mjs +1 -1
- package/dist/src/text/hitTest.min.mjs.map +1 -1
- package/dist/src/text/hitTest.mjs +1 -198
- package/dist/src/text/hitTest.mjs.map +1 -1
- package/dist/src/text/layout.min.mjs +1 -1
- package/dist/src/text/layout.min.mjs.map +1 -1
- package/dist/src/text/layout.mjs +122 -5
- package/dist/src/text/layout.mjs.map +1 -1
- package/dist/src/text/overlayEditor.min.mjs +1 -1
- package/dist/src/text/overlayEditor.min.mjs.map +1 -1
- package/dist/src/text/overlayEditor.mjs +132 -142
- package/dist/src/text/overlayEditor.mjs.map +1 -1
- package/dist/src/text/unicode.d.ts +28 -0
- package/dist/src/text/unicode.d.ts.map +1 -1
- package/dist/src/text/unicode.min.mjs +1 -1
- package/dist/src/text/unicode.min.mjs.map +1 -1
- package/dist/src/text/unicode.mjs +294 -1
- package/dist/src/text/unicode.mjs.map +1 -1
- package/dist-extensions/src/shapes/IText/IText.d.ts +31 -6
- package/dist-extensions/src/shapes/IText/IText.d.ts.map +1 -1
- package/dist-extensions/src/shapes/IText/ITextBehavior.d.ts +12 -0
- package/dist-extensions/src/shapes/IText/ITextBehavior.d.ts.map +1 -1
- package/dist-extensions/src/shapes/IText/ITextClickBehavior.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Text/Text.d.ts +69 -1
- package/dist-extensions/src/shapes/Text/Text.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Text/constants.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Textbox.d.ts +8 -1
- package/dist-extensions/src/shapes/Textbox.d.ts.map +1 -1
- package/dist-extensions/src/text/unicode.d.ts +28 -0
- package/dist-extensions/src/text/unicode.d.ts.map +1 -1
- package/package.json +164 -164
- package/rtl-debug.html +358 -200
- package/src/shapes/IText/IText.ts +524 -110
- package/src/shapes/IText/ITextBehavior.ts +174 -80
- package/src/shapes/IText/ITextClickBehavior.ts +20 -6
- package/src/shapes/IText/ITextKeyBehavior.ts +15 -15
- package/src/shapes/Text/Text.ts +488 -107
- package/src/shapes/Text/constants.ts +4 -2
- package/src/shapes/Textbox.ts +414 -65
- package/src/text/layout.ts +150 -23
- package/src/text/overlayEditor.ts +148 -148
- package/src/text/unicode.ts +177 -2
|
@@ -240,9 +240,9 @@ export class OverlayEditor {
|
|
|
240
240
|
requestAnimationFrame(() => {
|
|
241
241
|
if (!this.isDestroyed) {
|
|
242
242
|
this.applyOverlayStyle();
|
|
243
|
-
console.log(
|
|
244
|
-
|
|
245
|
-
);
|
|
243
|
+
// console.log(
|
|
244
|
+
// '📐 Height changed - rechecking alignment after repositioning:',
|
|
245
|
+
// );
|
|
246
246
|
}
|
|
247
247
|
});
|
|
248
248
|
}
|
|
@@ -340,7 +340,7 @@ export class OverlayEditor {
|
|
|
340
340
|
|
|
341
341
|
// Special handling for text objects loaded from JSON - ensure they're properly initialized
|
|
342
342
|
if (target.dirty !== false && target.initDimensions) {
|
|
343
|
-
console.log('🔧 Ensuring text object is properly initialized before overlay editing');
|
|
343
|
+
// console.log('🔧 Ensuring text object is properly initialized before overlay editing');
|
|
344
344
|
// Force re-initialization if the text object seems to be in a dirty state
|
|
345
345
|
target.initDimensions();
|
|
346
346
|
}
|
|
@@ -358,11 +358,11 @@ export class OverlayEditor {
|
|
|
358
358
|
const autoDetectedDirection = this.firstStrongDir(this.textarea.value || '');
|
|
359
359
|
|
|
360
360
|
// DEBUG: Log alignment details
|
|
361
|
-
console.log('🔍 ALIGNMENT DEBUG:');
|
|
362
|
-
console.log(' Fabric textAlign:', textAlign);
|
|
363
|
-
console.log(' Fabric direction:', (target as any).direction);
|
|
364
|
-
console.log(' Text content:', JSON.stringify(target.text));
|
|
365
|
-
console.log(' Detected direction:', autoDetectedDirection);
|
|
361
|
+
// console.log('🔍 ALIGNMENT DEBUG:');
|
|
362
|
+
// console.log(' Fabric textAlign:', textAlign);
|
|
363
|
+
// console.log(' Fabric direction:', (target as any).direction);
|
|
364
|
+
// console.log(' Text content:', JSON.stringify(target.text));
|
|
365
|
+
// console.log(' Detected direction:', autoDetectedDirection);
|
|
366
366
|
|
|
367
367
|
// Map fabric.js justify to CSS
|
|
368
368
|
if (textAlign.includes('justify')) {
|
|
@@ -380,7 +380,7 @@ export class OverlayEditor {
|
|
|
380
380
|
// If text is RTL but fabric says justify-left, override to justify-right for better UX
|
|
381
381
|
if (autoDetectedDirection === 'rtl') {
|
|
382
382
|
this.textarea.style.textAlignLast = 'right';
|
|
383
|
-
console.log(' → Overrode justify-left to justify-right for RTL text');
|
|
383
|
+
// console.log(' → Overrode justify-left to justify-right for RTL text');
|
|
384
384
|
} else {
|
|
385
385
|
this.textarea.style.textAlignLast = 'left';
|
|
386
386
|
}
|
|
@@ -388,7 +388,7 @@ export class OverlayEditor {
|
|
|
388
388
|
// If text is LTR but fabric says justify-right, override to justify-left for better UX
|
|
389
389
|
if (autoDetectedDirection === 'ltr') {
|
|
390
390
|
this.textarea.style.textAlignLast = 'left';
|
|
391
|
-
console.log(' → Overrode justify-right to justify-left for LTR text');
|
|
391
|
+
// console.log(' → Overrode justify-right to justify-left for LTR text');
|
|
392
392
|
} else {
|
|
393
393
|
this.textarea.style.textAlignLast = 'right';
|
|
394
394
|
}
|
|
@@ -408,16 +408,16 @@ export class OverlayEditor {
|
|
|
408
408
|
(this.textarea.style as any).textJustifyTrim = 'none';
|
|
409
409
|
(this.textarea.style as any).textAutospace = 'none';
|
|
410
410
|
|
|
411
|
-
console.log(' → Applied justify alignment:', textAlign, 'with last-line:', this.textarea.style.textAlignLast);
|
|
411
|
+
// console.log(' → Applied justify alignment:', textAlign, 'with last-line:', this.textarea.style.textAlignLast);
|
|
412
412
|
} catch (error) {
|
|
413
|
-
console.warn(' → Justify setup failed, falling back to standard alignment:', error);
|
|
413
|
+
// console.warn(' → Justify setup failed, falling back to standard alignment:', error);
|
|
414
414
|
cssTextAlign = textAlign.replace('justify-', '').replace('justify', 'left');
|
|
415
415
|
}
|
|
416
416
|
} else {
|
|
417
417
|
this.textarea.style.textAlignLast = 'auto';
|
|
418
418
|
(this.textarea.style as any).textJustify = 'auto';
|
|
419
419
|
(this.textarea.style as any).wordSpacing = 'normal';
|
|
420
|
-
console.log(' → Applied standard alignment:', cssTextAlign);
|
|
420
|
+
// console.log(' → Applied standard alignment:', cssTextAlign);
|
|
421
421
|
}
|
|
422
422
|
|
|
423
423
|
this.textarea.style.textAlign = cssTextAlign;
|
|
@@ -444,37 +444,37 @@ export class OverlayEditor {
|
|
|
444
444
|
this.textarea.style.hyphens = 'none';
|
|
445
445
|
|
|
446
446
|
// DEBUG: Log final CSS properties
|
|
447
|
-
console.log('🎨 FINAL TEXTAREA CSS:');
|
|
448
|
-
console.log(' textAlign:', this.textarea.style.textAlign);
|
|
449
|
-
console.log(' textAlignLast:', this.textarea.style.textAlignLast);
|
|
450
|
-
console.log(' direction:', this.textarea.style.direction);
|
|
451
|
-
console.log(' unicodeBidi:', this.textarea.style.unicodeBidi);
|
|
452
|
-
console.log(' width:', this.textarea.style.width);
|
|
453
|
-
console.log(' textJustify:', (this.textarea.style as any).textJustify);
|
|
454
|
-
console.log(' wordSpacing:', (this.textarea.style as any).wordSpacing);
|
|
455
|
-
console.log(' whiteSpace:', this.textarea.style.whiteSpace);
|
|
447
|
+
// console.log('🎨 FINAL TEXTAREA CSS:');
|
|
448
|
+
// console.log(' textAlign:', this.textarea.style.textAlign);
|
|
449
|
+
// console.log(' textAlignLast:', this.textarea.style.textAlignLast);
|
|
450
|
+
// console.log(' direction:', this.textarea.style.direction);
|
|
451
|
+
// console.log(' unicodeBidi:', this.textarea.style.unicodeBidi);
|
|
452
|
+
// console.log(' width:', this.textarea.style.width);
|
|
453
|
+
// console.log(' textJustify:', (this.textarea.style as any).textJustify);
|
|
454
|
+
// console.log(' wordSpacing:', (this.textarea.style as any).wordSpacing);
|
|
455
|
+
// console.log(' whiteSpace:', this.textarea.style.whiteSpace);
|
|
456
456
|
|
|
457
457
|
// If justify, log Fabric object dimensions for comparison
|
|
458
458
|
if (textAlign.includes('justify')) {
|
|
459
|
-
console.log('🔧 FABRIC OBJECT JUSTIFY INFO:');
|
|
460
|
-
console.log(' Fabric width:', (target as any).width);
|
|
461
|
-
console.log(' Fabric calcTextWidth:', (target as any).calcTextWidth?.());
|
|
462
|
-
console.log(' Fabric textAlign:', (target as any).textAlign);
|
|
463
|
-
console.log(' Text lines:', (target as any).textLines);
|
|
459
|
+
// console.log('🔧 FABRIC OBJECT JUSTIFY INFO:');
|
|
460
|
+
// console.log(' Fabric width:', (target as any).width);
|
|
461
|
+
// console.log(' Fabric calcTextWidth:', (target as any).calcTextWidth?.());
|
|
462
|
+
// console.log(' Fabric textAlign:', (target as any).textAlign);
|
|
463
|
+
// console.log(' Text lines:', (target as any).textLines);
|
|
464
464
|
}
|
|
465
465
|
|
|
466
466
|
// Debug font properties matching
|
|
467
|
-
console.log('🔤 FONT PROPERTIES COMPARISON:');
|
|
468
|
-
console.log(' Fabric fontFamily:', target.fontFamily);
|
|
469
|
-
console.log(' Fabric fontWeight:', target.fontWeight);
|
|
470
|
-
console.log(' Fabric fontStyle:', target.fontStyle);
|
|
471
|
-
console.log(' Fabric fontSize:', target.fontSize);
|
|
472
|
-
console.log(' → Textarea fontFamily:', this.textarea.style.fontFamily);
|
|
473
|
-
console.log(' → Textarea fontWeight:', this.textarea.style.fontWeight);
|
|
474
|
-
console.log(' → Textarea fontStyle:', this.textarea.style.fontStyle);
|
|
475
|
-
console.log(' → Textarea fontSize:', this.textarea.style.fontSize);
|
|
467
|
+
// console.log('🔤 FONT PROPERTIES COMPARISON:');
|
|
468
|
+
// console.log(' Fabric fontFamily:', target.fontFamily);
|
|
469
|
+
// console.log(' Fabric fontWeight:', target.fontWeight);
|
|
470
|
+
// console.log(' Fabric fontStyle:', target.fontStyle);
|
|
471
|
+
// console.log(' Fabric fontSize:', target.fontSize);
|
|
472
|
+
// console.log(' → Textarea fontFamily:', this.textarea.style.fontFamily);
|
|
473
|
+
// console.log(' → Textarea fontWeight:', this.textarea.style.fontWeight);
|
|
474
|
+
// console.log(' → Textarea fontStyle:', this.textarea.style.fontStyle);
|
|
475
|
+
// console.log(' → Textarea fontSize:', this.textarea.style.fontSize);
|
|
476
476
|
|
|
477
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
477
|
+
// console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
478
478
|
|
|
479
479
|
// Enhanced font rendering to better match fabric.js canvas rendering
|
|
480
480
|
// Default to auto for more natural rendering
|
|
@@ -491,12 +491,12 @@ export class OverlayEditor {
|
|
|
491
491
|
if (isBold) {
|
|
492
492
|
(this.textarea.style as any).webkitFontSmoothing = 'subpixel-antialiased';
|
|
493
493
|
(this.textarea.style as any).mozOsxFontSmoothing = 'unset';
|
|
494
|
-
console.log('🔤 Applied enhanced bold rendering for better thickness matching');
|
|
494
|
+
// console.log('🔤 Applied enhanced bold rendering for better thickness matching');
|
|
495
495
|
}
|
|
496
496
|
|
|
497
|
-
console.log('🎨 FONT SMOOTHING APPLIED:');
|
|
498
|
-
console.log(' webkitFontSmoothing:', (this.textarea.style as any).webkitFontSmoothing);
|
|
499
|
-
console.log(' mozOsxFontSmoothing:', (this.textarea.style as any).mozOsxFontSmoothing);
|
|
497
|
+
// console.log('🎨 FONT SMOOTHING APPLIED:');
|
|
498
|
+
// console.log(' webkitFontSmoothing:', (this.textarea.style as any).webkitFontSmoothing);
|
|
499
|
+
// console.log(' mozOsxFontSmoothing:', (this.textarea.style as any).mozOsxFontSmoothing);
|
|
500
500
|
|
|
501
501
|
|
|
502
502
|
// Initial bounds are set correctly by Fabric.js - don't force update here
|
|
@@ -527,26 +527,26 @@ export class OverlayEditor {
|
|
|
527
527
|
height: canvasBounds.height * zoom,
|
|
528
528
|
};
|
|
529
529
|
|
|
530
|
-
console.log('🔍 BOUNDING BOX COMPARISON:');
|
|
531
|
-
console.log('📦 Textarea Rect:', {
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
});
|
|
537
|
-
console.log('📦 Host Div Rect:', {
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
});
|
|
543
|
-
console.log('📦 Canvas Object Bounds (screen):', {
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
});
|
|
549
|
-
console.log('📦 Canvas Object Bounds (canvas):', canvasBounds);
|
|
530
|
+
// console.log('🔍 BOUNDING BOX COMPARISON:');
|
|
531
|
+
// console.log('📦 Textarea Rect:', {
|
|
532
|
+
// left: Math.round(textareaRect.left * 100) / 100,
|
|
533
|
+
// top: Math.round(textareaRect.top * 100) / 100,
|
|
534
|
+
// width: Math.round(textareaRect.width * 100) / 100,
|
|
535
|
+
// height: Math.round(textareaRect.height * 100) / 100,
|
|
536
|
+
// });
|
|
537
|
+
// console.log('📦 Host Div Rect:', {
|
|
538
|
+
// left: Math.round(hostRect.left * 100) / 100,
|
|
539
|
+
// top: Math.round(hostRect.top * 100) / 100,
|
|
540
|
+
// width: Math.round(hostRect.width * 100) / 100,
|
|
541
|
+
// height: Math.round(hostRect.height * 100) / 100,
|
|
542
|
+
// });
|
|
543
|
+
// console.log('📦 Canvas Object Bounds (screen):', {
|
|
544
|
+
// left: Math.round(screenObjectBounds.left * 100) / 100,
|
|
545
|
+
// top: Math.round(screenObjectBounds.top * 100) / 100,
|
|
546
|
+
// width: Math.round(screenObjectBounds.width * 100) / 100,
|
|
547
|
+
// height: Math.round(screenObjectBounds.height * 100) / 100,
|
|
548
|
+
// });
|
|
549
|
+
// console.log('📦 Canvas Object Bounds (canvas):', canvasBounds);
|
|
550
550
|
|
|
551
551
|
// Calculate differences
|
|
552
552
|
const hostVsObject = {
|
|
@@ -571,8 +571,8 @@ export class OverlayEditor {
|
|
|
571
571
|
100,
|
|
572
572
|
};
|
|
573
573
|
|
|
574
|
-
console.log('📏 Host Div vs Canvas Object Diff:', hostVsObject);
|
|
575
|
-
console.log('📏 Textarea vs Canvas Object Diff:', textareaVsObject);
|
|
574
|
+
// console.log('📏 Host Div vs Canvas Object Diff:', hostVsObject);
|
|
575
|
+
// console.log('📏 Textarea vs Canvas Object Diff:', textareaVsObject);
|
|
576
576
|
|
|
577
577
|
// Check if they're aligned (within 2px tolerance)
|
|
578
578
|
const tolerance = 2;
|
|
@@ -588,18 +588,18 @@ export class OverlayEditor {
|
|
|
588
588
|
Math.abs(textareaVsObject.widthDiff) < tolerance &&
|
|
589
589
|
Math.abs(textareaVsObject.heightDiff) < tolerance;
|
|
590
590
|
|
|
591
|
-
console.log(
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
);
|
|
596
|
-
console.log(
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
);
|
|
601
|
-
console.log('🔍 Zoom:', zoom, 'Viewport Transform:', vpt);
|
|
602
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
591
|
+
// console.log(
|
|
592
|
+
// hostAligned
|
|
593
|
+
// ? '✅ Host Div ALIGNED with canvas object'
|
|
594
|
+
// : '❌ Host Div MISALIGNED with canvas object',
|
|
595
|
+
// );
|
|
596
|
+
// console.log(
|
|
597
|
+
// textareaAligned
|
|
598
|
+
// ? '✅ Textarea ALIGNED with canvas object'
|
|
599
|
+
// : '❌ Textarea MISALIGNED with canvas object',
|
|
600
|
+
// );
|
|
601
|
+
// console.log('🔍 Zoom:', zoom, 'Viewport Transform:', vpt);
|
|
602
|
+
// console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
603
603
|
}
|
|
604
604
|
|
|
605
605
|
/**
|
|
@@ -609,44 +609,44 @@ export class OverlayEditor {
|
|
|
609
609
|
const target = this.target;
|
|
610
610
|
const text = this.textarea.value;
|
|
611
611
|
|
|
612
|
-
console.log('📝 TEXT WRAPPING COMPARISON:');
|
|
613
|
-
console.log('📄 Text Content:', `"${text}"`);
|
|
614
|
-
console.log('📄 Text Length:', text.length);
|
|
612
|
+
// console.log('📝 TEXT WRAPPING COMPARISON:');
|
|
613
|
+
// console.log('📄 Text Content:', `"${text}"`);
|
|
614
|
+
// console.log('📄 Text Length:', text.length);
|
|
615
615
|
|
|
616
616
|
// Analyze line breaks
|
|
617
617
|
const explicitLines = text.split('\n');
|
|
618
|
-
console.log('📄 Explicit Lines (\\n):', explicitLines.length);
|
|
618
|
+
// console.log('📄 Explicit Lines (\\n):', explicitLines.length);
|
|
619
619
|
explicitLines.forEach((line, i) => {
|
|
620
|
-
console.log(` Line ${i + 1}: "${line}" (${line.length} chars)`);
|
|
620
|
+
// console.log(` Line ${i + 1}: "${line}" (${line.length} chars)`);
|
|
621
621
|
});
|
|
622
622
|
|
|
623
623
|
// Get textarea computed styles for wrapping analysis
|
|
624
624
|
const textareaStyles = window.getComputedStyle(this.textarea);
|
|
625
|
-
console.log('📐 Textarea Wrapping Styles:');
|
|
626
|
-
console.log(' width:', textareaStyles.width);
|
|
627
|
-
console.log(' fontSize:', textareaStyles.fontSize);
|
|
628
|
-
console.log(' fontFamily:', textareaStyles.fontFamily);
|
|
629
|
-
console.log(' fontWeight:', textareaStyles.fontWeight);
|
|
630
|
-
console.log(' letterSpacing:', textareaStyles.letterSpacing);
|
|
631
|
-
console.log(' lineHeight:', textareaStyles.lineHeight);
|
|
632
|
-
console.log(' whiteSpace:', textareaStyles.whiteSpace);
|
|
633
|
-
console.log(' wordWrap:', textareaStyles.wordWrap);
|
|
634
|
-
console.log(' overflowWrap:', textareaStyles.overflowWrap);
|
|
635
|
-
console.log(' direction:', textareaStyles.direction);
|
|
636
|
-
console.log(' textAlign:', textareaStyles.textAlign);
|
|
625
|
+
// console.log('📐 Textarea Wrapping Styles:');
|
|
626
|
+
// console.log(' width:', textareaStyles.width);
|
|
627
|
+
// console.log(' fontSize:', textareaStyles.fontSize);
|
|
628
|
+
// console.log(' fontFamily:', textareaStyles.fontFamily);
|
|
629
|
+
// console.log(' fontWeight:', textareaStyles.fontWeight);
|
|
630
|
+
// console.log(' letterSpacing:', textareaStyles.letterSpacing);
|
|
631
|
+
// console.log(' lineHeight:', textareaStyles.lineHeight);
|
|
632
|
+
// console.log(' whiteSpace:', textareaStyles.whiteSpace);
|
|
633
|
+
// console.log(' wordWrap:', textareaStyles.wordWrap);
|
|
634
|
+
// console.log(' overflowWrap:', textareaStyles.overflowWrap);
|
|
635
|
+
// console.log(' direction:', textareaStyles.direction);
|
|
636
|
+
// console.log(' textAlign:', textareaStyles.textAlign);
|
|
637
637
|
|
|
638
638
|
// Get Fabric text object properties for comparison
|
|
639
|
-
console.log('📐 Fabric Text Object Properties:');
|
|
640
|
-
console.log(' width:', (target as any).width);
|
|
641
|
-
console.log(' fontSize:', target.fontSize);
|
|
642
|
-
console.log(' fontFamily:', target.fontFamily);
|
|
643
|
-
console.log(' fontWeight:', target.fontWeight);
|
|
644
|
-
console.log(' charSpacing:', target.charSpacing);
|
|
645
|
-
console.log(' lineHeight:', target.lineHeight);
|
|
646
|
-
console.log(' direction:', (target as any).direction);
|
|
647
|
-
console.log(' textAlign:', (target as any).textAlign);
|
|
648
|
-
console.log(' scaleX:', target.scaleX);
|
|
649
|
-
console.log(' scaleY:', target.scaleY);
|
|
639
|
+
// console.log('📐 Fabric Text Object Properties:');
|
|
640
|
+
// console.log(' width:', (target as any).width);
|
|
641
|
+
// console.log(' fontSize:', target.fontSize);
|
|
642
|
+
// console.log(' fontFamily:', target.fontFamily);
|
|
643
|
+
// console.log(' fontWeight:', target.fontWeight);
|
|
644
|
+
// console.log(' charSpacing:', target.charSpacing);
|
|
645
|
+
// console.log(' lineHeight:', target.lineHeight);
|
|
646
|
+
// console.log(' direction:', (target as any).direction);
|
|
647
|
+
// console.log(' textAlign:', (target as any).textAlign);
|
|
648
|
+
// console.log(' scaleX:', target.scaleX);
|
|
649
|
+
// console.log(' scaleY:', target.scaleY);
|
|
650
650
|
|
|
651
651
|
// Calculate effective dimensions for comparison - use actual rendered width
|
|
652
652
|
// **THE FIX:** Use getBoundingRect to get the *actual rendered width* of the Fabric object.
|
|
@@ -659,15 +659,15 @@ export class OverlayEditor {
|
|
|
659
659
|
textareaComputedWidth / this.canvas.getZoom();
|
|
660
660
|
const widthDiff = Math.abs(textareaEffectiveWidth - fabricEffectiveWidth);
|
|
661
661
|
|
|
662
|
-
console.log('📏 Effective Width Comparison:');
|
|
663
|
-
console.log(' Textarea Effective Width:', textareaEffectiveWidth);
|
|
664
|
-
console.log(' Fabric Effective Width:', fabricEffectiveWidth);
|
|
665
|
-
console.log(' Width Difference:', widthDiff.toFixed(2) + 'px');
|
|
666
|
-
console.log(
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
);
|
|
662
|
+
// console.log('📏 Effective Width Comparison:');
|
|
663
|
+
// console.log(' Textarea Effective Width:', textareaEffectiveWidth);
|
|
664
|
+
// console.log(' Fabric Effective Width:', fabricEffectiveWidth);
|
|
665
|
+
// console.log(' Width Difference:', widthDiff.toFixed(2) + 'px');
|
|
666
|
+
// console.log(
|
|
667
|
+
// widthDiff < 1
|
|
668
|
+
// ? '✅ Widths MATCH for wrapping'
|
|
669
|
+
// : '❌ Width MISMATCH may cause different wrapping',
|
|
670
|
+
// );
|
|
671
671
|
|
|
672
672
|
// Check text direction and bidi handling
|
|
673
673
|
const hasRTLText =
|
|
@@ -676,12 +676,12 @@ export class OverlayEditor {
|
|
|
676
676
|
);
|
|
677
677
|
const hasBidiText = /[\u0590-\u06FF]/.test(text) && /[a-zA-Z]/.test(text);
|
|
678
678
|
|
|
679
|
-
console.log('🌍 Text Direction Analysis:');
|
|
680
|
-
console.log(' Has RTL characters:', hasRTLText);
|
|
681
|
-
console.log(' Has mixed Bidi text:', hasBidiText);
|
|
682
|
-
console.log(' Textarea direction:', textareaStyles.direction);
|
|
683
|
-
console.log(' Fabric direction:', (target as any).direction || 'auto');
|
|
684
|
-
console.log(' Textarea unicodeBidi:', textareaStyles.unicodeBidi);
|
|
679
|
+
// console.log('🌍 Text Direction Analysis:');
|
|
680
|
+
// console.log(' Has RTL characters:', hasRTLText);
|
|
681
|
+
// console.log(' Has mixed Bidi text:', hasBidiText);
|
|
682
|
+
// console.log(' Textarea direction:', textareaStyles.direction);
|
|
683
|
+
// console.log(' Fabric direction:', (target as any).direction || 'auto');
|
|
684
|
+
// console.log(' Textarea unicodeBidi:', textareaStyles.unicodeBidi);
|
|
685
685
|
|
|
686
686
|
// Measure actual rendered line count
|
|
687
687
|
const textareaScrollHeight = this.textarea.scrollHeight;
|
|
@@ -692,23 +692,23 @@ export class OverlayEditor {
|
|
|
692
692
|
textareaScrollHeight / textareaLineHeight,
|
|
693
693
|
);
|
|
694
694
|
|
|
695
|
-
console.log('📊 Line Count Analysis:');
|
|
696
|
-
console.log(' Textarea scrollHeight:', textareaScrollHeight);
|
|
697
|
-
console.log(' Textarea lineHeight:', textareaLineHeight);
|
|
698
|
-
console.log(' Estimated rendered lines:', estimatedTextareaLines);
|
|
699
|
-
console.log(' Explicit line breaks:', explicitLines.length);
|
|
695
|
+
// console.log('📊 Line Count Analysis:');
|
|
696
|
+
// console.log(' Textarea scrollHeight:', textareaScrollHeight);
|
|
697
|
+
// console.log(' Textarea lineHeight:', textareaLineHeight);
|
|
698
|
+
// console.log(' Estimated rendered lines:', estimatedTextareaLines);
|
|
699
|
+
// console.log(' Explicit line breaks:', explicitLines.length);
|
|
700
700
|
|
|
701
701
|
if (estimatedTextareaLines > explicitLines.length) {
|
|
702
|
-
console.log('🔄 Text wrapping detected in textarea');
|
|
703
|
-
console.log(
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
);
|
|
702
|
+
// console.log('🔄 Text wrapping detected in textarea');
|
|
703
|
+
// console.log(
|
|
704
|
+
// ' Wrapped lines:',
|
|
705
|
+
// estimatedTextareaLines - explicitLines.length,
|
|
706
|
+
// );
|
|
707
707
|
} else {
|
|
708
|
-
console.log('📏 No text wrapping in textarea');
|
|
708
|
+
// console.log('📏 No text wrapping in textarea');
|
|
709
709
|
}
|
|
710
710
|
|
|
711
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
711
|
+
// console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
712
712
|
}
|
|
713
713
|
|
|
714
714
|
|
|
@@ -795,28 +795,28 @@ export class OverlayEditor {
|
|
|
795
795
|
|
|
796
796
|
// Handle commit/cancel after restoring visibility
|
|
797
797
|
if (commit && !this.isComposing) {
|
|
798
|
-
const finalText = this.textarea.value;
|
|
799
|
-
|
|
800
|
-
// Auto-detect text direction and update fabric object if needed
|
|
801
|
-
const detectedDirection = this.firstStrongDir(finalText);
|
|
802
|
-
const currentDirection = (this.target as any).direction || 'ltr';
|
|
803
|
-
const hasExplicitDirection =
|
|
804
|
-
currentDirection && currentDirection !== 'inherit';
|
|
805
|
-
|
|
806
|
-
// Only update direction when not explicitly set on the object
|
|
807
|
-
if (!hasExplicitDirection && detectedDirection && detectedDirection !== currentDirection) {
|
|
808
|
-
console.log(`🔄 Overlay Exit: Auto-detected direction change from "${currentDirection}" to "${detectedDirection}"`);
|
|
809
|
-
console.log(` Text content: "${finalText.substring(0, 50)}..."`);
|
|
810
|
-
|
|
811
|
-
// Update the fabric object's direction
|
|
812
|
-
(this.target as any).set('direction', detectedDirection);
|
|
798
|
+
const finalText = this.textarea.value;
|
|
799
|
+
|
|
800
|
+
// Auto-detect text direction and update fabric object if needed
|
|
801
|
+
const detectedDirection = this.firstStrongDir(finalText);
|
|
802
|
+
const currentDirection = (this.target as any).direction || 'ltr';
|
|
803
|
+
const hasExplicitDirection =
|
|
804
|
+
currentDirection && currentDirection !== 'inherit';
|
|
805
|
+
|
|
806
|
+
// Only update direction when not explicitly set on the object
|
|
807
|
+
if (!hasExplicitDirection && detectedDirection && detectedDirection !== currentDirection) {
|
|
808
|
+
// console.log(`🔄 Overlay Exit: Auto-detected direction change from "${currentDirection}" to "${detectedDirection}"`);
|
|
809
|
+
// console.log(` Text content: "${finalText.substring(0, 50)}..."`);
|
|
810
|
+
|
|
811
|
+
// Update the fabric object's direction
|
|
812
|
+
(this.target as any).set('direction', detectedDirection);
|
|
813
813
|
|
|
814
814
|
// Force a re-render to apply the direction change
|
|
815
815
|
this.canvas.requestRenderAll();
|
|
816
816
|
|
|
817
|
-
console.log(`✅ Fabric object direction updated to: ${detectedDirection}`);
|
|
817
|
+
// console.log(`✅ Fabric object direction updated to: ${detectedDirection}`);
|
|
818
818
|
} else {
|
|
819
|
-
console.log(`📝 Overlay Exit: Direction unchanged (${currentDirection}), text: "${finalText.substring(0, 30)}..."`);
|
|
819
|
+
// console.log(`📝 Overlay Exit: Direction unchanged (${currentDirection}), text: "${finalText.substring(0, 30)}..."`);
|
|
820
820
|
}
|
|
821
821
|
|
|
822
822
|
if (this.onCommit) {
|
package/src/text/unicode.ts
CHANGED
|
@@ -445,11 +445,186 @@ export function getLineBreakClass(char: string): string {
|
|
|
445
445
|
export function isLineBreakAllowed(before: string, after: string): boolean {
|
|
446
446
|
const beforeClass = getLineBreakClass(before);
|
|
447
447
|
const afterClass = getLineBreakClass(after);
|
|
448
|
-
|
|
448
|
+
|
|
449
449
|
// Simplified line breaking rules
|
|
450
450
|
if (beforeClass === 'OP') return false; // Don't break after opening
|
|
451
451
|
if (afterClass === 'CL') return false; // Don't break before closing
|
|
452
452
|
if (beforeClass === 'SP') return true; // Always allow break after space
|
|
453
|
-
|
|
453
|
+
|
|
454
454
|
return true; // Default allow
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// ============================================================================
|
|
458
|
+
// Arabic Kashida (Tatweel) Support
|
|
459
|
+
// ============================================================================
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Arabic Tatweel (kashida) character used for justification
|
|
463
|
+
*/
|
|
464
|
+
export const ARABIC_TATWEEL = '\u0640';
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Arabic letters that do NOT connect to the following letter (non-connecting on left).
|
|
468
|
+
* These letters cannot have kashida inserted after them.
|
|
469
|
+
* ا (Alef), د (Dal), ذ (Thal), ر (Ra), ز (Zay), و (Waw), ة (Teh Marbuta), ء (Hamza)
|
|
470
|
+
*/
|
|
471
|
+
const ARABIC_NON_CONNECTING = new Set([
|
|
472
|
+
'\u0627', // Alef
|
|
473
|
+
'\u062F', // Dal
|
|
474
|
+
'\u0630', // Thal
|
|
475
|
+
'\u0631', // Ra
|
|
476
|
+
'\u0632', // Zay
|
|
477
|
+
'\u0648', // Waw
|
|
478
|
+
'\u0629', // Teh Marbuta
|
|
479
|
+
'\u0621', // Hamza
|
|
480
|
+
'\u0622', // Alef with Madda
|
|
481
|
+
'\u0623', // Alef with Hamza Above
|
|
482
|
+
'\u0625', // Alef with Hamza Below
|
|
483
|
+
'\u0672', // Alef with Wavy Hamza Above
|
|
484
|
+
'\u0673', // Alef with Wavy Hamza Below
|
|
485
|
+
'\u0675', // High Hamza Alef
|
|
486
|
+
'\u0688', // Dal with Small Tah
|
|
487
|
+
'\u0689', // Dal with Ring
|
|
488
|
+
'\u068A', // Dal with Dot Below
|
|
489
|
+
'\u068B', // Dal with Dot Below and Small Tah
|
|
490
|
+
'\u068C', // Dahal
|
|
491
|
+
'\u068D', // Ddahal
|
|
492
|
+
'\u068E', // Dul
|
|
493
|
+
'\u068F', // Dal with Three Dots Above Downwards
|
|
494
|
+
'\u0690', // Dal with Four Dots Above
|
|
495
|
+
'\u0691', // Rreh
|
|
496
|
+
'\u0692', // Reh with Small V
|
|
497
|
+
'\u0693', // Reh with Ring
|
|
498
|
+
'\u0694', // Reh with Dot Below
|
|
499
|
+
'\u0695', // Reh with Small V Below
|
|
500
|
+
'\u0696', // Reh with Dot Below and Dot Above
|
|
501
|
+
'\u0697', // Reh with Two Dots Above
|
|
502
|
+
'\u0698', // Jeh
|
|
503
|
+
'\u0699', // Reh with Four Dots Above
|
|
504
|
+
'\u06C4', // Waw with Ring
|
|
505
|
+
'\u06C5', // Kirghiz Oe
|
|
506
|
+
'\u06C6', // Oe
|
|
507
|
+
'\u06C7', // U
|
|
508
|
+
'\u06C8', // Yu
|
|
509
|
+
'\u06C9', // Kirghiz Yu
|
|
510
|
+
'\u06CA', // Waw with Two Dots Above
|
|
511
|
+
'\u06CB', // Ve
|
|
512
|
+
'\u06CD', // Yeh with Tail
|
|
513
|
+
'\u06CF', // Waw with Dot Above
|
|
514
|
+
]);
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Check if a character is an Arabic letter (main Arabic block + extended)
|
|
518
|
+
*/
|
|
519
|
+
export function isArabicLetter(char: string): boolean {
|
|
520
|
+
if (!char) return false;
|
|
521
|
+
const code = char.charCodeAt(0);
|
|
522
|
+
// Arabic: U+0600-U+06FF (main block)
|
|
523
|
+
// Arabic Supplement: U+0750-U+077F
|
|
524
|
+
// Arabic Extended-A: U+08A0-U+08FF
|
|
525
|
+
return (
|
|
526
|
+
(code >= 0x0620 && code <= 0x064A) || // Main letters
|
|
527
|
+
(code >= 0x066E && code <= 0x06D3) || // Extended letters
|
|
528
|
+
(code >= 0x0750 && code <= 0x077F) || // Arabic Supplement
|
|
529
|
+
(code >= 0x08A0 && code <= 0x08FF) // Arabic Extended-A
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Check if kashida can be inserted between two characters.
|
|
535
|
+
* Kashida can only be inserted:
|
|
536
|
+
* - Between two Arabic letters
|
|
537
|
+
* - After a letter that connects to the next (not in ARABIC_NON_CONNECTING)
|
|
538
|
+
* - Not at word boundaries (no whitespace before/after)
|
|
539
|
+
*/
|
|
540
|
+
// Alef variants that form ligatures with lam
|
|
541
|
+
const ARABIC_ALEF_VARIANTS = new Set([
|
|
542
|
+
'\u0627', // ا ALEF
|
|
543
|
+
'\u0623', // أ ALEF WITH HAMZA ABOVE
|
|
544
|
+
'\u0625', // إ ALEF WITH HAMZA BELOW
|
|
545
|
+
'\u0622', // آ ALEF WITH MADDA ABOVE
|
|
546
|
+
'\u0671', // ٱ ALEF WASLA
|
|
547
|
+
]);
|
|
548
|
+
|
|
549
|
+
// Lam character
|
|
550
|
+
const ARABIC_LAM = '\u0644'; // ل
|
|
551
|
+
|
|
552
|
+
export function canInsertKashida(prevChar: string, nextChar: string): boolean {
|
|
553
|
+
if (!prevChar || !nextChar) return false;
|
|
554
|
+
|
|
555
|
+
// Can't insert at whitespace boundaries
|
|
556
|
+
if (/\s/.test(prevChar) || /\s/.test(nextChar)) return false;
|
|
557
|
+
|
|
558
|
+
// Both must be Arabic letters
|
|
559
|
+
if (!isArabicLetter(prevChar) || !isArabicLetter(nextChar)) return false;
|
|
560
|
+
|
|
561
|
+
// Previous char must connect to the next (not be non-connecting)
|
|
562
|
+
if (ARABIC_NON_CONNECTING.has(prevChar)) return false;
|
|
563
|
+
|
|
564
|
+
// NEVER insert kashida between lam and alef - they form a ligature (لا)
|
|
565
|
+
if (prevChar === ARABIC_LAM && ARABIC_ALEF_VARIANTS.has(nextChar)) return false;
|
|
566
|
+
|
|
567
|
+
return true;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Represents a valid kashida insertion point
|
|
572
|
+
*/
|
|
573
|
+
export interface KashidaPoint {
|
|
574
|
+
/** Index in the grapheme array where kashida can be inserted after */
|
|
575
|
+
charIndex: number;
|
|
576
|
+
/** Priority for kashida insertion (higher = insert here first) */
|
|
577
|
+
priority: number;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Find all valid kashida insertion points in a line of text.
|
|
582
|
+
* Returns points sorted by priority (highest first).
|
|
583
|
+
*
|
|
584
|
+
* Priority rules (similar to Adobe Illustrator):
|
|
585
|
+
* 1. Between connected letters (ب + ب = highest)
|
|
586
|
+
* 2. Prefer middle of words over edges
|
|
587
|
+
* 3. Avoid inserting right before/after spaces
|
|
588
|
+
*/
|
|
589
|
+
export function findKashidaPoints(graphemes: string[]): KashidaPoint[] {
|
|
590
|
+
const points: KashidaPoint[] = [];
|
|
591
|
+
|
|
592
|
+
for (let i = 0; i < graphemes.length - 1; i++) {
|
|
593
|
+
const prev = graphemes[i];
|
|
594
|
+
const next = graphemes[i + 1];
|
|
595
|
+
|
|
596
|
+
if (canInsertKashida(prev, next)) {
|
|
597
|
+
// Calculate priority based on position in word
|
|
598
|
+
let priority = 1;
|
|
599
|
+
|
|
600
|
+
// Find word boundaries
|
|
601
|
+
let wordStart = i;
|
|
602
|
+
let wordEnd = i + 1;
|
|
603
|
+
|
|
604
|
+
while (wordStart > 0 && !isWhitespace(graphemes[wordStart - 1])) {
|
|
605
|
+
wordStart--;
|
|
606
|
+
}
|
|
607
|
+
while (wordEnd < graphemes.length && !isWhitespace(graphemes[wordEnd])) {
|
|
608
|
+
wordEnd++;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
const wordLength = wordEnd - wordStart;
|
|
612
|
+
const posInWord = i - wordStart;
|
|
613
|
+
|
|
614
|
+
// Higher priority for middle positions
|
|
615
|
+
const distFromEdge = Math.min(posInWord, wordLength - 1 - posInWord);
|
|
616
|
+
priority = distFromEdge + 1;
|
|
617
|
+
|
|
618
|
+
// Boost priority for longer words
|
|
619
|
+
if (wordLength > 4) priority += 1;
|
|
620
|
+
if (wordLength > 6) priority += 1;
|
|
621
|
+
|
|
622
|
+
points.push({ charIndex: i, priority });
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// Sort by priority descending
|
|
627
|
+
points.sort((a, b) => b.priority - a.priority);
|
|
628
|
+
|
|
629
|
+
return points;
|
|
455
630
|
}
|