@nasser-sw/fabric 7.0.0-beta1 → 7.0.1-beta10

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.
Files changed (183) hide show
  1. package/0 +0 -0
  2. package/debug/{konva → konva-master}/CHANGELOG.md +2 -1
  3. package/debug/{konva → konva-master}/README.md +7 -3
  4. package/debug/{konva → konva-master}/package.json +1 -1
  5. package/debug/{konva → konva-master}/release.sh +1 -4
  6. package/debug/{konva → konva-master}/src/Canvas.ts +37 -0
  7. package/debug/{konva → konva-master}/src/shapes/Text.ts +2 -2
  8. package/dist/index.js +2198 -272
  9. package/dist/index.js.map +1 -1
  10. package/dist/index.min.js +1 -1
  11. package/dist/index.min.js.map +1 -1
  12. package/dist/index.min.mjs +1 -1
  13. package/dist/index.min.mjs.map +1 -1
  14. package/dist/index.mjs +2198 -272
  15. package/dist/index.mjs.map +1 -1
  16. package/dist/index.node.cjs +2198 -272
  17. package/dist/index.node.cjs.map +1 -1
  18. package/dist/index.node.mjs +2198 -272
  19. package/dist/index.node.mjs.map +1 -1
  20. package/dist/package.json.min.mjs +1 -1
  21. package/dist/package.json.mjs +1 -1
  22. package/dist/src/shapes/Line.d.ts +33 -86
  23. package/dist/src/shapes/Line.d.ts.map +1 -1
  24. package/dist/src/shapes/Line.min.mjs +1 -1
  25. package/dist/src/shapes/Line.min.mjs.map +1 -1
  26. package/dist/src/shapes/Line.mjs +405 -159
  27. package/dist/src/shapes/Line.mjs.map +1 -1
  28. package/dist/src/shapes/Polyline.d.ts +7 -0
  29. package/dist/src/shapes/Polyline.d.ts.map +1 -1
  30. package/dist/src/shapes/Polyline.min.mjs +1 -1
  31. package/dist/src/shapes/Polyline.min.mjs.map +1 -1
  32. package/dist/src/shapes/Polyline.mjs +48 -16
  33. package/dist/src/shapes/Polyline.mjs.map +1 -1
  34. package/dist/src/shapes/Text/Text.d.ts +19 -0
  35. package/dist/src/shapes/Text/Text.d.ts.map +1 -1
  36. package/dist/src/shapes/Text/Text.min.mjs +1 -1
  37. package/dist/src/shapes/Text/Text.min.mjs.map +1 -1
  38. package/dist/src/shapes/Text/Text.mjs +302 -16
  39. package/dist/src/shapes/Text/Text.mjs.map +1 -1
  40. package/dist/src/shapes/Textbox.d.ts +56 -1
  41. package/dist/src/shapes/Textbox.d.ts.map +1 -1
  42. package/dist/src/shapes/Textbox.min.mjs +1 -1
  43. package/dist/src/shapes/Textbox.min.mjs.map +1 -1
  44. package/dist/src/shapes/Textbox.mjs +633 -11
  45. package/dist/src/shapes/Textbox.mjs.map +1 -1
  46. package/dist/src/shapes/Triangle.d.ts +27 -2
  47. package/dist/src/shapes/Triangle.d.ts.map +1 -1
  48. package/dist/src/shapes/Triangle.min.mjs +1 -1
  49. package/dist/src/shapes/Triangle.min.mjs.map +1 -1
  50. package/dist/src/shapes/Triangle.mjs +72 -12
  51. package/dist/src/shapes/Triangle.mjs.map +1 -1
  52. package/dist/src/text/examples/arabicTextExample.d.ts +60 -0
  53. package/dist/src/text/examples/arabicTextExample.d.ts.map +1 -0
  54. package/dist/src/text/measure.d.ts +9 -0
  55. package/dist/src/text/measure.d.ts.map +1 -1
  56. package/dist/src/text/measure.min.mjs +1 -1
  57. package/dist/src/text/measure.min.mjs.map +1 -1
  58. package/dist/src/text/measure.mjs +175 -4
  59. package/dist/src/text/measure.mjs.map +1 -1
  60. package/dist/src/text/overlayEditor.d.ts +8 -0
  61. package/dist/src/text/overlayEditor.d.ts.map +1 -1
  62. package/dist/src/text/overlayEditor.min.mjs +1 -1
  63. package/dist/src/text/overlayEditor.min.mjs.map +1 -1
  64. package/dist/src/text/overlayEditor.mjs +395 -56
  65. package/dist/src/text/overlayEditor.mjs.map +1 -1
  66. package/dist/src/text/scriptUtils.d.ts +142 -0
  67. package/dist/src/text/scriptUtils.d.ts.map +1 -0
  68. package/dist/src/text/scriptUtils.min.mjs +2 -0
  69. package/dist/src/text/scriptUtils.min.mjs.map +1 -0
  70. package/dist/src/text/scriptUtils.mjs +212 -0
  71. package/dist/src/text/scriptUtils.mjs.map +1 -0
  72. package/dist/src/util/misc/cornerRadius.d.ts +70 -0
  73. package/dist/src/util/misc/cornerRadius.d.ts.map +1 -0
  74. package/dist/src/util/misc/cornerRadius.min.mjs +2 -0
  75. package/dist/src/util/misc/cornerRadius.min.mjs.map +1 -0
  76. package/dist/src/util/misc/cornerRadius.mjs +181 -0
  77. package/dist/src/util/misc/cornerRadius.mjs.map +1 -0
  78. package/dist-extensions/src/shapes/CustomLine.d.ts +10 -0
  79. package/dist-extensions/src/shapes/CustomLine.d.ts.map +1 -0
  80. package/dist-extensions/src/shapes/Line.d.ts +33 -86
  81. package/dist-extensions/src/shapes/Line.d.ts.map +1 -1
  82. package/dist-extensions/src/shapes/Polyline.d.ts +7 -0
  83. package/dist-extensions/src/shapes/Polyline.d.ts.map +1 -1
  84. package/dist-extensions/src/shapes/Text/Text.d.ts +19 -0
  85. package/dist-extensions/src/shapes/Text/Text.d.ts.map +1 -1
  86. package/dist-extensions/src/shapes/Textbox.d.ts +56 -1
  87. package/dist-extensions/src/shapes/Textbox.d.ts.map +1 -1
  88. package/dist-extensions/src/shapes/Triangle.d.ts +27 -2
  89. package/dist-extensions/src/shapes/Triangle.d.ts.map +1 -1
  90. package/dist-extensions/src/text/measure.d.ts +9 -0
  91. package/dist-extensions/src/text/measure.d.ts.map +1 -1
  92. package/dist-extensions/src/text/overlayEditor.d.ts +8 -0
  93. package/dist-extensions/src/text/overlayEditor.d.ts.map +1 -1
  94. package/dist-extensions/src/text/scriptUtils.d.ts +142 -0
  95. package/dist-extensions/src/text/scriptUtils.d.ts.map +1 -0
  96. package/dist-extensions/src/util/misc/cornerRadius.d.ts +70 -0
  97. package/dist-extensions/src/util/misc/cornerRadius.d.ts.map +1 -0
  98. package/fabric-test-editor.html +3552 -0
  99. package/fabric-test2.html +647 -0
  100. package/fabric.ts +182 -182
  101. package/fonts/STV Bold.ttf +0 -0
  102. package/fonts/STV Light.ttf +0 -0
  103. package/fonts/STV Regular.ttf +0 -0
  104. package/package.json +164 -164
  105. package/src/shapes/Line.ts +484 -157
  106. package/src/shapes/Polyline.ts +70 -29
  107. package/src/shapes/Text/Text.ts +317 -19
  108. package/src/shapes/Textbox.ts +663 -12
  109. package/src/shapes/Triangle.spec.ts +76 -0
  110. package/src/shapes/Triangle.ts +85 -15
  111. package/src/text/measure.ts +200 -50
  112. package/src/text/overlayEditor.ts +504 -94
  113. package/src/util/misc/cornerRadius.spec.ts +141 -0
  114. package/src/util/misc/cornerRadius.ts +269 -0
  115. /package/debug/{konva → konva-master}/LICENSE +0 -0
  116. /package/debug/{konva → konva-master}/gulpfile.mjs +0 -0
  117. /package/debug/{konva → konva-master}/resources/doc-includes/ContainerParams.txt +0 -0
  118. /package/debug/{konva → konva-master}/resources/doc-includes/NodeParams.txt +0 -0
  119. /package/debug/{konva → konva-master}/resources/doc-includes/ShapeParams.txt +0 -0
  120. /package/debug/{konva → konva-master}/resources/jsdoc.conf.json +0 -0
  121. /package/debug/{konva → konva-master}/rollup.config.mjs +0 -0
  122. /package/debug/{konva → konva-master}/src/Animation.ts +0 -0
  123. /package/debug/{konva → konva-master}/src/BezierFunctions.ts +0 -0
  124. /package/debug/{konva → konva-master}/src/Container.ts +0 -0
  125. /package/debug/{konva → konva-master}/src/Context.ts +0 -0
  126. /package/debug/{konva → konva-master}/src/Core.ts +0 -0
  127. /package/debug/{konva → konva-master}/src/DragAndDrop.ts +0 -0
  128. /package/debug/{konva → konva-master}/src/Factory.ts +0 -0
  129. /package/debug/{konva → konva-master}/src/FastLayer.ts +0 -0
  130. /package/debug/{konva → konva-master}/src/Global.ts +0 -0
  131. /package/debug/{konva → konva-master}/src/Group.ts +0 -0
  132. /package/debug/{konva → konva-master}/src/Layer.ts +0 -0
  133. /package/debug/{konva → konva-master}/src/Node.ts +0 -0
  134. /package/debug/{konva → konva-master}/src/PointerEvents.ts +0 -0
  135. /package/debug/{konva → konva-master}/src/Shape.ts +0 -0
  136. /package/debug/{konva → konva-master}/src/Stage.ts +0 -0
  137. /package/debug/{konva → konva-master}/src/Tween.ts +0 -0
  138. /package/debug/{konva → konva-master}/src/Util.ts +0 -0
  139. /package/debug/{konva → konva-master}/src/Validators.ts +0 -0
  140. /package/debug/{konva → konva-master}/src/_CoreInternals.ts +0 -0
  141. /package/debug/{konva → konva-master}/src/_FullInternals.ts +0 -0
  142. /package/debug/{konva → konva-master}/src/canvas-backend.ts +0 -0
  143. /package/debug/{konva → konva-master}/src/filters/Blur.ts +0 -0
  144. /package/debug/{konva → konva-master}/src/filters/Brighten.ts +0 -0
  145. /package/debug/{konva → konva-master}/src/filters/Brightness.ts +0 -0
  146. /package/debug/{konva → konva-master}/src/filters/Contrast.ts +0 -0
  147. /package/debug/{konva → konva-master}/src/filters/Emboss.ts +0 -0
  148. /package/debug/{konva → konva-master}/src/filters/Enhance.ts +0 -0
  149. /package/debug/{konva → konva-master}/src/filters/Grayscale.ts +0 -0
  150. /package/debug/{konva → konva-master}/src/filters/HSL.ts +0 -0
  151. /package/debug/{konva → konva-master}/src/filters/HSV.ts +0 -0
  152. /package/debug/{konva → konva-master}/src/filters/Invert.ts +0 -0
  153. /package/debug/{konva → konva-master}/src/filters/Kaleidoscope.ts +0 -0
  154. /package/debug/{konva → konva-master}/src/filters/Mask.ts +0 -0
  155. /package/debug/{konva → konva-master}/src/filters/Noise.ts +0 -0
  156. /package/debug/{konva → konva-master}/src/filters/Pixelate.ts +0 -0
  157. /package/debug/{konva → konva-master}/src/filters/Posterize.ts +0 -0
  158. /package/debug/{konva → konva-master}/src/filters/RGB.ts +0 -0
  159. /package/debug/{konva → konva-master}/src/filters/RGBA.ts +0 -0
  160. /package/debug/{konva → konva-master}/src/filters/Sepia.ts +0 -0
  161. /package/debug/{konva → konva-master}/src/filters/Solarize.ts +0 -0
  162. /package/debug/{konva → konva-master}/src/filters/Threshold.ts +0 -0
  163. /package/debug/{konva → konva-master}/src/index.ts +0 -0
  164. /package/debug/{konva → konva-master}/src/shapes/Arc.ts +0 -0
  165. /package/debug/{konva → konva-master}/src/shapes/Arrow.ts +0 -0
  166. /package/debug/{konva → konva-master}/src/shapes/Circle.ts +0 -0
  167. /package/debug/{konva → konva-master}/src/shapes/Ellipse.ts +0 -0
  168. /package/debug/{konva → konva-master}/src/shapes/Image.ts +0 -0
  169. /package/debug/{konva → konva-master}/src/shapes/Label.ts +0 -0
  170. /package/debug/{konva → konva-master}/src/shapes/Line.ts +0 -0
  171. /package/debug/{konva → konva-master}/src/shapes/Path.ts +0 -0
  172. /package/debug/{konva → konva-master}/src/shapes/Rect.ts +0 -0
  173. /package/debug/{konva → konva-master}/src/shapes/RegularPolygon.ts +0 -0
  174. /package/debug/{konva → konva-master}/src/shapes/Ring.ts +0 -0
  175. /package/debug/{konva → konva-master}/src/shapes/Sprite.ts +0 -0
  176. /package/debug/{konva → konva-master}/src/shapes/Star.ts +0 -0
  177. /package/debug/{konva → konva-master}/src/shapes/TextPath.ts +0 -0
  178. /package/debug/{konva → konva-master}/src/shapes/Transformer.ts +0 -0
  179. /package/debug/{konva → konva-master}/src/shapes/Wedge.ts +0 -0
  180. /package/debug/{konva → konva-master}/src/skia-backend.ts +0 -0
  181. /package/debug/{konva → konva-master}/src/types.ts +0 -0
  182. /package/debug/{konva → konva-master}/tsconfig.json +0 -0
  183. /package/debug/{konva → konva-master}/tsconfig.test.json +0 -0
