@nasser-sw/fabric 7.0.1-beta12 → 7.0.1-beta13

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.
@@ -20,11 +20,11 @@ const textboxDefaultValues = {
20
20
 
21
21
  // @TODO this is not complete
22
22
 
23
- /**
24
- * Textbox class, based on IText, allows the user to resize the text rectangle
25
- * and wraps lines automatically. Textboxes have their Y scaling locked, the
26
- * user can only change width. Height is adjusted automatically based on the
27
- * wrapping of lines.
23
+ /**
24
+ * Textbox class, based on IText, allows the user to resize the text rectangle
25
+ * and wraps lines automatically. Textboxes have their Y scaling locked, the
26
+ * user can only change width. Height is adjusted automatically based on the
27
+ * wrapping of lines.
28
28
  */
29
29
  class Textbox extends IText {
30
30
  static getDefaults() {
@@ -34,10 +34,10 @@ class Textbox extends IText {
34
34
  };
35
35
  }
36
36
 
37
- /**
38
- * Constructor
39
- * @param {String} text Text string
40
- * @param {Object} [options] Options object
37
+ /**
38
+ * Constructor
39
+ * @param {String} text Text string
40
+ * @param {Object} [options] Options object
41
41
  */
42
42
  constructor(text, options) {
43
43
  super(text, {
@@ -47,10 +47,10 @@ class Textbox extends IText {
47
47
  this.initializeEventListeners();
48
48
  }
49
49
 
50
- /**
51
- * Creates the default control object.
52
- * If you prefer to have on instance of controls shared among all objects
53
- * make this function return an empty object and add controls to the ownDefaults object
50
+ /**
51
+ * Creates the default control object.
52
+ * If you prefer to have on instance of controls shared among all objects
53
+ * make this function return an empty object and add controls to the ownDefaults object
54
54
  */
55
55
  static createControls() {
56
56
  return {
@@ -58,11 +58,11 @@ class Textbox extends IText {
58
58
  };
59
59
  }
60
60
 
61
- /**
62
- * Unlike superclass's version of this function, Textbox does not update
63
- * its width.
64
- * @private
65
- * @override
61
+ /**
62
+ * Unlike superclass's version of this function, Textbox does not update
63
+ * its width.
64
+ * @private
65
+ * @override
66
66
  */
67
67
  initDimensions() {
68
68
  if (!this.initialized) {
@@ -130,6 +130,7 @@ class Textbox extends IText {
130
130
  if (this._usingBrowserWrapping) {
131
131
  this._browserWrapInitialized = true;
132
132
  }
133
+ this.calcTextWidth();
133
134
  if (this.textAlign.includes(JUSTIFY)) {
134
135
  // For browser wrapping fonts, apply browser-calculated justify spaces
135
136
  if (this._usingBrowserWrapping) {
@@ -204,11 +205,38 @@ class Textbox extends IText {
204
205
  } else {
205
206
  this.height = this.calcTextHeight();
206
207
  }
208
+
209
+ // Double-check that justify was applied by checking space widths
210
+ if (this.textAlign.includes('justify') && this.__charBounds) {
211
+ setTimeout(() => {
212
+ // Verify justify was applied by checking if space widths vary
213
+ let hasVariableSpaces = false;
214
+ this.__charBounds.forEach((lineBounds, i) => {
215
+ if (lineBounds && this._textLines && this._textLines[i]) {
216
+ const spaces = lineBounds.filter((bound, j) => /\s/.test(this._textLines[i][j]));
217
+ if (spaces.length > 1) {
218
+ const firstSpaceWidth = spaces[0].width;
219
+ hasVariableSpaces = spaces.some(space => Math.abs(space.width - firstSpaceWidth) > 0.1);
220
+ }
221
+ }
222
+ });
223
+ if (!hasVariableSpaces && this.__charBounds.length > 0) {
224
+ console.warn(' ⚠️ Justify spaces still uniform - forcing enlargeSpaces again');
225
+ if (this.enlargeSpaces) {
226
+ var _this$canvas3;
227
+ this.enlargeSpaces();
228
+ (_this$canvas3 = this.canvas) === null || _this$canvas3 === void 0 || _this$canvas3.requestRenderAll();
229
+ }
230
+ } else {
231
+ console.log(' ✅ Justify spaces properly expanded');
232
+ }
233
+ }, 10);
234
+ }
207
235
  }
208
236
 
209
- /**
210
- * Schedule justify calculation after font loads (Textbox-specific)
211
- * @private
237
+ /**
238
+ * Schedule justify calculation after font loads (Textbox-specific)
239
+ * @private
212
240
  */
213
241
  _scheduleJustifyAfterFontLoad() {
214
242
  if (typeof document === 'undefined' || !('fonts' in document)) {
@@ -222,22 +250,22 @@ class Textbox extends IText {
222
250
  this._fontJustifyScheduled = true;
223
251
  const fontSpec = `${this.fontSize}px ${this.fontFamily}`;
224
252
  document.fonts.load(fontSpec).then(() => {
225
- var _this$canvas3;
253
+ var _this$canvas4;
226
254
  this._fontJustifyScheduled = false;
227
255
  console.log('🔧 Textbox: Font loaded, applying justify alignment');
228
256
 
229
257
  // Re-run initDimensions to ensure proper justify calculation
230
258
  this.initDimensions();
231
- (_this$canvas3 = this.canvas) === null || _this$canvas3 === void 0 || _this$canvas3.requestRenderAll();
259
+ (_this$canvas4 = this.canvas) === null || _this$canvas4 === void 0 || _this$canvas4.requestRenderAll();
232
260
  }).catch(() => {
233
261
  this._fontJustifyScheduled = false;
234
262
  console.warn('⚠️ Textbox: Font loading failed, justify may be incorrect');
235
263
  });
236
264
  }
237
265
 
238
- /**
239
- * Advanced dimensions calculation using new layout engine
240
- * @private
266
+ /**
267
+ * Advanced dimensions calculation using new layout engine
268
+ * @private
241
269
  */
242
270
  initDimensionsAdvanced() {
243
271
  if (!this.initialized) {
@@ -292,9 +320,9 @@ class Textbox extends IText {
292
320
  this.dirty = true;
293
321
  }
294
322
 
295
- /**
296
- * Generate style map from new layout format
297
- * @private
323
+ /**
324
+ * Generate style map from new layout format
325
+ * @private
298
326
  */
299
327
  _generateStyleMapFromLayout(layout) {
300
328
  const map = {};
@@ -316,12 +344,12 @@ class Textbox extends IText {
316
344
  return map;
317
345
  }
318
346
 
319
- /**
320
- * Generate an object that translates the style object so that it is
321
- * broken up by visual lines (new lines and automatic wrapping).
322
- * The original text styles object is broken up by actual lines (new lines only),
323
- * which is only sufficient for Text / IText
324
- * @private
347
+ /**
348
+ * Generate an object that translates the style object so that it is
349
+ * broken up by visual lines (new lines and automatic wrapping).
350
+ * The original text styles object is broken up by actual lines (new lines only),
351
+ * which is only sufficient for Text / IText
352
+ * @private
325
353
  */
326
354
  _generateStyleMap(textInfo) {
327
355
  let realLineCount = 0,
@@ -348,10 +376,10 @@ class Textbox extends IText {
348
376
  return map;
349
377
  }
350
378
 
351
- /**
352
- * Returns true if object has a style property or has it on a specified line
353
- * @param {Number} lineIndex
354
- * @return {Boolean}
379
+ /**
380
+ * Returns true if object has a style property or has it on a specified line
381
+ * @param {Number} lineIndex
382
+ * @return {Boolean}
355
383
  */
356
384
  styleHas(property, lineIndex) {
357
385
  if (this._styleMap && !this.isWrapping) {
@@ -363,10 +391,10 @@ class Textbox extends IText {
363
391
  return super.styleHas(property, lineIndex);
364
392
  }
365
393
 
366
- /**
367
- * Returns true if object has no styling or no styling in a line
368
- * @param {Number} lineIndex , lineIndex is on wrapped lines.
369
- * @return {Boolean}
394
+ /**
395
+ * Returns true if object has no styling or no styling in a line
396
+ * @param {Number} lineIndex , lineIndex is on wrapped lines.
397
+ * @return {Boolean}
370
398
  */
371
399
  isEmptyStyles(lineIndex) {
372
400
  if (!this.styles) {
@@ -403,11 +431,11 @@ class Textbox extends IText {
403
431
  return true;
404
432
  }
405
433
 
406
- /**
407
- * @protected
408
- * @param {Number} lineIndex
409
- * @param {Number} charIndex
410
- * @return {TextStyleDeclaration} a style object reference to the existing one or a new empty object when undefined
434
+ /**
435
+ * @protected
436
+ * @param {Number} lineIndex
437
+ * @param {Number} charIndex
438
+ * @return {TextStyleDeclaration} a style object reference to the existing one or a new empty object when undefined
411
439
  */
412
440
  _getStyleDeclaration(lineIndex, charIndex) {
413
441
  if (this._styleMap && !this.isWrapping) {
@@ -421,59 +449,59 @@ class Textbox extends IText {
421
449
  return super._getStyleDeclaration(lineIndex, charIndex);
422
450
  }
423
451
 
424
- /**
425
- * @param {Number} lineIndex
426
- * @param {Number} charIndex
427
- * @param {Object} style
428
- * @private
452
+ /**
453
+ * @param {Number} lineIndex
454
+ * @param {Number} charIndex
455
+ * @param {Object} style
456
+ * @private
429
457
  */
430
458
  _setStyleDeclaration(lineIndex, charIndex, style) {
431
459
  const map = this._styleMap[lineIndex];
432
460
  super._setStyleDeclaration(map.line, map.offset + charIndex, style);
433
461
  }
434
462
 
435
- /**
436
- * @param {Number} lineIndex
437
- * @param {Number} charIndex
438
- * @private
463
+ /**
464
+ * @param {Number} lineIndex
465
+ * @param {Number} charIndex
466
+ * @private
439
467
  */
440
468
  _deleteStyleDeclaration(lineIndex, charIndex) {
441
469
  const map = this._styleMap[lineIndex];
442
470
  super._deleteStyleDeclaration(map.line, map.offset + charIndex);
443
471
  }
444
472
 
445
- /**
446
- * probably broken need a fix
447
- * Returns the real style line that correspond to the wrapped lineIndex line
448
- * Used just to verify if the line does exist or not.
449
- * @param {Number} lineIndex
450
- * @returns {Boolean} if the line exists or not
451
- * @private
473
+ /**
474
+ * probably broken need a fix
475
+ * Returns the real style line that correspond to the wrapped lineIndex line
476
+ * Used just to verify if the line does exist or not.
477
+ * @param {Number} lineIndex
478
+ * @returns {Boolean} if the line exists or not
479
+ * @private
452
480
  */
453
481
  _getLineStyle(lineIndex) {
454
482
  const map = this._styleMap[lineIndex];
455
483
  return !!this.styles[map.line];
456
484
  }
457
485
 
458
- /**
459
- * Set the line style to an empty object so that is initialized
460
- * @param {Number} lineIndex
461
- * @param {Object} style
462
- * @private
486
+ /**
487
+ * Set the line style to an empty object so that is initialized
488
+ * @param {Number} lineIndex
489
+ * @param {Object} style
490
+ * @private
463
491
  */
464
492
  _setLineStyle(lineIndex) {
465
493
  const map = this._styleMap[lineIndex];
466
494
  super._setLineStyle(map.line);
467
495
  }
468
496
 
469
- /**
470
- * Wraps text using the 'width' property of Textbox. First this function
471
- * splits text on newlines, so we preserve newlines entered by the user.
472
- * Then it wraps each line using the width of the Textbox by calling
473
- * _wrapLine().
474
- * @param {Array} lines The string array of text that is split into lines
475
- * @param {Number} desiredWidth width you want to wrap to
476
- * @returns {Array} Array of lines
497
+ /**
498
+ * Wraps text using the 'width' property of Textbox. First this function
499
+ * splits text on newlines, so we preserve newlines entered by the user.
500
+ * Then it wraps each line using the width of the Textbox by calling
501
+ * _wrapLine().
502
+ * @param {Array} lines The string array of text that is split into lines
503
+ * @param {Number} desiredWidth width you want to wrap to
504
+ * @returns {Array} Array of lines
477
505
  */
478
506
  _wrapText(lines, desiredWidth) {
479
507
  this.isWrapping = true;
@@ -487,12 +515,12 @@ class Textbox extends IText {
487
515
  return wrapped;
488
516
  }
489
517
 
490
- /**
491
- * For each line of text terminated by an hard line stop,
492
- * measure each word width and extract the largest word from all.
493
- * The returned words here are the one that at the end will be rendered.
494
- * @param {string[]} lines the lines we need to measure
495
- *
518
+ /**
519
+ * For each line of text terminated by an hard line stop,
520
+ * measure each word width and extract the largest word from all.
521
+ * The returned words here are the one that at the end will be rendered.
522
+ * @param {string[]} lines the lines we need to measure
523
+ *
496
524
  */
497
525
  getGraphemeDataForRender(lines) {
498
526
  const splitByGrapheme = this.splitByGrapheme,
@@ -525,17 +553,17 @@ class Textbox extends IText {
525
553
  };
526
554
  }
527
555
 
528
- /**
529
- * Helper function to measure a string of text, given its lineIndex and charIndex offset
530
- * It gets called when charBounds are not available yet.
531
- * Override if necessary
532
- * Use with {@link Textbox#wordSplit}
533
- *
534
- * @param {CanvasRenderingContext2D} ctx
535
- * @param {String} text
536
- * @param {number} lineIndex
537
- * @param {number} charOffset
538
- * @returns {number}
556
+ /**
557
+ * Helper function to measure a string of text, given its lineIndex and charIndex offset
558
+ * It gets called when charBounds are not available yet.
559
+ * Override if necessary
560
+ * Use with {@link Textbox#wordSplit}
561
+ *
562
+ * @param {CanvasRenderingContext2D} ctx
563
+ * @param {String} text
564
+ * @param {number} lineIndex
565
+ * @param {number} charOffset
566
+ * @returns {number}
539
567
  */
540
568
  _measureWord(word, lineIndex) {
541
569
  let charOffset = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
@@ -550,26 +578,26 @@ class Textbox extends IText {
550
578
  return width;
551
579
  }
552
580
 
553
- /**
554
- * Override this method to customize word splitting
555
- * Use with {@link Textbox#_measureWord}
556
- * @param {string} value
557
- * @returns {string[]} array of words
581
+ /**
582
+ * Override this method to customize word splitting
583
+ * Use with {@link Textbox#_measureWord}
584
+ * @param {string} value
585
+ * @returns {string[]} array of words
558
586
  */
559
587
  wordSplit(value) {
560
588
  return value.split(this._wordJoiners);
561
589
  }
562
590
 
563
- /**
564
- * Wraps a line of text using the width of the Textbox as desiredWidth
565
- * and leveraging the known width o words from GraphemeData
566
- * @private
567
- * @param {Number} lineIndex
568
- * @param {Number} desiredWidth width you want to wrap the line to
569
- * @param {GraphemeData} graphemeData an object containing all the lines' words width.
570
- * @param {Number} reservedSpace space to remove from wrapping for custom functionalities
571
- * @returns {Array} Array of line(s) into which the given text is wrapped
572
- * to.
591
+ /**
592
+ * Wraps a line of text using the width of the Textbox as desiredWidth
593
+ * and leveraging the known width o words from GraphemeData
594
+ * @private
595
+ * @param {Number} lineIndex
596
+ * @param {Number} desiredWidth width you want to wrap the line to
597
+ * @param {GraphemeData} graphemeData an object containing all the lines' words width.
598
+ * @param {Number} reservedSpace space to remove from wrapping for custom functionalities
599
+ * @returns {Array} Array of line(s) into which the given text is wrapped
600
+ * to.
573
601
  */
574
602
  _wrapLine(lineIndex, desiredWidth, _ref) {
575
603
  let {
@@ -651,11 +679,11 @@ class Textbox extends IText {
651
679
  return graphemeLines;
652
680
  }
653
681
 
654
- /**
655
- * Detect if the text line is ended with an hard break
656
- * text and itext do not have wrapping, return false
657
- * @param {Number} lineIndex text to split
658
- * @return {Boolean}
682
+ /**
683
+ * Detect if the text line is ended with an hard break
684
+ * text and itext do not have wrapping, return false
685
+ * @param {Number} lineIndex text to split
686
+ * @return {Boolean}
659
687
  */
660
688
  isEndOfWrapping(lineIndex) {
661
689
  if (!this._styleMap[lineIndex + 1]) {
@@ -669,12 +697,12 @@ class Textbox extends IText {
669
697
  return false;
670
698
  }
671
699
 
672
- /**
673
- * Detect if a line has a linebreak and so we need to account for it when moving
674
- * and counting style.
675
- * This is important only for splitByGrapheme at the end of wrapping.
676
- * If we are not wrapping the offset is always 1
677
- * @return Number
700
+ /**
701
+ * Detect if a line has a linebreak and so we need to account for it when moving
702
+ * and counting style.
703
+ * This is important only for splitByGrapheme at the end of wrapping.
704
+ * If we are not wrapping the offset is always 1
705
+ * @return Number
678
706
  */
679
707
  missingNewlineOffset(lineIndex, skipWrapping) {
680
708
  if (this.splitByGrapheme && !skipWrapping) {
@@ -683,12 +711,12 @@ class Textbox extends IText {
683
711
  return 1;
684
712
  }
685
713
 
686
- /**
687
- * Gets lines of text to render in the Textbox. This function calculates
688
- * text wrapping on the fly every time it is called.
689
- * @param {String} text text to split
690
- * @returns {Array} Array of lines in the Textbox.
691
- * @override
714
+ /**
715
+ * Gets lines of text to render in the Textbox. This function calculates
716
+ * text wrapping on the fly every time it is called.
717
+ * @param {String} text text to split
718
+ * @returns {Array} Array of lines in the Textbox.
719
+ * @override
692
720
  */
693
721
  _splitTextIntoLines(text) {
694
722
  // Check if we need browser wrapping using smart font detection
@@ -735,9 +763,9 @@ class Textbox extends IText {
735
763
  return newText;
736
764
  }
737
765
 
738
- /**
739
- * Use browser's native text wrapping for accurate handling of fonts without English glyphs
740
- * @private
766
+ /**
767
+ * Use browser's native text wrapping for accurate handling of fonts without English glyphs
768
+ * @private
741
769
  */
742
770
  _splitTextIntoLinesWithBrowser(text) {
743
771
  if (typeof document === 'undefined') {
@@ -863,9 +891,9 @@ class Textbox extends IText {
863
891
  };
864
892
  }
865
893
 
866
- /**
867
- * Extract justify space measurements from browser
868
- * @private
894
+ /**
895
+ * Extract justify space measurements from browser
896
+ * @private
869
897
  */
870
898
  _extractJustifySpaceMeasurements(element, lines) {
871
899
  console.log(`🔤 Extracting browser justify space measurements for ${lines.length} lines`);
@@ -901,9 +929,9 @@ class Textbox extends IText {
901
929
  return spaceWidths;
902
930
  }
903
931
 
904
- /**
905
- * Apply browser-calculated justify space measurements
906
- * @private
932
+ /**
933
+ * Apply browser-calculated justify space measurements
934
+ * @private
907
935
  */
908
936
  _applyBrowserJustifySpaces() {
909
937
  if (!this._textLines || !this.__charBounds) {
@@ -926,23 +954,59 @@ class Textbox extends IText {
926
954
  const lineBounds = this.__charBounds[lineIndex];
927
955
  const lineSpaceWidths = spaceWidths[lineIndex];
928
956
  let spaceIndex = 0;
957
+ let accumulatedSpace = 0;
958
+ const isRtl = this.direction === 'rtl';
959
+ const spaceDiffs = [];
960
+
961
+ // First, calculate the difference for each space
929
962
  for (let charIndex = 0; charIndex < line.length; charIndex++) {
930
963
  if (/\s/.test(line[charIndex]) && spaceIndex < lineSpaceWidths.length) {
931
- const expandedWidth = lineSpaceWidths[spaceIndex];
932
- if (lineBounds[charIndex]) {
933
- const oldWidth = lineBounds[charIndex].width;
934
- lineBounds[charIndex].width = expandedWidth;
935
- console.log(`🔤 Line ${lineIndex} space ${spaceIndex}: ${oldWidth.toFixed(1)}px -> ${expandedWidth.toFixed(1)}px`);
964
+ const charBound = lineBounds[charIndex];
965
+ if (charBound) {
966
+ spaceDiffs.push(lineSpaceWidths[spaceIndex] - charBound.width);
967
+ }
968
+ spaceIndex++;
969
+ }
970
+ }
971
+ spaceIndex = 0;
972
+ let remainingDiff = spaceDiffs.reduce((a, b) => a + b, 0);
973
+ for (let charIndex = 0; charIndex < line.length; charIndex++) {
974
+ const charBound = lineBounds[charIndex];
975
+ if (!charBound) continue;
976
+ if (isRtl) {
977
+ charBound.left += remainingDiff;
978
+ } else {
979
+ charBound.left += accumulatedSpace;
980
+ }
981
+ if (/\s/.test(line[charIndex]) && spaceIndex < spaceDiffs.length) {
982
+ const diff = spaceDiffs[spaceIndex];
983
+ const oldWidth = charBound.width;
984
+ charBound.width += diff;
985
+ charBound.kernedWidth += diff;
986
+ console.log(`🔤 Line ${lineIndex} space ${spaceIndex}: ${oldWidth.toFixed(1)}px -> ${charBound.width.toFixed(1)}px`);
987
+ if (isRtl) {
988
+ remainingDiff -= diff;
989
+ } else {
990
+ accumulatedSpace += diff;
936
991
  }
937
992
  spaceIndex++;
938
993
  }
939
994
  }
995
+ // also need to update the last charBound
996
+ const lastBound = lineBounds[line.length];
997
+ if (lastBound) {
998
+ if (isRtl) {
999
+ lastBound.left += remainingDiff;
1000
+ } else {
1001
+ lastBound.left += accumulatedSpace;
1002
+ }
1003
+ }
940
1004
  });
941
1005
  }
942
1006
 
943
- /**
944
- * Fallback to default Fabric wrapping
945
- * @private
1007
+ /**
1008
+ * Fallback to default Fabric wrapping
1009
+ * @private
946
1010
  */
947
1011
  _splitTextIntoLinesDefault(text) {
948
1012
  const newText = super._splitTextIntoLines(text),
@@ -974,12 +1038,12 @@ class Textbox extends IText {
974
1038
  }
975
1039
  }
976
1040
 
977
- /**
978
- * Initialize event listeners for safety snap functionality
979
- * @private
1041
+ /**
1042
+ * Initialize event listeners for safety snap functionality
1043
+ * @private
980
1044
  */
981
1045
  initializeEventListeners() {
982
- var _this$canvas4;
1046
+ var _this$canvas5;
983
1047
  // Track which side is being used for resize to handle position compensation
984
1048
  let resizeOrigin = null;
985
1049
 
@@ -1010,7 +1074,7 @@ class Textbox extends IText {
1010
1074
  });
1011
1075
 
1012
1076
  // Also listen to canvas-level modified event as backup
1013
- (_this$canvas4 = this.canvas) === null || _this$canvas4 === void 0 || _this$canvas4.on('object:modified', e => {
1077
+ (_this$canvas5 = this.canvas) === null || _this$canvas5 === void 0 || _this$canvas5.on('object:modified', e => {
1014
1078
  if (e.target === this) {
1015
1079
  const currentResizeOrigin = resizeOrigin; // Capture the value before reset
1016
1080
  setTimeout(() => this.safetySnapWidth(currentResizeOrigin), 10);
@@ -1019,12 +1083,12 @@ class Textbox extends IText {
1019
1083
  });
1020
1084
  }
1021
1085
 
1022
- /**
1023
- * Safety snap to prevent glyph clipping after manual resize.
1024
- * Similar to Polotno - checks if any glyphs are too close to edges
1025
- * and automatically expands width if needed.
1026
- * @private
1027
- * @param resizeOrigin - Which side was used for resizing ('left' or 'right')
1086
+ /**
1087
+ * Safety snap to prevent glyph clipping after manual resize.
1088
+ * Similar to Polotno - checks if any glyphs are too close to edges
1089
+ * and automatically expands width if needed.
1090
+ * @private
1091
+ * @param resizeOrigin - Which side was used for resizing ('left' or 'right')
1028
1092
  */
1029
1093
  safetySnapWidth(resizeOrigin) {
1030
1094
  // For Textbox objects, we always want to check for clipping regardless of isWrapping flag
@@ -1054,7 +1118,7 @@ class Textbox extends IText {
1054
1118
  const safetyThreshold = 2; // px - very subtle trigger
1055
1119
 
1056
1120
  if (maxRequiredWidth > this.width - safetyThreshold) {
1057
- var _this$canvas5;
1121
+ var _this$canvas6;
1058
1122
  // Set width to exactly what's needed + minimal safety margin
1059
1123
  const newWidth = maxRequiredWidth + 1; // Add just 1px safety margin
1060
1124
 
@@ -1087,13 +1151,13 @@ class Textbox extends IText {
1087
1151
  this.__overlayEditor.refresh();
1088
1152
  }, 0);
1089
1153
  }
1090
- (_this$canvas5 = this.canvas) === null || _this$canvas5 === void 0 || _this$canvas5.requestRenderAll();
1154
+ (_this$canvas6 = this.canvas) === null || _this$canvas6 === void 0 || _this$canvas6.requestRenderAll();
1091
1155
  }
1092
1156
  }
1093
1157
 
1094
- /**
1095
- * Fix character selection mismatch after JSON loading for browser-wrapped fonts
1096
- * @private
1158
+ /**
1159
+ * Fix character selection mismatch after JSON loading for browser-wrapped fonts
1160
+ * @private
1097
1161
  */
1098
1162
  _fixCharacterMappingAfterJsonLoad() {
1099
1163
  if (this._usingBrowserWrapping) {
@@ -1113,9 +1177,9 @@ class Textbox extends IText {
1113
1177
  }
1114
1178
  }
1115
1179
 
1116
- /**
1117
- * Force complete textbox re-initialization (useful after JSON loading)
1118
- * Overrides Text version with Textbox-specific logic
1180
+ /**
1181
+ * Force complete textbox re-initialization (useful after JSON loading)
1182
+ * Overrides Text version with Textbox-specific logic
1119
1183
  */
1120
1184
  forceTextReinitialization() {
1121
1185
  console.log('🔄 Force reinitializing Textbox object');
@@ -1138,7 +1202,7 @@ class Textbox extends IText {
1138
1202
  // Double-check that justify was applied by checking space widths
1139
1203
  if (this.textAlign.includes('justify') && this.__charBounds) {
1140
1204
  setTimeout(() => {
1141
- var _this$canvas6;
1205
+ var _this$canvas7;
1142
1206
  // Verify justify was applied by checking if space widths vary
1143
1207
  let hasVariableSpaces = false;
1144
1208
  this.__charBounds.forEach((lineBounds, i) => {
@@ -1167,36 +1231,36 @@ class Textbox extends IText {
1167
1231
  this.height = this.calcTextHeight();
1168
1232
  console.log(`🔧 JUSTIFY: Used calcTextHeight: ${this.height}px`);
1169
1233
  }
1170
- (_this$canvas6 = this.canvas) === null || _this$canvas6 === void 0 || _this$canvas6.requestRenderAll();
1234
+ (_this$canvas7 = this.canvas) === null || _this$canvas7 === void 0 || _this$canvas7.requestRenderAll();
1171
1235
  }, 10);
1172
1236
  }
1173
1237
  }
1174
1238
 
1175
- /**
1176
- * Returns object representation of an instance
1177
- * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
1178
- * @return {Object} object representation of an instance
1239
+ /**
1240
+ * Returns object representation of an instance
1241
+ * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
1242
+ * @return {Object} object representation of an instance
1179
1243
  */
1180
1244
  toObject() {
1181
1245
  let propertiesToInclude = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
1182
1246
  return super.toObject(['minWidth', 'splitByGrapheme', ...propertiesToInclude]);
1183
1247
  }
1184
1248
  }
1185
- /**
1186
- * Minimum width of textbox, in pixels.
1187
- * @type Number
1249
+ /**
1250
+ * Minimum width of textbox, in pixels.
1251
+ * @type Number
1188
1252
  */
1189
- /**
1190
- * Minimum calculated width of a textbox, in pixels.
1191
- * fixed to 2 so that an empty textbox cannot go to 0
1192
- * and is still selectable without text.
1193
- * @type Number
1253
+ /**
1254
+ * Minimum calculated width of a textbox, in pixels.
1255
+ * fixed to 2 so that an empty textbox cannot go to 0
1256
+ * and is still selectable without text.
1257
+ * @type Number
1194
1258
  */
1195
- /**
1196
- * Use this boolean property in order to split strings that have no white space concept.
1197
- * this is a cheap way to help with chinese/japanese
1198
- * @type Boolean
1199
- * @since 2.6.0
1259
+ /**
1260
+ * Use this boolean property in order to split strings that have no white space concept.
1261
+ * this is a cheap way to help with chinese/japanese
1262
+ * @type Boolean
1263
+ * @since 2.6.0
1200
1264
  */
1201
1265
  _defineProperty(Textbox, "type", 'Textbox');
1202
1266
  _defineProperty(Textbox, "textLayoutProperties", [...IText.textLayoutProperties, 'width']);