@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.
package/dist/index.js CHANGED
@@ -360,7 +360,7 @@
360
360
  }
361
361
  const cache = new Cache();
362
362
 
363
- var version = "7.0.1-beta11";
363
+ var version = "7.0.1-beta12";
364
364
 
365
365
  // use this syntax so babel plugin see this import here
366
366
  const VERSION = version;
@@ -20722,9 +20722,9 @@
20722
20722
 
20723
20723
  let measuringContext;
20724
20724
 
20725
- /**
20726
- * Return a context for measurement of text string.
20727
- * if created it gets stored for reuse
20725
+ /**
20726
+ * Return a context for measurement of text string.
20727
+ * if created it gets stored for reuse
20728
20728
  */
20729
20729
  function getMeasuringContext() {
20730
20730
  if (!measuringContext) {
@@ -20737,17 +20737,17 @@
20737
20737
  return measuringContext;
20738
20738
  }
20739
20739
 
20740
- /**
20741
- * Measure and return the info of a single grapheme.
20742
- * needs the the info of previous graphemes already filled
20743
- * Override to customize measuring
20740
+ /**
20741
+ * Measure and return the info of a single grapheme.
20742
+ * needs the the info of previous graphemes already filled
20743
+ * Override to customize measuring
20744
20744
  */
20745
20745
 
20746
20746
  // @TODO this is not complete
20747
20747
 
20748
- /**
20749
- * Text class
20750
- * @see {@link http://fabricjs.com/fabric-intro-part-2#text}
20748
+ /**
20749
+ * Text class
20750
+ * @see {@link http://fabricjs.com/fabric-intro-part-2#text}
20751
20751
  */
20752
20752
  class FabricText extends StyledText {
20753
20753
  static getDefaults() {
@@ -20758,11 +20758,11 @@
20758
20758
  }
20759
20759
  constructor(text, options) {
20760
20760
  super();
20761
- /**
20762
- * contains characters bounding boxes
20763
- * This variable is considered to be protected.
20764
- * But for how mixins are implemented right now, we can't leave it private
20765
- * @protected
20761
+ /**
20762
+ * contains characters bounding boxes
20763
+ * This variable is considered to be protected.
20764
+ * But for how mixins are implemented right now, we can't leave it private
20765
+ * @protected
20766
20766
  */
20767
20767
  _defineProperty(this, "__charBounds", []);
20768
20768
  Object.assign(this, FabricText.ownDefaults);
@@ -20779,9 +20779,9 @@
20779
20779
  this.setCoords();
20780
20780
  }
20781
20781
 
20782
- /**
20783
- * If text has a path, it will add the extra information needed
20784
- * for path and text calculations
20782
+ /**
20783
+ * If text has a path, it will add the extra information needed
20784
+ * for path and text calculations
20785
20785
  */
20786
20786
  setPathInfo() {
20787
20787
  const path = this.path;
@@ -20790,10 +20790,10 @@
20790
20790
  }
20791
20791
  }
20792
20792
 
20793
- /**
20794
- * @private
20795
- * Divides text into lines of text and lines of graphemes.
20796
- * Uses browser lines when available for pixel-perfect consistency.
20793
+ /**
20794
+ * @private
20795
+ * Divides text into lines of text and lines of graphemes.
20796
+ * Uses browser lines when available for pixel-perfect consistency.
20797
20797
  */
20798
20798
  _splitText() {
20799
20799
  // Check if we have valid browser lines and should use them
@@ -20809,9 +20809,9 @@
20809
20809
  return newLines;
20810
20810
  }
20811
20811
 
20812
- /**
20813
- * Create TextLinesInfo from browser-extracted lines
20814
- * @private
20812
+ /**
20813
+ * Create TextLinesInfo from browser-extracted lines
20814
+ * @private
20815
20815
  */
20816
20816
  _splitTextFromBrowserLines(browserLines) {
20817
20817
  const lines = [];
@@ -20845,10 +20845,10 @@
20845
20845
  return result;
20846
20846
  }
20847
20847
 
20848
- /**
20849
- * Initialize or update text dimensions.
20850
- * Updates this.width and this.height with the proper values.
20851
- * Does not return dimensions.
20848
+ /**
20849
+ * Initialize or update text dimensions.
20850
+ * Updates this.width and this.height with the proper values.
20851
+ * Does not return dimensions.
20852
20852
  */
20853
20853
  initDimensions() {
20854
20854
  // Check if font is ready for accurate measurements
@@ -20894,14 +20894,14 @@
20894
20894
  }
20895
20895
  }
20896
20896
 
20897
- /**
20898
- * Enlarge space boxes and shift the others
20897
+ /**
20898
+ * Enlarge space boxes and shift the others
20899
20899
  */
20900
20900
  enlargeSpaces() {
20901
20901
  let diffSpace, currentLineWidth, numberOfSpaces, accumulatedSpace, line, charBound, spaces;
20902
20902
  const isRtl = this.direction === 'rtl';
20903
20903
  for (let i = 0, len = this._textLines.length; i < len; i++) {
20904
- if (!this.textAlign.includes('justify') && (i === len - 1 || this.isEndOfWrapping(i))) {
20904
+ if (!this.textAlign.includes('justify') || i === len - 1 || this.isEndOfWrapping(i)) {
20905
20905
  continue;
20906
20906
  }
20907
20907
  accumulatedSpace = 0;
@@ -20957,18 +20957,18 @@
20957
20957
  }
20958
20958
  }
20959
20959
 
20960
- /**
20961
- * Advanced layout using new text engine (Konva-compatible)
20962
- * @private
20960
+ /**
20961
+ * Advanced layout using new text engine (Konva-compatible)
20962
+ * @private
20963
20963
  */
20964
20964
  _layoutTextAdvanced() {
20965
20965
  const options = this._getAdvancedLayoutOptions();
20966
20966
  return layoutText(options);
20967
20967
  }
20968
20968
 
20969
- /**
20970
- * Get advanced layout options from current text properties
20971
- * @private
20969
+ /**
20970
+ * Get advanced layout options from current text properties
20971
+ * @private
20972
20972
  */
20973
20973
  _getAdvancedLayoutOptions() {
20974
20974
  return {
@@ -20990,9 +20990,9 @@
20990
20990
  };
20991
20991
  }
20992
20992
 
20993
- /**
20994
- * Map Fabric textAlign to Konva align format
20995
- * @private
20993
+ /**
20994
+ * Map Fabric textAlign to Konva align format
20995
+ * @private
20996
20996
  */
20997
20997
  _mapTextAlignToAlign(textAlign) {
20998
20998
  switch (textAlign) {
@@ -21013,8 +21013,8 @@
21013
21013
  }
21014
21014
  }
21015
21015
 
21016
- /**
21017
- * Enhanced initDimensions that uses advanced layout when enabled
21016
+ /**
21017
+ * Enhanced initDimensions that uses advanced layout when enabled
21018
21018
  */
21019
21019
  initDimensionsAdvanced() {
21020
21020
  if (!this.enableAdvancedLayout) {
@@ -21043,9 +21043,9 @@
21043
21043
  this.dirty = true;
21044
21044
  }
21045
21045
 
21046
- /**
21047
- * Convert new layout format to legacy _textLines and __charBounds format
21048
- * @private
21046
+ /**
21047
+ * Convert new layout format to legacy _textLines and __charBounds format
21048
+ * @private
21049
21049
  */
21050
21050
  _convertLayoutToLegacyFormat(layout) {
21051
21051
  this._textLines = layout.lines.map(line => line.graphemes);
@@ -21067,30 +21067,30 @@
21067
21067
  }
21068
21068
  }
21069
21069
 
21070
- /**
21071
- * Detect if the text line is ended with an hard break
21072
- * text and itext do not have wrapping, return false
21073
- * @return {Boolean}
21070
+ /**
21071
+ * Detect if the text line is ended with an hard break
21072
+ * text and itext do not have wrapping, return false
21073
+ * @return {Boolean}
21074
21074
  */
21075
21075
  isEndOfWrapping(lineIndex) {
21076
21076
  return lineIndex === this._textLines.length - 1;
21077
21077
  }
21078
21078
 
21079
- /**
21080
- * Detect if a line has a linebreak and so we need to account for it when moving
21081
- * and counting style.
21082
- * It return always 1 for text and Itext. Textbox has its own implementation
21083
- * @return Number
21079
+ /**
21080
+ * Detect if a line has a linebreak and so we need to account for it when moving
21081
+ * and counting style.
21082
+ * It return always 1 for text and Itext. Textbox has its own implementation
21083
+ * @return Number
21084
21084
  */
21085
21085
 
21086
21086
  missingNewlineOffset(_lineIndex) {
21087
21087
  return 1;
21088
21088
  }
21089
21089
 
21090
- /**
21091
- * Returns 2d representation (lineIndex and charIndex) of cursor
21092
- * @param {Number} selectionStart
21093
- * @param {Boolean} [skipWrapping] consider the location for unwrapped lines. useful to manage styles.
21090
+ /**
21091
+ * Returns 2d representation (lineIndex and charIndex) of cursor
21092
+ * @param {Number} selectionStart
21093
+ * @param {Boolean} [skipWrapping] consider the location for unwrapped lines. useful to manage styles.
21094
21094
  */
21095
21095
  get2DCursorLocation(selectionStart, skipWrapping) {
21096
21096
  const lines = skipWrapping ? this._unwrappedTextLines : this._textLines;
@@ -21110,24 +21110,24 @@
21110
21110
  };
21111
21111
  }