@@ -25,16 +25,23 @@ import {
25
25
  TOP,
26
26
  } from '../constants';
27
27
  import type { CSSRules } from '../parser/typedefs';
28
+ import {
29
+ applyCornerRadiusToPolygon,
30
+ renderRoundedPolygon,
31
+ generateRoundedPolygonPath,
32
+ } from '../util/misc/cornerRadius';
28
33
 
29
34
  export const polylineDefaultValues: Partial<TClassProperties<Polyline>> = {
30
35
  /**
31
36
  * @deprecated transient option soon to be removed in favor of a different design
32
37
  */
33
38
  exactBoundingBox: false,
39
+ cornerRadius: 0,
34
40
  };
35
41
 
36
42
  export interface SerializedPolylineProps extends SerializedObjectProps {
37
43
  points: XY[];
44
+ cornerRadius: number;
38
45
  }
39
46
 
40
47
  export class Polyline<
@@ -59,6 +66,13 @@ export class Polyline<
59
66
  */
60
67
  declare exactBoundingBox: boolean;
61
68
 
69
+ /**
70
+ * Corner radius for rounded corners
71
+ * @type Number
72
+ * @default 0
73
+ */
74
+ declare cornerRadius: number;
75
+
62
76
  declare private initialized: true | undefined;
63
77
 