21112
21112
 
21113
- /**
21114
- * Returns string representation of an instance
21115
- * @return {String} String representation of text object
21113
+ /**
21114
+ * Returns string representation of an instance
21115
+ * @return {String} String representation of text object
21116
21116
  */
21117
21117
  toString() {
21118
21118
  return `#<Text (${this.complexity()}): { "text": "${this.text}", "fontFamily": "${this.fontFamily}" }>`;
21119
21119
  }
21120
21120
 
21121
- /**
21122
- * Return the dimension and the zoom level needed to create a cache canvas
21123
- * big enough to host the object to be cached.
21124
- * @private
21125
- * @param {Object} dim.x width of object to be cached
21126
- * @param {Object} dim.y height of object to be cached
21127
- * @return {Object}.width width of canvas
21128
- * @return {Object}.height height of canvas
21129
- * @return {Object}.zoomX zoomX zoom value to unscale the canvas before drawing cache
21130
- * @return {Object}.zoomY zoomY zoom value to unscale the canvas before drawing cache
21121
+ /**
21122
+ * Return the dimension and the zoom level needed to create a cache canvas
21123
+ * big enough to host the object to be cached.
21124
+ * @private
21125
+ * @param {Object} dim.x width of object to be cached
21126
+ * @param {Object} dim.y height of object to be cached
21127
+ * @return {Object}.width width of canvas
21128
+ * @return {Object}.height height of canvas
21129
+ * @return {Object}.zoomX zoomX zoom value to unscale the canvas before drawing cache
21130
+ * @return {Object}.zoomY zoomY zoom value to unscale the canvas before drawing cache
21131
21131
  */
21132
21132
  _getCacheCanvasDimensions() {
21133
21133
  const dims = super._getCacheCanvasDimensions();
@@ -21137,9 +21137,9 @@
21137
21137
  return dims;
21138
21138
  }
21139
21139
 
21140
- /**
21141
- * @private
21142
- * @param {CanvasRenderingContext2D} ctx Context to render on
21140
+ /**
21141
+ * @private
21142
+ * @param {CanvasRenderingContext2D} ctx Context to render on
21143
21143
  */
21144
21144
  _render(ctx) {
21145
21145
  const path = this.path;
@@ -21152,9 +21152,9 @@
21152
21152
  this._renderTextDecoration(ctx, 'linethrough');
21153
21153
  }
21154
21154
 
21155
- /**
21156
- * @private
21157
- * @param {CanvasRenderingContext2D} ctx Context to render on
21155
+ /**
21156
+ * @private
21157
+ * @param {CanvasRenderingContext2D} ctx Context to render on
21158
21158
  */
21159
21159
  _renderText(ctx) {
21160
21160
  // Skip text rendering if in overlay editing mode
@@ -21170,15 +21170,15 @@
21170
21170
  }
21171
21171
  }
21172
21172
 
21173
- /**
21174
- * Set the font parameter of the context with the object properties or with charStyle
21175
- * @private
21176
- * @param {CanvasRenderingContext2D} ctx Context to render on
21177
- * @param {Object} [charStyle] object with font style properties
21178
- * @param {String} [charStyle.fontFamily] Font Family
21179
- * @param {Number} [charStyle.fontSize] Font size in pixels. ( without px suffix )
21180
- * @param {String} [charStyle.fontWeight] Font weight
21181
- * @param {String} [charStyle.fontStyle] Font style (italic|normal)
21173
+ /**
21174
+ * Set the font parameter of the context with the object properties or with charStyle
21175
+ * @private
21176
+ * @param {CanvasRenderingContext2D} ctx Context to render on
21177
+ * @param {Object} [charStyle] object with font style properties
21178
+ * @param {String} [charStyle.fontFamily] Font Family
21179
+ * @param {Number} [charStyle.fontSize] Font size in pixels. ( without px suffix )
21180
+ * @param {String} [charStyle.fontWeight] Font weight
21181
+ * @param {String} [charStyle.fontStyle] Font style (italic|normal)
21182
21182
  */
21183
21183
  _setTextStyles(ctx, charStyle, forMeasuring) {
21184
21184
  ctx.textBaseline = 'alphabetic';
@@ -21198,11 +21198,11 @@
21198
21198
  ctx.font = this._getFontDeclaration(charStyle, forMeasuring);
21199
21199
  }
21200
21200
 
21201
- /**
21202
- * calculate and return the text Width measuring each line.
21203
- * @private
21204
- * @param {CanvasRenderingContext2D} ctx Context to render on
21205
- * @return {Number} Maximum width of Text object
21201
+ /**
21202
+ * calculate and return the text Width measuring each line.
21203
+ * @private
21204
+ * @param {CanvasRenderingContext2D} ctx Context to render on
21205
+ * @return {Number} Maximum width of Text object
21206
21206
  */
21207
21207
  calcTextWidth() {
21208
21208
  let maxWidth = this.getLineWidth(0);
@@ -21215,23 +21215,23 @@
21215
21215
  return maxWidth;
21216
21216
  }
21217
21217
 
21218
- /**
21219
- * @private
21220
- * @param {String} method Method name ("fillText" or "strokeText")
21221
- * @param {CanvasRenderingContext2D} ctx Context to render on
21222
- * @param {String} line Text to render
21223
- * @param {Number} left Left position of text
21224
- * @param {Number} top Top position of text
21225
- * @param {Number} lineIndex Index of a line in a text
21218
+ /**
21219
+ * @private
21220
+ * @param {String} method Method name ("fillText" or "strokeText")
21221
+ * @param {CanvasRenderingContext2D} ctx Context to render on
21222
+ * @param {String} line Text to render
21223
+ * @param {Number} left Left position of text
21224
+ * @param {Number} top Top position of text
21225
+ * @param {Number} lineIndex Index of a line in a text
21226
21226
  */
21227
21227
  _renderTextLine(method, ctx, line, left, top, lineIndex) {
21228
21228
  this._renderChars(method, ctx, line, left, top, lineIndex);
21229
21229
  }
21230
21230
 
21231
- /**
21232
- * Renders the text background for lines, taking care of style
21233
- * @private
21234
- * @param {CanvasRenderingContext2D} ctx Context to render on
21231
+ /**
21232
+ * Renders the text background for lines, taking care of style
21233
+ * @private
21234
+ * @param {CanvasRenderingContext2D} ctx Context to render on
21235
21235
  */
21236
21236
  _renderTextLinesBackground(ctx) {
21237
21237
  if (!this.textBackgroundColor && !this.styleHas('textBackgroundColor')) {
@@ -21294,15 +21294,15 @@
21294
21294
  this._removeShadow(ctx);
21295
21295
  }
21296
21296
 
21297
- /**
21298
- * measure and return the width of a single character.
21299
- * possibly overridden to accommodate different measure logic or
21300
- * to hook some external lib for character measurement
21301
- * @private
21302
- * @param {String} _char, char to be measured
21303
- * @param {Object} charStyle style of char to be measured
21304
- * @param {String} [previousChar] previous char
21305
- * @param {Object} [prevCharStyle] style of previous char
21297
+ /**
21298
+ * measure and return the width of a single character.
21299
+ * possibly overridden to accommodate different measure logic or
21300
+ * to hook some external lib for character measurement
21301
+ * @private
21302
+ * @param {String} _char, char to be measured
21303
+ * @param {Object} charStyle style of char to be measured
21304
+ * @param {String} [previousChar] previous char
21305
+ * @param {Object} [prevCharStyle] style of previous char
21306
21306
  */
21307
21307
  _measureChar(_char, charStyle, previousChar, prevCharStyle) {
21308
21308
  const fontCache = cache.getFontCache(charStyle),
@@ -21347,19 +21347,19 @@
21347
21347
  };
21348
21348
  }
21349
21349
 
21350
- /**
21351
- * Computes height of character at given position
21352
- * @param {Number} line the line index number
21353
- * @param {Number} _char the character index number
21354
- * @return {Number} fontSize of the character
21350
+ /**
21351
+ * Computes height of character at given position
21352
+ * @param {Number} line the line index number
21353
+ * @param {Number} _char the character index number
21354
+ * @return {Number} fontSize of the character
21355
21355
  */
21356
21356
  getHeightOfChar(line, _char) {
21357
21357
  return this.getValueOfPropertyAt(line, _char, 'fontSize');
21358
21358
  }
21359
21359
 
21360
- /**
21361
- * measure a text line measuring all characters.
21362
- * @param {Number} lineIndex line number
21360
+ /**
21361
+ * measure a text line measuring all characters.
21362
+ * @param {Number} lineIndex line number
21363
21363
  */
21364
21364
  measureLine(lineIndex) {
21365
21365
  const lineInfo = this._measureLine(lineIndex);
@@ -21372,11 +21372,11 @@
21372
21372
  return lineInfo;
21373
21373
  }
21374
21374
 
21375
- /**
21376
- * measure every grapheme of a line, populating __charBounds
21377
- * @param {Number} lineIndex
21378
- * @return {Object} object.width total width of characters
21379
- * @return {Object} object.numOfSpaces length of chars that match this._reSpacesAndTabs
21375
+ /**
21376
+ * measure every grapheme of a line, populating __charBounds
21377
+ * @param {Number} lineIndex
21378
+ * @return {Object} object.width total width of characters
21379
+ * @return {Object} object.numOfSpaces length of chars that match this._reSpacesAndTabs
21380
21380
  */