64
78
  static ownDefaults = polylineDefaultValues;
@@ -91,7 +105,7 @@ export class Polyline<
91
105
 
92
106
  declare strokeOffset: Point;
93
107
 
94
- static cacheProperties = [...cacheProperties, 'points'];
108
+ static cacheProperties = [...cacheProperties, 'points', 'cornerRadius'];
95
109
 
96
110
  strokeDiff: Point;
97
111
 
@@ -312,7 +326,7 @@ export class Polyline<
312
326
  K extends keyof T = never,
313
327
  >(propertiesToInclude: K[] = []): Pick<T, K> & SProps {
314
328
  return {
315
- ...super.toObject(propertiesToInclude),
329
+ ...super.toObject(['cornerRadius', ...propertiesToInclude]),
316
330
  points: this.points.map(({ x, y }) => ({ x, y })),
317
331
  };
318
332
  }
@@ -323,28 +337,42 @@ export class Polyline<
323
337
  * of the instance
324
338
  */
325
339
  _toSVG() {
326
- const points = [],
327
- diffX = this.pathOffset.x,
328
- diffY = this.pathOffset.y,
329
- NUM_FRACTION_DIGITS = config.NUM_FRACTION_DIGITS;
330
-
331
- for (let i = 0, len = this.points.length; i < len; i++) {
332
- points.push(
333
- toFixed(this.points[i].x - diffX, NUM_FRACTION_DIGITS),
334
- ',',
335
- toFixed(this.points[i].y - diffY, NUM_FRACTION_DIGITS),
336
- ' ',
337
- );
340
+ if (this.cornerRadius > 0 && this.points.length >= 3) {
341
+ // Generate rounded polygon/polyline as path
342
+ const diffX = this.pathOffset.x;
343
+ const diffY = this.pathOffset.y;
344
+ const adjustedPoints = this.points.map(point => ({
345
+ x: point.x - diffX,
346
+ y: point.y - diffY,
347
+ }));
348
+ const roundedCorners = applyCornerRadiusToPolygon(adjustedPoints, this.cornerRadius);
349
+ const pathData = generateRoundedPolygonPath(roundedCorners, !this.isOpen());
350
+ return ['<path ', 'COMMON_PARTS', `d="${pathData}" />\n`];
351
+ } else {
352
+ // Original sharp corners implementation
353
+ const points = [];
354
+ const diffX = this.pathOffset.x;
355
+ const diffY = this.pathOffset.y;
356
+ const NUM_FRACTION_DIGITS = config.NUM_FRACTION_DIGITS;
357
+
358
+ for (let i = 0, len = this.points.length; i < len; i++) {
359
+ points.push(
360
+ toFixed(this.points[i].x - diffX, NUM_FRACTION_DIGITS),
361
+ ',',
362
+ toFixed(this.points[i].y - diffY, NUM_FRACTION_DIGITS),
363
+ ' ',
364
+ );
365
+ }
366
+ return [
367
+ `<${
368
+ (this.constructor as typeof Polyline).type.toLowerCase() as
369
+ | 'polyline'
370
+ | 'polygon'
371
+ } `,
372
+ 'COMMON_PARTS',
373
+ `points="${points.join('')}" />\n`,
374
+ ];
338
375
  }
339
- return [
340
- `<${
341
- (this.constructor as typeof Polyline).type.toLowerCase() as
342
- | 'polyline'
343
- | 'polygon'
344
- } `,
345
- 'COMMON_PARTS',
346
- `points="${points.join('')}" />\n`,
347
- ];
348
376
  }
349
377
 
350
378
  /**
@@ -361,13 +389,26 @@ export class Polyline<
361
389
  // NaN comes from parseFloat of a empty string in parser
362
390
  return;
363
391
  }
364
- ctx.beginPath();
365
- ctx.moveTo(this.points[0].x - x, this.points[0].y - y);
366
- for (let i = 0; i < len; i++) {
367
- const point = this.points[i];
368
- ctx.lineTo(point.x - x, point.y - y);
392
+
393
+ if (this.cornerRadius > 0 && len >= 3) {
394
+ // Render with rounded corners
395
+ const adjustedPoints = this.points.map(point => ({
396
+ x: point.x - x,
397
+ y: point.y - y,
398
+ }));
399
+ const roundedCorners = applyCornerRadiusToPolygon(adjustedPoints, this.cornerRadius);
400
+ renderRoundedPolygon(ctx, roundedCorners, !this.isOpen());
401
+ } else {
402
+ // Original sharp corners implementation
403
+ ctx.beginPath();
404
+ ctx.moveTo(this.points[0].x - x, this.points[0].y - y);
405
+ for (let i = 0; i < len; i++) {
406
+ const point = this.points[i];
407
+ ctx.lineTo(point.x - x, point.y - y);
408
+ }
409
+ !this.isOpen() && ctx.closePath();
369
410
  }
370
- !this.isOpen() && ctx.closePath();
411
+
371
412
  this._renderPaintInOrder(ctx);
372
413
  }
373
414
 
@@ -560,6 +560,15 @@ export class FabricText<
560
560
  * Does not return dimensions.
561
561
  */
562
562
  initDimensions(): void {
563
+ // Check if font is ready for accurate measurements
564
+ // Only block initialization if it's a critical font loading situation
565
+ const fontReady = this._isFontReady();
566
+ if (!fontReady && !this.initialized) {
567
+ // Only schedule font loading on first initialization
568
+ this._scheduleInitAfterFontLoad();
569
+ // Continue with fallback measurements for now
570
+ }
571
+
563
572
  // Use advanced layout if enabled
564
573
  if (this.enableAdvancedLayout && !this.path) {
565
574
  return this.initDimensionsAdvanced();
@@ -578,7 +587,20 @@ export class FabricText<
578
587
  }
579
588
  if (this.textAlign.includes(JUSTIFY)) {
580
589
  // once text is measured we need to make space fatter to make justified text.
581
- this.enlargeSpaces();
590
+ // Ensure __charBounds exists before calling enlargeSpaces
591
+ if (this.__charBounds && this.__charBounds.length > 0) {
592
+ this.enlargeSpaces();
593
+ } else {
594
+ console.warn('⚠️ __charBounds not ready for justify alignment, deferring enlargeSpaces');
595
+ // Defer the justify calculation until the next frame
596
+ setTimeout(() => {
597
+ if (this.__charBounds && this.__charBounds.length > 0 && this.enlargeSpaces) {
598
+ console.log('🔧 Applying deferred justify alignment');
599
+ this.enlargeSpaces();
600
+ this.canvas?.requestRenderAll();
601
+ }
602
+ }, 0);
603
+ }
582
604
  }
583
605
  }
584
606
 
@@ -593,9 +615,11 @@ export class FabricText<
593
615
  line,
594
616
  charBound,
595
617
  spaces;
618
+ const isRtl = this.direction === 'rtl';
619
+
596
620
  for (let i = 0, len = this._textLines.length; i < len; i++) {
597
621
  if (
598
- this.textAlign !== JUSTIFY &&
622
+ !this.textAlign.includes('justify') &&
599
623
  (i === len - 1 || this.isEndOfWrapping(i))
600
624
  ) {
601
625
  continue;
@@ -609,15 +633,60 @@ export class FabricText<
609
633
  ) {
610
634
  numberOfSpaces = spaces.length;
611
635
  diffSpace = (this.width - currentLineWidth) / numberOfSpaces;
612
- for (let j = 0; j <= line.length; j++) {
613
- charBound = this.__charBounds[i][j];
614
- if (this._reSpaceAndTab.test(line[j])) {
615
- charBound.width += diffSpace;
616
- charBound.kernedWidth += diffSpace;
617
- charBound.left += accumulatedSpace;
618
- accumulatedSpace += diffSpace;
619
- } else {
620
- charBound.left += accumulatedSpace;
636
+
637
+ console.log(`🔧 EnlargeSpaces Line ${i}:`);
638
+ console.log(` Current width: ${currentLineWidth}, Target: ${this.width}`);
639
+ console.log(` Spaces: ${numberOfSpaces}, diffSpace: ${diffSpace.toFixed(2)}`);
640
+
641
+ if (isRtl) {
642
+ // For RTL text, we need to redistribute spaces while maintaining correct visual order
643
+ // The key insight is that RTL text is stored in logical order but displayed in visual order
644
+ // We need to adjust bounds to account for the visual positioning
645
+
646
+ // First, collect all space positions
647
+ const spaceIndices = [];
648
+ for (let j = 0; j < line.length; j++) {
649
+ if (this._reSpaceAndTab.test(line[j])) {
650
+ spaceIndices.push(j);
651
+ }
652
+ }
653
+
654
+ // Calculate total expansion
655
+ const totalExpansion = diffSpace * numberOfSpaces;
656
+
657
+ // For RTL, we need to work backwards through the visual positions
658
+ // but still update logical positions correctly
659
+ let spaceCount = 0;
660
+ for (let j = 0; j <= line.length; j++) {
661
+ charBound = this.__charBounds[i][j];
662
+ if (charBound) {
663
+ if (this._reSpaceAndTab.test(line[j])) {
664
+ charBound.width += diffSpace;
665
+ charBound.kernedWidth += diffSpace;
666
+ spaceCount++;
667
+ }
668
+
669
+ // For RTL, shift all characters to the right by the total expansion
670
+ // minus the expansion that comes after this character
671
+ const remainingSpaces = numberOfSpaces - spaceCount;
672
+ const shiftAmount = remainingSpaces * diffSpace;
673
+ charBound.left += shiftAmount;
674
+ }
675
+ }
676
+ } else {
677
+ // LTR processing (original logic)
678
+ for (let j = 0; j <= line.length; j++) {
679
+ charBound = this.__charBounds[i][j];
680
+ if (charBound) {
681
+ if (this._reSpaceAndTab.test(line[j])) {
682
+ charBound.width += diffSpace;
683
+ charBound.kernedWidth += diffSpace;
684
+ charBound.left += accumulatedSpace;
685
+ accumulatedSpace += diffSpace;
686
+ } else {
687
+ charBound.left += accumulatedSpace;
688
+ }
689
+ }
621
690
  }
622
691
  }
623
692
  }
@@ -697,6 +766,17 @@ export class FabricText<
697
766
  // Convert layout to legacy format for compatibility
698
767
  this._convertLayoutToLegacyFormat(layout);
699
768
 
769
+ // Ensure justify alignment is properly applied for compatibility with legacy rendering
770
+ if (this.textAlign.includes(JUSTIFY)) {
771
+ // Force enlarge spaces after advanced layout calculation
772
+ setTimeout(() => {
773
+ if (this.enlargeSpaces) {
774
+ this.enlargeSpaces();
775
+ this.canvas?.renderAll();
776
+ }
777
+ }, 0);
778
+ }
779
+
700
780
  this.dirty = true;
701
781
  }
702
782
 
@@ -1376,7 +1456,15 @@ export class FabricText<
1376
1456
  if (currentDirection !== this.direction) {
1377
1457
  ctx.canvas.setAttribute('dir', isLtr ? 'ltr' : 'rtl');
1378
1458
  ctx.direction = isLtr ? 'ltr' : 'rtl';
1379
- ctx.textAlign = isLtr ? LEFT : RIGHT;
1459
+
1460
+ // For justify alignments, we need to set the correct canvas text alignment
1461
+ // This is crucial for RTL text to render in the correct order
1462
+ if (isJustify) {
1463
+ // Justify uses LEFT alignment as a base, letting the character positioning handle justification
1464
+ ctx.textAlign = LEFT;
1465
+ } else {
1466
+ ctx.textAlign = isLtr ? LEFT : RIGHT;
1467
+ }
1380
1468
  }
1381
1469
  top -= (lineHeight * this._fontSizeFraction) / this.lineHeight;
1382
1470
  if (shortCut) {
@@ -1665,14 +1753,26 @@ export class FabricText<
1665
1753
  direction = this.direction,
1666
1754
  isEndOfWrapping = this.isEndOfWrapping(lineIndex);
1667
1755
  let leftOffset = 0;
1668
- if (
1756
+
1757
+ // Handle justify alignments (excluding last lines and wrapped line ends)
1758
+ const isJustifyLine =
1669
1759
  textAlign === JUSTIFY ||
1670
1760
  (textAlign === JUSTIFY_CENTER && !isEndOfWrapping) ||
1671
1761
  (textAlign === JUSTIFY_RIGHT && !isEndOfWrapping) ||
1672
- (textAlign === JUSTIFY_LEFT && !isEndOfWrapping)
1673
- ) {
1674
- return 0;
1762
+ (textAlign === JUSTIFY_LEFT && !isEndOfWrapping);
1763
+
1764
+ if (isJustifyLine) {
1765
+ // Justify lines should start at the left edge for LTR and right edge for RTL
1766
+ // The space distribution is handled by enlargeSpaces()
1767
+ if (direction === 'rtl') {
1768
+ // For RTL justify, we need to account for the line being right-aligned
1769
+ return 0;
1770
+ } else {
1771
+ return 0;
1772
+ }
1675
1773
  }
1774
+
1775
+ // Handle non-justify alignments
1676
1776
  if (textAlign === CENTER) {
1677
1777
  leftOffset = lineDiff / 2;
1678
1778
  }
@@ -1685,6 +1785,8 @@ export class FabricText<
1685
1785
  if (textAlign === JUSTIFY_RIGHT) {
1686
1786
  leftOffset = lineDiff;
1687
1787
  }
1788
+
1789
+ // Apply RTL adjustments for non-justify alignments
1688
1790
  if (direction === 'rtl') {
1689
1791
  if (
1690
1792
  textAlign === RIGHT ||
@@ -1893,13 +1995,27 @@ export class FabricText<
1893
1995
  > = {},
1894
1996
  forMeasuring?: boolean,
1895
1997
  ): string {
1896
- const parsedFontFamily =
1998
+ let parsedFontFamily =
1897
1999
  fontFamily.includes("'") ||
1898
2000
  fontFamily.includes('"') ||
1899
2001
  fontFamily.includes(',') ||
1900
2002
  FabricText.genericFonts.includes(fontFamily.toLowerCase())
1901
2003
  ? fontFamily
1902
2004
  : `"${fontFamily}"`;
2005
+
2006
+ // For fonts like STV that don't support English/Latin characters,
2007
+ // add fallback fonts for consistent rendering of unsupported characters
2008
+ // Only add fallbacks during actual rendering, not for measurements
2009
+ if (!forMeasuring && // Only during rendering, not measuring
2010
+ !fontFamily.includes(',') && // Don't add fallbacks if already has them
2011
+ (fontFamily.toLowerCase().includes('stv') ||
2012
+ fontFamily.toLowerCase().includes('arabic') ||
2013
+ fontFamily.toLowerCase().includes('naskh') ||
2014
+ fontFamily.toLowerCase().includes('kufi'))) {
2015
+ // Add fallback fonts for unsupported characters (spaces, punctuation, etc.)
2016
+ parsedFontFamily = `${parsedFontFamily}, "Arial Unicode MS", Arial, sans-serif`;
2017
+ }
2018
+
1903
2019
  return [
1904
2020
  fontStyle,
1905
2021
  fontWeight,
@@ -1953,7 +2069,13 @@ export class FabricText<
1953
2069
  newLine = ['\n'];
1954
2070
  let newText: string[] = [];
1955
2071
  for (let i = 0; i < lines.length; i++) {
1956
- newLines[i] = this.graphemeSplit(lines[i]);
2072
+ // Use BiDi-aware grapheme splitting for RTL text
2073
+ if (this.direction === 'rtl' || this._containsArabicText(lines[i])) {
2074
+ newLines[i] = segmentGraphemes(lines[i]);
2075
+ console.log(`🔤 BiDi-aware split line ${i}: "${lines[i]}" -> [${newLines[i].join(', ')}]`);
2076
+ } else {
2077
+ newLines[i] = this.graphemeSplit(lines[i]);
2078
+ }
1957
2079
  newText = newText.concat(newLines[i], newLine);
1958
2080
  }
1959
2081
  newText.pop();
@@ -1964,6 +2086,14 @@ export class FabricText<
1964
2086
  graphemeLines: newLines,
1965
2087
  };
1966
2088
  }
2089
+
2090
+ /**
2091
+ * Check if text contains Arabic characters
2092
+ * @private
2093
+ */
2094
+ _containsArabicText(text: string): boolean {
2095
+ return /[\u0600-\u06FF\u0750-\u077F\uFB50-\uFDFF\uFE70-\uFEFF]/.test(text);
2096
+ }
1967
2097
 
1968
2098
  /**
1969
2099
  * Returns object representation of an instance
@@ -2135,6 +2265,87 @@ export class FabricText<
2135
2265
 
2136
2266
  /* _FROM_SVG_END_ */
2137
2267
 
2268
+ /**
2269
+ * Check if the font is ready for accurate measurements
2270
+ * @private
2271
+ */
2272
+ _isFontReady(): boolean {
2273
+ if (typeof document === 'undefined' || !('fonts' in document)) {
2274
+ return true; // Assume ready in non-browser environments
2275
+ }
2276
+
2277
+ try {
2278
+ return document.fonts.check(`${this.fontSize}px ${this.fontFamily}`);
2279
+ } catch (e) {
2280
+ return true; // Fallback to assuming ready if check fails
2281
+ }
2282
+ }
2283
+
2284
+ /**
2285
+ * Schedule re-initialization after font loads
2286
+ * @private
2287
+ */
2288
+ _scheduleInitAfterFontLoad(): void {
2289
+ if (typeof document === 'undefined' || !('fonts' in document)) {
2290
+ return;
2291
+ }
2292
+
2293
+ // Only schedule if not already waiting
2294
+ if ((this as any)._fontLoadScheduled) {
2295
+ return;
2296
+ }
2297
+ (this as any)._fontLoadScheduled = true;
2298
+
2299
+ const fontSpec = `${this.fontSize}px ${this.fontFamily}`;
2300
+ document.fonts.load(fontSpec).then(() => {
2301
+ (this as any)._fontLoadScheduled = false;
2302
+ // Re-initialize dimensions with proper font metrics
2303
+ this.initDimensions();
2304
+
2305
+ // Extra step for justify alignment after font loading
2306
+ if (this.textAlign && this.textAlign.includes(JUSTIFY)) {
2307
+ setTimeout(() => {
2308
+ if (this.enlargeSpaces) {
2309
+ this.enlargeSpaces();
2310
+ }
2311
+ this.canvas?.requestRenderAll();
2312
+ }, 10);
2313
+ } else {
2314
+ this.canvas?.requestRenderAll();
2315
+ }
2316
+ }).catch(() => {
2317
+ (this as any)._fontLoadScheduled = false;
2318
+ });
2319
+ }
2320
+
2321
+ /**
2322
+ * Force complete text re-initialization (useful after JSON loading)
2323
+ */
2324
+ forceTextReinitialization(): void {
2325
+ console.log('🔄 Force reinitializing text object');
2326
+
2327
+ // Clear all caches
2328
+ this._clearCache();
2329
+ this.dirty = true;
2330
+
2331
+ // Force text splitting to rebuild internal structures
2332
+ this._splitText();
2333
+
2334
+ // Re-initialize dimensions
2335
+ this.initDimensions();
2336
+
2337
+ // Special handling for justify alignment
2338
+ if (this.textAlign && this.textAlign.includes(JUSTIFY)) {
2339
+ // Ensure justify is applied after dimensions are set
2340
+ setTimeout(() => {
2341
+ if (this.__charBounds && this.__charBounds.length > 0 && this.enlargeSpaces) {
2342
+ this.enlargeSpaces();
2343
+ this.canvas?.requestRenderAll();
2344
+ }
2345
+ }, 10);
2346
+ }
2347
+ }
2348
+
2138
2349
  /**
2139
2350
  * Returns FabricText instance from an object representation
2140
2351
  * @param {Object} object plain js Object to create an instance from
@@ -2152,7 +2363,94 @@ export class FabricText<
2152
2363
  {
2153
2364
  extraParam: 'text',
2154
2365
  },
2155
- );
2366
+ ).then((textObject: S) => {
2367
+ // Ensure text object is properly initialized after JSON deserialization
2368
+ // This is critical for justify alignment and other text layout features
2369
+ textObject.initialized = true;
2370
+
2371
+ // Force reinitialization to ensure proper layout
2372
+ if (textObject._clearCache) {
2373
+ textObject._clearCache();
2374
+ }
2375
+ textObject.dirty = true;
2376
+
2377
+ // Check if we need to wait for font loading (especially for custom fonts like STV)
2378
+ const fontSpec = `${textObject.fontSize}px ${textObject.fontFamily}`;
2379
+
2380
+ // For custom fonts, ensure they're loaded before initializing dimensions
2381
+ if (typeof document !== 'undefined' && 'fonts' in document && textObject.fontFamily !== 'Arial' && textObject.fontFamily !== 'Times New Roman') {
2382
+ return document.fonts.load(fontSpec).then(() => {
2383
+ console.log(`🔤 Font loaded for JSON object: ${fontSpec}`);
2384
+ // Ensure initialized flag is set again (in case constructor reset it)
2385
+ textObject.initialized = true;
2386
+
2387
+ // Special handling for STV fonts which have measurement issues
2388
+ const isStvFont = textObject.fontFamily?.toLowerCase().includes('stv');
2389
+ if (isStvFont) {
2390
+ console.log(`🔤 STV font detected, using enhanced reinitialization`);
2391
+
2392
+ // Clear all cached state that might interfere with browser wrapping
2393
+ (textObject as any)._browserWrapCache = null;
2394
+ (textObject as any)._lastDimensionState = null;
2395
+ (textObject as any)._browserWrapInitialized = false;
2396
+ console.log(`🔤 STV font: Cleared all cached states for fresh initialization`);
2397
+
2398
+ // Force browser wrapping flag for STV fonts
2399
+ (textObject as any)._usingBrowserWrapping = true;
2400
+ console.log(`🔤 STV font: Forcing browser wrapping flag during JSON load`);
2401
+
2402
+ // Multiple initialization attempts for STV fonts
2403
+ const reinitWithDelay = (attempt: number) => {
2404
+ if ((textObject as any).forceTextReinitialization) {
2405
+ (textObject as any).forceTextReinitialization();
2406
+ } else {
2407
+ textObject.initDimensions();
2408
+ }
2409
+
2410
+ // Check if width is still problematic after initialization
2411
+ if (textObject.width < 50 && attempt < 3) {
2412
+ console.log(`🔤 STV font width still ${textObject.width}px, retrying in ${100 * attempt}ms (attempt ${attempt + 1}/3)`);
2413
+ setTimeout(() => reinitWithDelay(attempt + 1), 100 * attempt);
2414
+ }
2415
+ };
2416
+ reinitWithDelay(0);
2417
+ } else {
2418
+ // Use specialized reinitialization for Textbox objects
2419
+ if ((textObject as any).forceTextReinitialization) {
2420
+ console.log(`🔤 Using Textbox specialized reinitialization`);
2421
+ (textObject as any).forceTextReinitialization();
2422
+ } else {
2423
+ // Reinitialize dimensions with proper font metrics
2424
+ textObject.initDimensions();
2425
+ }
2426
+ }
2427
+ return textObject;
2428
+ }).catch(() => {
2429
+ console.warn(`⚠️ Font loading failed for ${fontSpec}, proceeding with fallback`);
2430
+ // Ensure initialized flag is set again
2431
+ textObject.initialized = true;
2432
+
2433
+ // Still initialize dimensions even if font loading fails
2434
+ if ((textObject as any).forceTextReinitialization) {
2435
+ (textObject as any).forceTextReinitialization();
2436
+ } else {
2437
+ textObject.initDimensions();
2438
+ }
2439
+ return textObject;
2440
+ });
2441
+ } else {
2442
+ // Standard fonts - ensure initialized and use appropriate method
2443
+ textObject.initialized = true;
2444
+
2445
+ if ((textObject as any).forceTextReinitialization) {
2446
+ console.log(`🔤 Using Textbox specialized reinitialization for standard font`);
2447
+ (textObject as any).forceTextReinitialization();
2448
+ } else {
2449
+ textObject.initDimensions();
2450
+ }
2451
+ return textObject;
2452
+ }
2453
+ });
2156
2454
  }
2157
2455
  }
2158
2456