21381
21381
  _measureLine(lineIndex) {
21382
21382
  let width = 0,
@@ -21439,13 +21439,13 @@
21439
21439
  };
21440
21440
  }
21441
21441
 
21442
- /**
21443
- * Calculate the angle and the left,top position of the char that follow a path.
21444
- * It appends it to graphemeInfo to be reused later at rendering
21445
- * @private
21446
- * @param {Number} positionInPath to be measured
21447
- * @param {GraphemeBBox} graphemeInfo current grapheme box information
21448
- * @param {Object} startingPoint position of the point
21442
+ /**
21443
+ * Calculate the angle and the left,top position of the char that follow a path.
21444
+ * It appends it to graphemeInfo to be reused later at rendering
21445
+ * @private
21446
+ * @param {Number} positionInPath to be measured
21447
+ * @param {GraphemeBBox} graphemeInfo current grapheme box information
21448
+ * @param {Object} startingPoint position of the point
21449
21449
  */
21450
21450
  _setGraphemeOnPath(positionInPath, graphemeInfo) {
21451
21451
  const centerPosition = positionInPath + graphemeInfo.kernedWidth / 2,
@@ -21458,13 +21458,13 @@
21458
21458
  graphemeInfo.angle = info.angle + (this.pathSide === RIGHT ? Math.PI : 0);
21459
21459
  }
21460
21460
 
21461
- /**
21462
- *
21463
- * @param {String} grapheme to be measured
21464
- * @param {Number} lineIndex index of the line where the char is
21465
- * @param {Number} charIndex position in the line
21466
- * @param {String} [prevGrapheme] character preceding the one to be measured
21467
- * @returns {GraphemeBBox} grapheme bbox
21461
+ /**
21462
+ *
21463
+ * @param {String} grapheme to be measured
21464
+ * @param {Number} lineIndex index of the line where the char is
21465
+ * @param {Number} charIndex position in the line
21466
+ * @param {String} [prevGrapheme] character preceding the one to be measured
21467
+ * @returns {GraphemeBBox} grapheme bbox
21468
21468
  */
21469
21469
  _getGraphemeBox(grapheme, lineIndex, charIndex, prevGrapheme, skipLeft) {
21470
21470
  const style = this.getCompleteStyleDeclaration(lineIndex, charIndex),
@@ -21492,10 +21492,10 @@
21492
21492
  return box;
21493
21493
  }
21494
21494
 
21495
- /**
21496
- * Calculate height of line at 'lineIndex'
21497
- * @param {Number} lineIndex index of line to calculate
21498
- * @return {Number}
21495
+ /**
21496
+ * Calculate height of line at 'lineIndex'
21497
+ * @param {Number} lineIndex index of line to calculate
21498
+ * @return {Number}
21499
21499
  */
21500
21500
  getHeightOfLine(lineIndex) {
21501
21501
  if (this.__lineHeights[lineIndex]) {
@@ -21511,8 +21511,8 @@
21511
21511
  return this.__lineHeights[lineIndex] = maxHeight * this.lineHeight * this._fontSizeMult;
21512
21512
  }
21513
21513
 
21514
- /**
21515
- * Calculate text box height
21514
+ /**
21515
+ * Calculate text box height
21516
21516
  */
21517
21517
  calcTextHeight() {
21518
21518
  let lineHeight,
@@ -21524,26 +21524,26 @@
21524
21524
  return height;
21525
21525
  }
21526
21526
 
21527
- /**
21528
- * @private
21529
- * @return {Number} Left offset
21527
+ /**
21528
+ * @private
21529
+ * @return {Number} Left offset
21530
21530
  */
21531
21531
  _getLeftOffset() {
21532
21532
  return this.direction === 'ltr' ? -this.width / 2 : this.width / 2;
21533
21533
  }
21534
21534
 
21535
- /**
21536
- * @private
21537
- * @return {Number} Top offset
21535
+ /**
21536
+ * @private
21537
+ * @return {Number} Top offset
21538
21538
  */
21539
21539
  _getTopOffset() {
21540
21540
  return -this.height / 2;
21541
21541
  }
21542
21542
 
21543
- /**
21544
- * @private
21545
- * @param {CanvasRenderingContext2D} ctx Context to render on
21546
- * @param {String} method Method name ("fillText" or "strokeText")
21543
+ /**
21544
+ * @private
21545
+ * @param {CanvasRenderingContext2D} ctx Context to render on
21546
+ * @param {String} method Method name ("fillText" or "strokeText")
21547
21547
  */
21548
21548
  _renderTextCommon(ctx, method) {
21549
21549
  ctx.save();
@@ -21560,9 +21560,9 @@
21560
21560
  ctx.restore();
21561
21561
  }
21562
21562
 
21563
- /**
21564
- * @private
21565
- * @param {CanvasRenderingContext2D} ctx Context to render on
21563
+ /**
21564
+ * @private
21565
+ * @param {CanvasRenderingContext2D} ctx Context to render on
21566
21566
  */
21567
21567
  _renderTextFill(ctx) {
21568
21568
  if (!this.fill && !this.styleHas(FILL)) {
@@ -21571,9 +21571,9 @@
21571
21571
  this._renderTextCommon(ctx, 'fillText');
21572
21572
  }
21573
21573
 
21574
- /**
21575
- * @private
21576
- * @param {CanvasRenderingContext2D} ctx Context to render on
21574
+ /**
21575
+ * @private
21576
+ * @param {CanvasRenderingContext2D} ctx Context to render on
21577
21577
  */
21578
21578
  _renderTextStroke(ctx) {
21579
21579
  if ((!this.stroke || this.strokeWidth === 0) && this.isEmptyStyles()) {
@@ -21590,14 +21590,14 @@
21590
21590
  ctx.restore();
21591
21591
  }
21592
21592
 
21593
- /**
21594
- * @private
21595
- * @param {String} method fillText or strokeText.
21596
- * @param {CanvasRenderingContext2D} ctx Context to render on
21597
- * @param {Array} line Content of the line, splitted in an array by grapheme
21598
- * @param {Number} left
21599
- * @param {Number} top
21600
- * @param {Number} lineIndex
21593
+ /**
21594
+ * @private
21595
+ * @param {String} method fillText or strokeText.
21596
+ * @param {CanvasRenderingContext2D} ctx Context to render on
21597
+ * @param {Array} line Content of the line, splitted in an array by grapheme
21598
+ * @param {Number} left
21599
+ * @param {Number} top
21600
+ * @param {Number} lineIndex
21601
21601
  */
21602
21602
  _renderChars(method, ctx, line, left, top, lineIndex) {
21603
21603
  const lineHeight = this.getHeightOfLine(lineIndex),
@@ -21679,16 +21679,16 @@
21679
21679
  ctx.restore();
21680
21680
  }
21681
21681
 
21682
- /**
21683
- * This function try to patch the missing gradientTransform on canvas gradients.
21684
- * transforming a context to transform the gradient, is going to transform the stroke too.
21685
- * we want to transform the gradient but not the stroke operation, so we create
21686
- * a transformed gradient on a pattern and then we use the pattern instead of the gradient.
21687
- * this method has drawbacks: is slow, is in low resolution, needs a patch for when the size
21688
- * is limited.
21689
- * @private
21690
- * @param {TFiller} filler a fabric gradient instance
21691
- * @return {CanvasPattern} a pattern to use as fill/stroke style
21682
+ /**
21683
+ * This function try to patch the missing gradientTransform on canvas gradients.
21684
+ * transforming a context to transform the gradient, is going to transform the stroke too.
21685
+ * we want to transform the gradient but not the stroke operation, so we create
21686
+ * a transformed gradient on a pattern and then we use the pattern instead of the gradient.
21687
+ * this method has drawbacks: is slow, is in low resolution, needs a patch for when the size
21688
+ * is limited.
21689
+ * @private
21690
+ * @param {TFiller} filler a fabric gradient instance
21691
+ * @return {CanvasPattern} a pattern to use as fill/stroke style
21692
21692
  */
21693
21693
  _applyPatternGradientTransformText(filler) {
21694
21694
  // TODO: verify compatibility with strokeUniform
@@ -21744,12 +21744,12 @@
21744
21744
  };
21745
21745
  }
21746
21746
 
21747
- /**
21748
- * This function prepare the canvas for a stroke style, and stroke and strokeWidth
21749
- * need to be sent in as defined
21750
- * @param {CanvasRenderingContext2D} ctx
21751
- * @param {CompleteTextStyleDeclaration} style with stroke and strokeWidth defined
21752
- * @returns
21747
+ /**
21748
+ * This function prepare the canvas for a stroke style, and stroke and strokeWidth
21749
+ * need to be sent in as defined
21750
+ * @param {CanvasRenderingContext2D} ctx
21751
+ * @param {CompleteTextStyleDeclaration} style with stroke and strokeWidth defined
21752
+ * @returns
21753
21753
  */
21754
21754
  _setStrokeStyles(ctx, _ref) {
21755
21755
  let {
@@ -21764,12 +21764,12 @@
21764
21764
  return this.handleFiller(ctx, 'strokeStyle', stroke);
21765
21765
  }
21766
21766
 
21767
- /**
21768
- * This function prepare the canvas for a ill style, and fill
21769
- * need to be sent in as defined
21770
- * @param {CanvasRenderingContext2D} ctx
21771
- * @param {CompleteTextStyleDeclaration} style with ill defined
21772
- * @returns
21767
+ /**
21768
+ * This function prepare the canvas for a ill style, and fill
21769
+ * need to be sent in as defined
21770
+ * @param {CanvasRenderingContext2D} ctx
21771
+ * @param {CompleteTextStyleDeclaration} style with ill defined
21772
+ * @returns
21773
21773
  */
21774
21774
  _setFillStyles(ctx, _ref2) {
21775
21775
  let {
@@ -21778,16 +21778,16 @@
21778
21778
  return this.handleFiller(ctx, 'fillStyle', fill);
21779
21779
  }
21780
21780
 
21781
- /**
21782
- * @private
21783
- * @param {String} method
21784
- * @param {CanvasRenderingContext2D} ctx Context to render on
21785
- * @param {Number} lineIndex
21786
- * @param {Number} charIndex
21787
- * @param {String} _char
21788
- * @param {Number} left Left coordinate
21789
- * @param {Number} top Top coordinate
21790
- * @param {Number} lineHeight Height of the line
21781
+ /**
21782
+ * @private
21783
+ * @param {String} method
21784
+ * @param {CanvasRenderingContext2D} ctx Context to render on
21785
+ * @param {Number} lineIndex
21786
+ * @param {Number} charIndex
21787
+ * @param {String} _char
21788
+ * @param {Number} left Left coordinate
21789
+ * @param {Number} top Top coordinate
21790
+ * @param {Number} lineHeight Height of the line
21791
21791
  */
21792
21792
  _renderChar(method, ctx, lineIndex, charIndex, _char, left, top) {
21793
21793
  const decl = this._getStyleDeclaration(lineIndex, charIndex),
@@ -21816,30 +21816,30 @@
21816
21816
  ctx.restore();
21817
21817
  }
21818
21818
 
21819
- /**
21820
- * Turns the character into a 'superior figure' (i.e. 'superscript')
21821
- * @param {Number} start selection start
21822
- * @param {Number} end selection end
21819
+ /**
21820
+ * Turns the character into a 'superior figure' (i.e. 'superscript')
21821
+ * @param {Number} start selection start
21822
+ * @param {Number} end selection end
21823
21823
  */
21824
21824
  setSuperscript(start, end) {
21825
21825
  this._setScript(start, end, this.superscript);
21826
21826
  }
21827
21827
 
21828
- /**
21829
- * Turns the character into an 'inferior figure' (i.e. 'subscript')
21830
- * @param {Number} start selection start
21831
- * @param {Number} end selection end
21828
+ /**
21829
+ * Turns the character into an 'inferior figure' (i.e. 'subscript')
21830
+ * @param {Number} start selection start
21831
+ * @param {Number} end selection end
21832
21832
  */
21833
21833
  setSubscript(start, end) {
21834
21834
  this._setScript(start, end, this.subscript);
21835
21835
  }
21836
21836
 
21837
- /**
21838
- * Applies 'schema' at given position
21839
- * @private
21840
- * @param {Number} start selection start
21841
- * @param {Number} end selection end
21842
- * @param {Number} schema
21837
+ /**
21838
+ * Applies 'schema' at given position
21839
+ * @private
21840
+ * @param {Number} start selection start
21841
+ * @param {Number} end selection end
21842
+ * @param {Number} schema
21843
21843
  */
21844
21844
  _setScript(start, end, schema) {
21845
21845
  const loc = this.get2DCursorLocation(start, true),
@@ -21852,10 +21852,10 @@
21852
21852
  this.setSelectionStyles(style, start, end);
21853
21853
  }
21854
21854
 
21855
- /**
21856
- * @private
21857
- * @param {Number} lineIndex index text line
21858
- * @return {Number} Line left offset
21855
+ /**
21856
+ * @private
21857
+ * @param {Number} lineIndex index text line
21858
+ * @return {Number} Line left offset
21859
21859
  */
21860
21860
  _getLineLeftOffset(lineIndex) {
21861
21861
  const lineWidth = this.getLineWidth(lineIndex),
@@ -21905,8 +21905,8 @@
21905
21905
  return leftOffset;
21906
21906
  }
21907
21907
 
21908
- /**
21909
- * @private
21908
+ /**
21909
+ * @private
21910
21910
  */
21911
21911
  _clearCache() {
21912
21912
  this._forceClearCache = false;
@@ -21915,12 +21915,12 @@
21915
21915
  this.__charBounds = [];
21916
21916
  }
21917
21917
 
21918
- /**
21919
- * Measure a single line given its index. Used to calculate the initial
21920
- * text bounding box. The values are calculated and stored in __lineWidths cache.
21921
- * @private
21922
- * @param {Number} lineIndex line number
21923
- * @return {Number} Line width
21918
+ /**
21919
+ * Measure a single line given its index. Used to calculate the initial
21920
+ * text bounding box. The values are calculated and stored in __lineWidths cache.
21921
+ * @private
21922
+ * @param {Number} lineIndex line number
21923
+ * @return {Number} Line width
21924
21924
  */
21925
21925
  getLineWidth(lineIndex) {
21926
21926
  if (this.__lineWidths[lineIndex] !== undefined) {
@@ -21939,12 +21939,12 @@
21939
21939
  return 0;
21940
21940
  }
21941
21941
 
21942
- /**
21943
- * Retrieves the value of property at given character position
21944
- * @param {Number} lineIndex the line number
21945
- * @param {Number} charIndex the character number
21946
- * @param {String} property the property name
21947
- * @returns the value of 'property'
21942
+ /**
21943
+ * Retrieves the value of property at given character position
21944
+ * @param {Number} lineIndex the line number
21945
+ * @param {Number} charIndex the character number
21946
+ * @param {String} property the property name
21947
+ * @returns the value of 'property'
21948
21948
  */
21949
21949
  getValueOfPropertyAt(lineIndex, charIndex, property) {
21950
21950
  var _charStyle$property;
@@ -21952,9 +21952,9 @@
21952
21952
  return (_charStyle$property = charStyle[property]) !== null && _charStyle$property !== void 0 ? _charStyle$property : this[property];
21953
21953
  }
21954
21954
 
21955
- /**
21956
- * @private
21957
- * @param {CanvasRenderingContext2D} ctx Context to render on
21955
+ /**
21956
+ * @private
21957
+ * @param {CanvasRenderingContext2D} ctx Context to render on
21958
21958
  */
21959
21959
  _renderTextDecoration(ctx, type) {
21960
21960
  if (!this[type] && !this.styleHas(type)) {
@@ -22038,10 +22038,10 @@
22038
22038
  this._removeShadow(ctx);
22039
22039
  }
22040
22040
 
22041
- /**
22042
- * return font declaration string for canvas context
22043
- * @param {Object} [styleObject] object
22044
- * @returns {String} font declaration formatted for canvas context.
22041
+ /**
22042
+ * return font declaration string for canvas context
22043
+ * @param {Object} [styleObject] object
22044
+ * @returns {String} font declaration formatted for canvas context.
22045
22045
  */
22046
22046
  _getFontDeclaration() {
22047
22047
  let {
@@ -22067,9 +22067,9 @@
22067
22067
  return [fontStyle, fontWeight, `${forMeasuring ? this.CACHE_FONT_SIZE : fontSize}px`, parsedFontFamily].join(' ');
22068
22068
  }
22069
22069
 
22070
- /**
22071
- * Renders text instance on a specified context
22072
- * @param {CanvasRenderingContext2D} ctx Context to render on
22070
+ /**
22071
+ * Renders text instance on a specified context
22072
+ * @param {CanvasRenderingContext2D} ctx Context to render on
22073
22073
  */
22074
22074
  render(ctx) {
22075
22075
  if (!this.visible) {
@@ -22084,22 +22084,22 @@
22084
22084
  super.render(ctx);
22085
22085
  }
22086
22086
 
22087
- /**
22088
- * Override this method to customize grapheme splitting
22089
- * @todo the util `graphemeSplit` needs to be injectable in some way.
22090
- * is more comfortable to inject the correct util rather than having to override text
22091
- * in the middle of the prototype chain
22092
- * @param {string} value
22093
- * @returns {string[]} array of graphemes
22087
+ /**
22088
+ * Override this method to customize grapheme splitting
22089
+ * @todo the util `graphemeSplit` needs to be injectable in some way.
22090
+ * is more comfortable to inject the correct util rather than having to override text
22091
+ * in the middle of the prototype chain
22092
+ * @param {string} value
22093
+ * @returns {string[]} array of graphemes
22094
22094
  */
22095
22095
  graphemeSplit(value) {
22096
22096
  return graphemeSplit(value);
22097
22097
  }
22098
22098
 
22099
- /**
22100
- * Returns the text as an array of lines.
22101
- * @param {String} text text to split
22102
- * @returns Lines in the text
22099
+ /**
22100
+ * Returns the text as an array of lines.
22101
+ * @param {String} text text to split
22102
+ * @returns Lines in the text
22103
22103
  */
22104
22104
  _splitTextIntoLines(text) {
22105
22105
  const lines = text.split(this._reNewline),
@@ -22125,18 +22125,18 @@
22125
22125
  };
22126
22126
  }
22127
22127
 
22128
- /**
22129
- * Check if text contains Arabic characters
22130
- * @private
22128
+ /**
22129
+ * Check if text contains Arabic characters
22130
+ * @private
22131
22131
  */
22132
22132
  _containsArabicText(text) {
22133
22133
  return /[\u0600-\u06FF\u0750-\u077F\uFB50-\uFDFF\uFE70-\uFEFF]/.test(text);
22134
22134
  }
22135
22135
 
22136
- /**
22137
- * Returns object representation of an instance
22138
- * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
22139
- * @return {Object} Object representation of an instance
22136
+ /**
22137
+ * Returns object representation of an instance
22138
+ * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
22139
+ * @return {Object} Object representation of an instance
22140
22140
  */
22141
22141
  toObject() {
22142
22142
  let propertiesToInclude = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
@@ -22179,23 +22179,23 @@
22179
22179
  return this;
22180
22180
  }
22181
22181
 
22182
- /**
22183
- * Returns complexity of an instance
22184
- * @return {Number} complexity
22182
+ /**
22183
+ * Returns complexity of an instance
22184
+ * @return {Number} complexity
22185
22185
  */
22186
22186
  complexity() {
22187
22187
  return 1;
22188
22188
  }
22189
22189
 
22190
- /**
22191
- * List of generic font families
22192
- * @see https://developer.mozilla.org/en-US/docs/Web/CSS/font-family#generic-name
22190
+ /**
22191
+ * List of generic font families
22192
+ * @see https://developer.mozilla.org/en-US/docs/Web/CSS/font-family#generic-name
22193
22193
  */
22194
22194
 
22195
- /**
22196
- * Returns FabricText instance from an SVG element (<b>not yet implemented</b>)
22197
- * @param {HTMLElement} element Element to parse
22198
- * @param {Object} [options] Options object
22195
+ /**
22196
+ * Returns FabricText instance from an SVG element (<b>not yet implemented</b>)
22197
+ * @param {HTMLElement} element Element to parse
22198
+ * @param {Object} [options] Options object
22199
22199
  */
22200
22200
  static async fromElement(element, options, cssRules) {
22201
22201
  const parsedAttributes = parseAttributes(element, FabricText.ATTRIBUTE_NAMES, cssRules);
@@ -22234,10 +22234,10 @@
22234
22234
  scaledDiff = lineHeightDiff * textHeightScaleFactor,
22235
22235
  textHeight = text.getScaledHeight() + scaledDiff;
22236
22236
  let offX = 0;
22237
- /*
22238
- Adjust positioning:
22239
- x/y attributes in SVG correspond to the bottom-left corner of text bounding box
22240
- fabric output by default at top, left.
22237
+ /*
22238
+ Adjust positioning:
22239
+ x/y attributes in SVG correspond to the bottom-left corner of text bounding box
22240
+ fabric output by default at top, left.
22241
22241
  */
22242
22242
  if (textAnchor === CENTER) {
22243
22243
  offX = text.getScaledWidth() / 2;
@@ -22255,9 +22255,9 @@
22255
22255
 
22256
22256
  /* _FROM_SVG_END_ */
22257
22257
 
22258
- /**
22259
- * Check if the font is ready for accurate measurements
22260
- * @private
22258
+ /**
22259
+ * Check if the font is ready for accurate measurements
22260
+ * @private
22261
22261
  */
22262
22262
  _isFontReady() {
22263
22263
  if (typeof document === 'undefined' || !('fonts' in document)) {
@@ -22270,9 +22270,9 @@
22270
22270
  }
22271
22271
  }
22272
22272
 
22273
- /**
22274
- * Schedule re-initialization after font loads
22275
- * @private
22273
+ /**
22274
+ * Schedule re-initialization after font loads
22275
+ * @private
22276
22276
  */
22277
22277
  _scheduleInitAfterFontLoad() {
22278
22278
  if (typeof document === 'undefined' || !('fonts' in document)) {
@@ -22308,8 +22308,8 @@
22308
22308
  });
22309
22309
  }
22310
22310
 
22311
- /**
22312
- * Force complete text re-initialization (useful after JSON loading)
22311
+ /**
22312
+ * Force complete text re-initialization (useful after JSON loading)
22313
22313
  */
22314
22314
  forceTextReinitialization() {
22315
22315
  console.log('🔄 Force reinitializing text object');
@@ -22337,10 +22337,10 @@
22337
22337
  }
22338
22338
  }
22339
22339
 
22340
- /**
22341
- * Returns FabricText instance from an object representation
22342
- * @param {Object} object plain js Object to create an instance from
22343
- * @returns {Promise<FabricText>}
22340
+ /**
22341
+ * Returns FabricText instance from an object representation
22342
+ * @param {Object} object plain js Object to create an instance from
22343
+ * @returns {Promise<FabricText>}
22344
22344
  */
22345
22345
  static fromObject(object) {
22346
22346
  return this._fromObject({
@@ -22442,10 +22442,10 @@
22442
22442
  });
22443
22443
  }
22444
22444
  }
22445
- /**
22446
- * Properties that requires a text layout recalculation when changed
22447
- * @type string[]
22448
- * @protected
22445
+ /**
22446
+ * Properties that requires a text layout recalculation when changed
22447
+ * @type string[]
22448
+ * @protected
22449
22449
  */
22450
22450
  _defineProperty(FabricText, "textLayoutProperties", textLayoutProperties);
22451
22451
  _defineProperty(FabricText, "cacheProperties", [...cacheProperties, ...additionalProps]);
@@ -22453,9 +22453,9 @@
22453
22453
  _defineProperty(FabricText, "type", 'Text');
22454
22454
  _defineProperty(FabricText, "genericFonts", ['serif', 'sans-serif', 'monospace', 'cursive', 'fantasy', 'system-ui', 'ui-serif', 'ui-sans-serif', 'ui-monospace', 'ui-rounded', 'math', 'emoji', 'fangsong']);
22455
22455
  /* _FROM_SVG_START_ */
22456
- /**
22457
- * List of attribute names to account for when parsing SVG element (used by {@link FabricText.fromElement})
22458
- * @see: http://www.w3.org/TR/SVG/text.html#TextElement
22456
+ /**
22457
+ * List of attribute names to account for when parsing SVG element (used by {@link FabricText.fromElement})
22458
+ * @see: http://www.w3.org/TR/SVG/text.html#TextElement
22459
22459
  */
22460
22460
  _defineProperty(FabricText, "ATTRIBUTE_NAMES", SHARED_ATTRIBUTES.concat('x', 'y', 'dx', 'dy', 'font-family', 'font-style', 'font-weight', 'font-size', 'letter-spacing', 'text-decoration', 'text-anchor'));
22461
22461
  applyMixins(FabricText, [TextSVGExportMixin]);
@@ -26594,11 +26594,11 @@
26594
26594
 
26595
26595
  // @TODO this is not complete
26596
26596
 
26597
- /**
26598
- * Textbox class, based on IText, allows the user to resize the text rectangle
26599
- * and wraps lines automatically. Textboxes have their Y scaling locked, the
26600
- * user can only change width. Height is adjusted automatically based on the
26601
- * wrapping of lines.
26597
+ /**
26598
+ * Textbox class, based on IText, allows the user to resize the text rectangle
26599
+ * and wraps lines automatically. Textboxes have their Y scaling locked, the
26600
+ * user can only change width. Height is adjusted automatically based on the
26601
+ * wrapping of lines.
26602
26602
  */
26603
26603
  class Textbox extends IText {
26604
26604
  static getDefaults() {
@@ -26608,10 +26608,10 @@
26608
26608
  };
26609
26609
  }
26610
26610
 
26611
- /**
26612
- * Constructor
26613
- * @param {String} text Text string
26614
- * @param {Object} [options] Options object
26611
+ /**
26612
+ * Constructor
26613
+ * @param {String} text Text string
26614
+ * @param {Object} [options] Options object
26615
26615
  */
26616
26616
  constructor(text, options) {
26617
26617
  super(text, {
@@ -26621,10 +26621,10 @@
26621
26621
  this.initializeEventListeners();
26622
26622
  }
26623
26623
 
26624
- /**
26625
- * Creates the default control object.
26626
- * If you prefer to have on instance of controls shared among all objects
26627
- * make this function return an empty object and add controls to the ownDefaults object
26624
+ /**
26625
+ * Creates the default control object.
26626
+ * If you prefer to have on instance of controls shared among all objects
26627
+ * make this function return an empty object and add controls to the ownDefaults object
26628
26628
  */
26629
26629
  static createControls() {
26630
26630
  return {
@@ -26632,11 +26632,11 @@
26632
26632
  };
26633
26633
  }
26634
26634
 
26635
- /**
26636
- * Unlike superclass's version of this function, Textbox does not update
26637
- * its width.
26638
- * @private
26639
- * @override
26635
+ /**
26636
+ * Unlike superclass's version of this function, Textbox does not update
26637
+ * its width.
26638
+ * @private
26639
+ * @override
26640
26640
  */
26641
26641
  initDimensions() {
26642
26642
  if (!this.initialized) {
@@ -26704,6 +26704,7 @@
26704
26704
  if (this._usingBrowserWrapping) {
26705
26705
  this._browserWrapInitialized = true;
26706
26706
  }
26707
+ this.calcTextWidth();
26707
26708
  if (this.textAlign.includes(JUSTIFY)) {
26708
26709
  // For browser wrapping fonts, apply browser-calculated justify spaces
26709
26710
  if (this._usingBrowserWrapping) {
@@ -26778,11 +26779,38 @@
26778
26779
  } else {
26779
26780
  this.height = this.calcTextHeight();
26780
26781
  }
26782
+
26783
+ // Double-check that justify was applied by checking space widths
26784
+ if (this.textAlign.includes('justify') && this.__charBounds) {
26785
+ setTimeout(() => {
26786
+ // Verify justify was applied by checking if space widths vary
26787
+ let hasVariableSpaces = false;
26788
+ this.__charBounds.forEach((lineBounds, i) => {
26789
+ if (lineBounds && this._textLines && this._textLines[i]) {
26790
+ const spaces = lineBounds.filter((bound, j) => /\s/.test(this._textLines[i][j]));
26791
+ if (spaces.length > 1) {
26792
+ const firstSpaceWidth = spaces[0].width;
26793
+ hasVariableSpaces = spaces.some(space => Math.abs(space.width - firstSpaceWidth) > 0.1);
26794
+ }
26795
+ }
26796
+ });
26797
+ if (!hasVariableSpaces && this.__charBounds.length > 0) {
26798
+ console.warn(' ⚠️ Justify spaces still uniform - forcing enlargeSpaces again');
26799
+ if (this.enlargeSpaces) {
26800
+ var _this$canvas3;
26801
+ this.enlargeSpaces();
26802
+ (_this$canvas3 = this.canvas) === null || _this$canvas3 === void 0 || _this$canvas3.requestRenderAll();
26803
+ }
26804
+ } else {
26805
+ console.log(' ✅ Justify spaces properly expanded');
26806
+ }
26807
+ }, 10);
26808
+ }
26781
26809
  }
26782
26810
 
26783
- /**
26784
- * Schedule justify calculation after font loads (Textbox-specific)
26785
- * @private
26811
+ /**
26812
+ * Schedule justify calculation after font loads (Textbox-specific)
26813
+ * @private
26786
26814
  */
26787
26815
  _scheduleJustifyAfterFontLoad() {
26788
26816
  if (typeof document === 'undefined' || !('fonts' in document)) {
@@ -26796,22 +26824,22 @@
26796
26824
  this._fontJustifyScheduled = true;
26797
26825
  const fontSpec = `${this.fontSize}px ${this.fontFamily}`;
26798
26826
  document.fonts.load(fontSpec).then(() => {
26799
- var _this$canvas3;
26827
+ var _this$canvas4;
26800
26828
  this._fontJustifyScheduled = false;
26801
26829
  console.log('🔧 Textbox: Font loaded, applying justify alignment');
26802
26830
 
26803
26831
  // Re-run initDimensions to ensure proper justify calculation
26804
26832
  this.initDimensions();
26805
- (_this$canvas3 = this.canvas) === null || _this$canvas3 === void 0 || _this$canvas3.requestRenderAll();
26833
+ (_this$canvas4 = this.canvas) === null || _this$canvas4 === void 0 || _this$canvas4.requestRenderAll();
26806
26834
  }).catch(() => {
26807
26835
  this._fontJustifyScheduled = false;
26808
26836
  console.warn('⚠️ Textbox: Font loading failed, justify may be incorrect');
26809
26837
  });
26810
26838
  }
26811
26839
 
26812
- /**
26813
- * Advanced dimensions calculation using new layout engine
26814
- * @private
26840
+ /**
26841
+ * Advanced dimensions calculation using new layout engine
26842
+ * @private
26815
26843
  */
26816
26844
  initDimensionsAdvanced() {
26817
26845
  if (!this.initialized) {
@@ -26866,9 +26894,9 @@
26866
26894
  this.dirty = true;
26867
26895
  }
26868
26896
 
26869
- /**
26870
- * Generate style map from new layout format
26871
- * @private
26897
+ /**
26898
+ * Generate style map from new layout format
26899
+ * @private
26872
26900
  */
26873
26901
  _generateStyleMapFromLayout(layout) {
26874
26902
  const map = {};
@@ -26890,12 +26918,12 @@
26890
26918
  return map;
26891
26919
  }
26892
26920
 
26893
- /**
26894
- * Generate an object that translates the style object so that it is
26895
- * broken up by visual lines (new lines and automatic wrapping).
26896
- * The original text styles object is broken up by actual lines (new lines only),
26897
- * which is only sufficient for Text / IText
26898
- * @private
26921
+ /**
26922
+ * Generate an object that translates the style object so that it is
26923
+ * broken up by visual lines (new lines and automatic wrapping).
26924
+ * The original text styles object is broken up by actual lines (new lines only),
26925
+ * which is only sufficient for Text / IText
26926
+ * @private
26899
26927
  */
26900
26928
  _generateStyleMap(textInfo) {
26901
26929
  let realLineCount = 0,
@@ -26922,10 +26950,10 @@
26922
26950
  return map;
26923
26951
  }
26924
26952
 
26925
- /**
26926
- * Returns true if object has a style property or has it on a specified line
26927
- * @param {Number} lineIndex
26928
- * @return {Boolean}
26953
+ /**
26954
+ * Returns true if object has a style property or has it on a specified line
26955
+ * @param {Number} lineIndex
26956
+ * @return {Boolean}
26929
26957
  */
26930
26958
  styleHas(property, lineIndex) {
26931
26959
  if (this._styleMap && !this.isWrapping) {
@@ -26937,10 +26965,10 @@
26937
26965
  return super.styleHas(property, lineIndex);
26938
26966
  }
26939
26967
 
26940
- /**
26941
- * Returns true if object has no styling or no styling in a line
26942
- * @param {Number} lineIndex , lineIndex is on wrapped lines.
26943
- * @return {Boolean}
26968
+ /**
26969
+ * Returns true if object has no styling or no styling in a line
26970
+ * @param {Number} lineIndex , lineIndex is on wrapped lines.
26971
+ * @return {Boolean}
26944
26972
  */
26945
26973
  isEmptyStyles(lineIndex) {
26946
26974
  if (!this.styles) {
@@ -26977,11 +27005,11 @@
26977
27005
  return true;
26978
27006
  }
26979
27007
 
26980
- /**
26981
- * @protected
26982
- * @param {Number} lineIndex
26983
- * @param {Number} charIndex
26984
- * @return {TextStyleDeclaration} a style object reference to the existing one or a new empty object when undefined
27008
+ /**
27009
+ * @protected
27010
+ * @param {Number} lineIndex
27011
+ * @param {Number} charIndex
27012
+ * @return {TextStyleDeclaration} a style object reference to the existing one or a new empty object when undefined
26985
27013
  */
26986
27014
  _getStyleDeclaration(lineIndex, charIndex) {
26987
27015
  if (this._styleMap && !this.isWrapping) {
@@ -26995,59 +27023,59 @@
26995
27023
  return super._getStyleDeclaration(lineIndex, charIndex);
26996
27024
  }
26997
27025
 
26998
- /**
26999
- * @param {Number} lineIndex
27000
- * @param {Number} charIndex
27001
- * @param {Object} style
27002
- * @private
27026
+ /**
27027
+ * @param {Number} lineIndex
27028
+ * @param {Number} charIndex
27029
+ * @param {Object} style
27030
+ * @private
27003
27031
  */
27004
27032
  _setStyleDeclaration(lineIndex, charIndex, style) {
27005
27033
  const map = this._styleMap[lineIndex];
27006
27034
  super._setStyleDeclaration(map.line, map.offset + charIndex, style);
27007
27035
  }
27008
27036
 
27009
- /**
27010
- * @param {Number} lineIndex
27011
- * @param {Number} charIndex
27012
- * @private
27037
+ /**
27038
+ * @param {Number} lineIndex
27039
+ * @param {Number} charIndex
27040
+ * @private
27013
27041
  */
27014
27042
  _deleteStyleDeclaration(lineIndex, charIndex) {
27015
27043
  const map = this._styleMap[lineIndex];
27016
27044
  super._deleteStyleDeclaration(map.line, map.offset + charIndex);
27017
27045
  }
27018
27046
 
27019
- /**
27020
- * probably broken need a fix
27021
- * Returns the real style line that correspond to the wrapped lineIndex line
27022
- * Used just to verify if the line does exist or not.
27023
- * @param {Number} lineIndex
27024
- * @returns {Boolean} if the line exists or not
27025
- * @private
27047
+ /**
27048
+ * probably broken need a fix
27049
+ * Returns the real style line that correspond to the wrapped lineIndex line
27050
+ * Used just to verify if the line does exist or not.
27051
+ * @param {Number} lineIndex
27052
+ * @returns {Boolean} if the line exists or not
27053
+ * @private
27026
27054
  */
27027
27055
  _getLineStyle(lineIndex) {
27028
27056
  const map = this._styleMap[lineIndex];
27029
27057
  return !!this.styles[map.line];
27030
27058
  }
27031
27059
 
27032
- /**
27033
- * Set the line style to an empty object so that is initialized
27034
- * @param {Number} lineIndex
27035
- * @param {Object} style
27036
- * @private
27060
+ /**
27061
+ * Set the line style to an empty object so that is initialized
27062
+ * @param {Number} lineIndex
27063
+ * @param {Object} style
27064
+ * @private
27037
27065
  */
27038
27066
  _setLineStyle(lineIndex) {
27039
27067
  const map = this._styleMap[lineIndex];
27040
27068
  super._setLineStyle(map.line);
27041
27069
  }
27042
27070
 
27043
- /**
27044
- * Wraps text using the 'width' property of Textbox. First this function
27045
- * splits text on newlines, so we preserve newlines entered by the user.
27046
- * Then it wraps each line using the width of the Textbox by calling
27047
- * _wrapLine().
27048
- * @param {Array} lines The string array of text that is split into lines
27049
- * @param {Number} desiredWidth width you want to wrap to
27050
- * @returns {Array} Array of lines
27071
+ /**
27072
+ * Wraps text using the 'width' property of Textbox. First this function
27073
+ * splits text on newlines, so we preserve newlines entered by the user.
27074
+ * Then it wraps each line using the width of the Textbox by calling
27075
+ * _wrapLine().
27076
+ * @param {Array} lines The string array of text that is split into lines
27077
+ * @param {Number} desiredWidth width you want to wrap to
27078
+ * @returns {Array} Array of lines
27051
27079
  */
27052
27080
  _wrapText(lines, desiredWidth) {
27053
27081
  this.isWrapping = true;
@@ -27061,12 +27089,12 @@
27061
27089
  return wrapped;
27062
27090
  }
27063
27091
 
27064
- /**
27065
- * For each line of text terminated by an hard line stop,
27066
- * measure each word width and extract the largest word from all.
27067
- * The returned words here are the one that at the end will be rendered.
27068
- * @param {string[]} lines the lines we need to measure
27069
- *
27092
+ /**
27093
+ * For each line of text terminated by an hard line stop,
27094
+ * measure each word width and extract the largest word from all.
27095
+ * The returned words here are the one that at the end will be rendered.
27096
+ * @param {string[]} lines the lines we need to measure
27097
+ *
27070
27098
  */
27071
27099
  getGraphemeDataForRender(lines) {
27072
27100
  const splitByGrapheme = this.splitByGrapheme,
@@ -27099,17 +27127,17 @@
27099
27127
  };
27100
27128
  }
27101
27129
 
27102
- /**
27103
- * Helper function to measure a string of text, given its lineIndex and charIndex offset
27104
- * It gets called when charBounds are not available yet.
27105
- * Override if necessary
27106
- * Use with {@link Textbox#wordSplit}
27107
- *
27108
- * @param {CanvasRenderingContext2D} ctx
27109
- * @param {String} text
27110
- * @param {number} lineIndex
27111
- * @param {number} charOffset
27112
- * @returns {number}
27130
+ /**
27131
+ * Helper function to measure a string of text, given its lineIndex and charIndex offset
27132
+ * It gets called when charBounds are not available yet.
27133
+ * Override if necessary
27134
+ * Use with {@link Textbox#wordSplit}
27135
+ *
27136
+ * @param {CanvasRenderingContext2D} ctx
27137
+ * @param {String} text
27138
+ * @param {number} lineIndex
27139
+ * @param {number} charOffset
27140
+ * @returns {number}
27113
27141
  */
27114
27142
  _measureWord(word, lineIndex) {
27115
27143
  let charOffset = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
@@ -27124,26 +27152,26 @@
27124
27152
  return width;
27125
27153
  }
27126
27154
 
27127
- /**
27128
- * Override this method to customize word splitting
27129
- * Use with {@link Textbox#_measureWord}
27130
- * @param {string} value
27131
- * @returns {string[]} array of words
27155
+ /**
27156
+ * Override this method to customize word splitting
27157
+ * Use with {@link Textbox#_measureWord}
27158
+ * @param {string} value
27159
+ * @returns {string[]} array of words
27132
27160
  */
27133
27161
  wordSplit(value) {
27134
27162
  return value.split(this._wordJoiners);
27135
27163
  }
27136
27164
 
27137
- /**
27138
- * Wraps a line of text using the width of the Textbox as desiredWidth
27139
- * and leveraging the known width o words from GraphemeData
27140
- * @private
27141
- * @param {Number} lineIndex
27142
- * @param {Number} desiredWidth width you want to wrap the line to
27143
- * @param {GraphemeData} graphemeData an object containing all the lines' words width.
27144
- * @param {Number} reservedSpace space to remove from wrapping for custom functionalities
27145
- * @returns {Array} Array of line(s) into which the given text is wrapped
27146
- * to.
27165
+ /**
27166
+ * Wraps a line of text using the width of the Textbox as desiredWidth
27167
+ * and leveraging the known width o words from GraphemeData
27168
+ * @private
27169
+ * @param {Number} lineIndex
27170
+ * @param {Number} desiredWidth width you want to wrap the line to
27171
+ * @param {GraphemeData} graphemeData an object containing all the lines' words width.
27172
+ * @param {Number} reservedSpace space to remove from wrapping for custom functionalities
27173
+ * @returns {Array} Array of line(s) into which the given text is wrapped
27174
+ * to.
27147
27175
  */
27148
27176
  _wrapLine(lineIndex, desiredWidth, _ref) {
27149
27177
  let {
@@ -27225,11 +27253,11 @@
27225
27253
  return graphemeLines;
27226
27254
  }
27227
27255
 
27228
- /**
27229
- * Detect if the text line is ended with an hard break
27230
- * text and itext do not have wrapping, return false
27231
- * @param {Number} lineIndex text to split
27232
- * @return {Boolean}
27256
+ /**
27257
+ * Detect if the text line is ended with an hard break
27258
+ * text and itext do not have wrapping, return false
27259
+ * @param {Number} lineIndex text to split
27260
+ * @return {Boolean}
27233
27261
  */
27234
27262
  isEndOfWrapping(lineIndex) {
27235
27263
  if (!this._styleMap[lineIndex + 1]) {
@@ -27243,12 +27271,12 @@
27243
27271
  return false;
27244
27272
  }
27245
27273
 
27246
- /**
27247
- * Detect if a line has a linebreak and so we need to account for it when moving
27248
- * and counting style.
27249
- * This is important only for splitByGrapheme at the end of wrapping.
27250
- * If we are not wrapping the offset is always 1
27251
- * @return Number
27274
+ /**
27275
+ * Detect if a line has a linebreak and so we need to account for it when moving
27276
+ * and counting style.
27277
+ * This is important only for splitByGrapheme at the end of wrapping.
27278
+ * If we are not wrapping the offset is always 1
27279
+ * @return Number
27252
27280
  */
27253
27281
  missingNewlineOffset(lineIndex, skipWrapping) {
27254
27282
  if (this.splitByGrapheme && !skipWrapping) {
@@ -27257,12 +27285,12 @@
27257
27285
  return 1;
27258
27286
  }
27259
27287
 
27260
- /**
27261
- * Gets lines of text to render in the Textbox. This function calculates
27262
- * text wrapping on the fly every time it is called.
27263
- * @param {String} text text to split
27264
- * @returns {Array} Array of lines in the Textbox.
27265
- * @override
27288
+ /**
27289
+ * Gets lines of text to render in the Textbox. This function calculates
27290
+ * text wrapping on the fly every time it is called.
27291
+ * @param {String} text text to split
27292
+ * @returns {Array} Array of lines in the Textbox.
27293
+ * @override
27266
27294
  */
27267
27295
  _splitTextIntoLines(text) {
27268
27296
  // Check if we need browser wrapping using smart font detection
@@ -27309,9 +27337,9 @@
27309
27337
  return newText;
27310
27338
  }
27311
27339
 
27312
- /**
27313
- * Use browser's native text wrapping for accurate handling of fonts without English glyphs
27314
- * @private
27340
+ /**
27341
+ * Use browser's native text wrapping for accurate handling of fonts without English glyphs
27342
+ * @private
27315
27343
  */
27316
27344
  _splitTextIntoLinesWithBrowser(text) {
27317
27345
  if (typeof document === 'undefined') {
@@ -27437,9 +27465,9 @@
27437
27465
  };
27438
27466
  }
27439
27467
 
27440
- /**
27441
- * Extract justify space measurements from browser
27442
- * @private
27468
+ /**
27469
+ * Extract justify space measurements from browser
27470
+ * @private
27443
27471
  */
27444
27472
  _extractJustifySpaceMeasurements(element, lines) {
27445
27473
  console.log(`🔤 Extracting browser justify space measurements for ${lines.length} lines`);
@@ -27475,9 +27503,9 @@
27475
27503
  return spaceWidths;
27476
27504
  }
27477
27505
 
27478
- /**
27479
- * Apply browser-calculated justify space measurements
27480
- * @private
27506
+ /**
27507
+ * Apply browser-calculated justify space measurements
27508
+ * @private
27481
27509
  */
27482
27510
  _applyBrowserJustifySpaces() {
27483
27511
  if (!this._textLines || !this.__charBounds) {
@@ -27500,23 +27528,59 @@
27500
27528
  const lineBounds = this.__charBounds[lineIndex];
27501
27529
  const lineSpaceWidths = spaceWidths[lineIndex];
27502
27530
  let spaceIndex = 0;
27531
+ let accumulatedSpace = 0;
27532
+ const isRtl = this.direction === 'rtl';
27533
+ const spaceDiffs = [];
27534
+
27535
+ // First, calculate the difference for each space
27503
27536
  for (let charIndex = 0; charIndex < line.length; charIndex++) {
27504
27537
  if (/\s/.test(line[charIndex]) && spaceIndex < lineSpaceWidths.length) {
27505
- const expandedWidth = lineSpaceWidths[spaceIndex];
27506
- if (lineBounds[charIndex]) {
27507
- const oldWidth = lineBounds[charIndex].width;
27508
- lineBounds[charIndex].width = expandedWidth;
27509
- console.log(`🔤 Line ${lineIndex} space ${spaceIndex}: ${oldWidth.toFixed(1)}px -> ${expandedWidth.toFixed(1)}px`);
27538
+ const charBound = lineBounds[charIndex];
27539
+ if (charBound) {
27540
+ spaceDiffs.push(lineSpaceWidths[spaceIndex] - charBound.width);
27510
27541
  }
27511
27542
  spaceIndex++;
27512
27543
  }
27513
27544
  }
27545
+ spaceIndex = 0;
27546
+ let remainingDiff = spaceDiffs.reduce((a, b) => a + b, 0);
27547
+ for (let charIndex = 0; charIndex < line.length; charIndex++) {
27548
+ const charBound = lineBounds[charIndex];
27549
+ if (!charBound) continue;
27550
+ if (isRtl) {
27551
+ charBound.left += remainingDiff;
27552
+ } else {
27553
+ charBound.left += accumulatedSpace;
27554
+ }
27555
+ if (/\s/.test(line[charIndex]) && spaceIndex < spaceDiffs.length) {
27556
+ const diff = spaceDiffs[spaceIndex];
27557
+ const oldWidth = charBound.width;
27558
+ charBound.width += diff;
27559
+ charBound.kernedWidth += diff;
27560
+ console.log(`🔤 Line ${lineIndex} space ${spaceIndex}: ${oldWidth.toFixed(1)}px -> ${charBound.width.toFixed(1)}px`);
27561
+ if (isRtl) {
27562
+ remainingDiff -= diff;
27563
+ } else {
27564
+ accumulatedSpace += diff;
27565
+ }
27566
+ spaceIndex++;
27567
+ }
27568
+ }
27569
+ // also need to update the last charBound
27570
+ const lastBound = lineBounds[line.length];
27571
+ if (lastBound) {
27572
+ if (isRtl) {
27573
+ lastBound.left += remainingDiff;
27574
+ } else {
27575
+ lastBound.left += accumulatedSpace;
27576
+ }
27577
+ }
27514
27578
  });
27515
27579
  }
27516
27580
 
27517
- /**
27518
- * Fallback to default Fabric wrapping
27519
- * @private
27581
+ /**
27582
+ * Fallback to default Fabric wrapping
27583
+ * @private
27520
27584
  */
27521
27585
  _splitTextIntoLinesDefault(text) {
27522
27586
  const newText = super._splitTextIntoLines(text),
@@ -27548,12 +27612,12 @@
27548
27612
  }
27549
27613
  }
27550
27614
 
27551
- /**
27552
- * Initialize event listeners for safety snap functionality
27553
- * @private
27615
+ /**
27616
+ * Initialize event listeners for safety snap functionality
27617
+ * @private
27554
27618
  */
27555
27619
  initializeEventListeners() {
27556
- var _this$canvas4;
27620
+ var _this$canvas5;
27557
27621
  // Track which side is being used for resize to handle position compensation
27558
27622
  let resizeOrigin = null;
27559
27623
 
@@ -27584,7 +27648,7 @@
27584
27648
  });
27585
27649
 
27586
27650
  // Also listen to canvas-level modified event as backup
27587
- (_this$canvas4 = this.canvas) === null || _this$canvas4 === void 0 || _this$canvas4.on('object:modified', e => {
27651
+ (_this$canvas5 = this.canvas) === null || _this$canvas5 === void 0 || _this$canvas5.on('object:modified', e => {
27588
27652
  if (e.target === this) {
27589
27653
  const currentResizeOrigin = resizeOrigin; // Capture the value before reset
27590
27654
  setTimeout(() => this.safetySnapWidth(currentResizeOrigin), 10);
@@ -27593,12 +27657,12 @@
27593
27657
  });
27594
27658
  }
27595
27659
 
27596
- /**
27597
- * Safety snap to prevent glyph clipping after manual resize.
27598
- * Similar to Polotno - checks if any glyphs are too close to edges
27599
- * and automatically expands width if needed.
27600
- * @private
27601
- * @param resizeOrigin - Which side was used for resizing ('left' or 'right')
27660
+ /**
27661
+ * Safety snap to prevent glyph clipping after manual resize.
27662
+ * Similar to Polotno - checks if any glyphs are too close to edges
27663
+ * and automatically expands width if needed.
27664
+ * @private
27665
+ * @param resizeOrigin - Which side was used for resizing ('left' or 'right')
27602
27666
  */
27603
27667
  safetySnapWidth(resizeOrigin) {
27604
27668
  // For Textbox objects, we always want to check for clipping regardless of isWrapping flag
@@ -27628,7 +27692,7 @@
27628
27692
  const safetyThreshold = 2; // px - very subtle trigger
27629
27693
 
27630
27694
  if (maxRequiredWidth > this.width - safetyThreshold) {
27631
- var _this$canvas5;
27695
+ var _this$canvas6;
27632
27696
  // Set width to exactly what's needed + minimal safety margin
27633
27697
  const newWidth = maxRequiredWidth + 1; // Add just 1px safety margin
27634
27698
 
@@ -27661,13 +27725,13 @@
27661
27725
  this.__overlayEditor.refresh();
27662
27726
  }, 0);
27663
27727
  }
27664
- (_this$canvas5 = this.canvas) === null || _this$canvas5 === void 0 || _this$canvas5.requestRenderAll();
27728
+ (_this$canvas6 = this.canvas) === null || _this$canvas6 === void 0 || _this$canvas6.requestRenderAll();
27665
27729
  }
27666
27730
  }
27667
27731
 
27668
- /**
27669
- * Fix character selection mismatch after JSON loading for browser-wrapped fonts
27670
- * @private
27732
+ /**
27733
+ * Fix character selection mismatch after JSON loading for browser-wrapped fonts
27734
+ * @private
27671
27735
  */
27672
27736
  _fixCharacterMappingAfterJsonLoad() {
27673
27737
  if (this._usingBrowserWrapping) {
@@ -27687,9 +27751,9 @@
27687
27751
  }
27688
27752
  }
27689
27753
 
27690
- /**
27691
- * Force complete textbox re-initialization (useful after JSON loading)
27692
- * Overrides Text version with Textbox-specific logic
27754
+ /**
27755
+ * Force complete textbox re-initialization (useful after JSON loading)
27756
+ * Overrides Text version with Textbox-specific logic
27693
27757
  */
27694
27758
  forceTextReinitialization() {
27695
27759
  console.log('🔄 Force reinitializing Textbox object');
@@ -27712,7 +27776,7 @@
27712
27776
  // Double-check that justify was applied by checking space widths
27713
27777
  if (this.textAlign.includes('justify') && this.__charBounds) {
27714
27778
  setTimeout(() => {
27715
- var _this$canvas6;
27779
+ var _this$canvas7;
27716
27780
  // Verify justify was applied by checking if space widths vary
27717
27781
  let hasVariableSpaces = false;
27718
27782
  this.__charBounds.forEach((lineBounds, i) => {
@@ -27741,36 +27805,36 @@
27741
27805
  this.height = this.calcTextHeight();
27742
27806
  console.log(`🔧 JUSTIFY: Used calcTextHeight: ${this.height}px`);
27743
27807
  }
27744
- (_this$canvas6 = this.canvas) === null || _this$canvas6 === void 0 || _this$canvas6.requestRenderAll();
27808
+ (_this$canvas7 = this.canvas) === null || _this$canvas7 === void 0 || _this$canvas7.requestRenderAll();
27745
27809
  }, 10);
27746
27810
  }
27747
27811
  }
27748
27812
 
27749
- /**
27750
- * Returns object representation of an instance
27751
- * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
27752
- * @return {Object} object representation of an instance
27813
+ /**
27814
+ * Returns object representation of an instance
27815
+ * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
27816
+ * @return {Object} object representation of an instance
27753
27817
  */
27754
27818
  toObject() {
27755
27819
  let propertiesToInclude = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
27756
27820
  return super.toObject(['minWidth', 'splitByGrapheme', ...propertiesToInclude]);
27757
27821
  }
27758
27822
  }
27759
- /**
27760
- * Minimum width of textbox, in pixels.
27761
- * @type Number
27823
+ /**
27824
+ * Minimum width of textbox, in pixels.
27825
+ * @type Number
27762
27826
  */
27763
- /**
27764
- * Minimum calculated width of a textbox, in pixels.
27765
- * fixed to 2 so that an empty textbox cannot go to 0
27766
- * and is still selectable without text.
27767
- * @type Number
27827
+ /**
27828
+ * Minimum calculated width of a textbox, in pixels.
27829
+ * fixed to 2 so that an empty textbox cannot go to 0
27830
+ * and is still selectable without text.
27831
+ * @type Number
27768
27832
  */
27769
- /**
27770
- * Use this boolean property in order to split strings that have no white space concept.
27771
- * this is a cheap way to help with chinese/japanese
27772
- * @type Boolean
27773
- * @since 2.6.0
27833
+ /**
27834
+ * Use this boolean property in order to split strings that have no white space concept.
27835
+ * this is a cheap way to help with chinese/japanese
27836
+ * @type Boolean
27837
+ * @since 2.6.0
27774
27838
  */
27775
27839
  _defineProperty(Textbox, "type", 'Textbox');
27776
27840
  _defineProperty(Textbox, "textLayoutProperties", [...IText.textLayoutProperties, 'width']);