@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.
- package/0 +0 -0
- package/debug/{konva → konva-master}/CHANGELOG.md +2 -1
- package/debug/{konva → konva-master}/README.md +7 -3
- package/debug/{konva → konva-master}/package.json +1 -1
- package/debug/{konva → konva-master}/release.sh +1 -4
- package/debug/{konva → konva-master}/src/Canvas.ts +37 -0
- package/debug/{konva → konva-master}/src/shapes/Text.ts +2 -2
- package/dist/index.js +2198 -272
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/index.min.mjs +1 -1
- package/dist/index.min.mjs.map +1 -1
- package/dist/index.mjs +2198 -272
- package/dist/index.mjs.map +1 -1
- package/dist/index.node.cjs +2198 -272
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.mjs +2198 -272
- package/dist/index.node.mjs.map +1 -1
- package/dist/package.json.min.mjs +1 -1
- package/dist/package.json.mjs +1 -1
- package/dist/src/shapes/Line.d.ts +33 -86
- package/dist/src/shapes/Line.d.ts.map +1 -1
- package/dist/src/shapes/Line.min.mjs +1 -1
- package/dist/src/shapes/Line.min.mjs.map +1 -1
- package/dist/src/shapes/Line.mjs +405 -159
- package/dist/src/shapes/Line.mjs.map +1 -1
- package/dist/src/shapes/Polyline.d.ts +7 -0
- package/dist/src/shapes/Polyline.d.ts.map +1 -1
- package/dist/src/shapes/Polyline.min.mjs +1 -1
- package/dist/src/shapes/Polyline.min.mjs.map +1 -1
- package/dist/src/shapes/Polyline.mjs +48 -16
- package/dist/src/shapes/Polyline.mjs.map +1 -1
- package/dist/src/shapes/Text/Text.d.ts +19 -0
- package/dist/src/shapes/Text/Text.d.ts.map +1 -1
- package/dist/src/shapes/Text/Text.min.mjs +1 -1
- package/dist/src/shapes/Text/Text.min.mjs.map +1 -1
- package/dist/src/shapes/Text/Text.mjs +302 -16
- package/dist/src/shapes/Text/Text.mjs.map +1 -1
- package/dist/src/shapes/Textbox.d.ts +56 -1
- package/dist/src/shapes/Textbox.d.ts.map +1 -1
- package/dist/src/shapes/Textbox.min.mjs +1 -1
- package/dist/src/shapes/Textbox.min.mjs.map +1 -1
- package/dist/src/shapes/Textbox.mjs +633 -11
- package/dist/src/shapes/Textbox.mjs.map +1 -1
- package/dist/src/shapes/Triangle.d.ts +27 -2
- package/dist/src/shapes/Triangle.d.ts.map +1 -1
- package/dist/src/shapes/Triangle.min.mjs +1 -1
- package/dist/src/shapes/Triangle.min.mjs.map +1 -1
- package/dist/src/shapes/Triangle.mjs +72 -12
- package/dist/src/shapes/Triangle.mjs.map +1 -1
- package/dist/src/text/examples/arabicTextExample.d.ts +60 -0
- package/dist/src/text/examples/arabicTextExample.d.ts.map +1 -0
- package/dist/src/text/measure.d.ts +9 -0
- package/dist/src/text/measure.d.ts.map +1 -1
- package/dist/src/text/measure.min.mjs +1 -1
- package/dist/src/text/measure.min.mjs.map +1 -1
- package/dist/src/text/measure.mjs +175 -4
- package/dist/src/text/measure.mjs.map +1 -1
- package/dist/src/text/overlayEditor.d.ts +8 -0
- package/dist/src/text/overlayEditor.d.ts.map +1 -1
- package/dist/src/text/overlayEditor.min.mjs +1 -1
- package/dist/src/text/overlayEditor.min.mjs.map +1 -1
- package/dist/src/text/overlayEditor.mjs +395 -56
- package/dist/src/text/overlayEditor.mjs.map +1 -1
- package/dist/src/text/scriptUtils.d.ts +142 -0
- package/dist/src/text/scriptUtils.d.ts.map +1 -0
- package/dist/src/text/scriptUtils.min.mjs +2 -0
- package/dist/src/text/scriptUtils.min.mjs.map +1 -0
- package/dist/src/text/scriptUtils.mjs +212 -0
- package/dist/src/text/scriptUtils.mjs.map +1 -0
- package/dist/src/util/misc/cornerRadius.d.ts +70 -0
- package/dist/src/util/misc/cornerRadius.d.ts.map +1 -0
- package/dist/src/util/misc/cornerRadius.min.mjs +2 -0
- package/dist/src/util/misc/cornerRadius.min.mjs.map +1 -0
- package/dist/src/util/misc/cornerRadius.mjs +181 -0
- package/dist/src/util/misc/cornerRadius.mjs.map +1 -0
- package/dist-extensions/src/shapes/CustomLine.d.ts +10 -0
- package/dist-extensions/src/shapes/CustomLine.d.ts.map +1 -0
- package/dist-extensions/src/shapes/Line.d.ts +33 -86
- package/dist-extensions/src/shapes/Line.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Polyline.d.ts +7 -0
- package/dist-extensions/src/shapes/Polyline.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Text/Text.d.ts +19 -0
- package/dist-extensions/src/shapes/Text/Text.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Textbox.d.ts +56 -1
- package/dist-extensions/src/shapes/Textbox.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Triangle.d.ts +27 -2
- package/dist-extensions/src/shapes/Triangle.d.ts.map +1 -1
- package/dist-extensions/src/text/measure.d.ts +9 -0
- package/dist-extensions/src/text/measure.d.ts.map +1 -1
- package/dist-extensions/src/text/overlayEditor.d.ts +8 -0
- package/dist-extensions/src/text/overlayEditor.d.ts.map +1 -1
- package/dist-extensions/src/text/scriptUtils.d.ts +142 -0
- package/dist-extensions/src/text/scriptUtils.d.ts.map +1 -0
- package/dist-extensions/src/util/misc/cornerRadius.d.ts +70 -0
- package/dist-extensions/src/util/misc/cornerRadius.d.ts.map +1 -0
- package/fabric-test-editor.html +3552 -0
- package/fabric-test2.html +647 -0
- package/fabric.ts +182 -182
- package/fonts/STV Bold.ttf +0 -0
- package/fonts/STV Light.ttf +0 -0
- package/fonts/STV Regular.ttf +0 -0
- package/package.json +164 -164
- package/src/shapes/Line.ts +484 -157
- package/src/shapes/Polyline.ts +70 -29
- package/src/shapes/Text/Text.ts +317 -19
- package/src/shapes/Textbox.ts +663 -12
- package/src/shapes/Triangle.spec.ts +76 -0
- package/src/shapes/Triangle.ts +85 -15
- package/src/text/measure.ts +200 -50
- package/src/text/overlayEditor.ts +504 -94
- package/src/util/misc/cornerRadius.spec.ts +141 -0
- package/src/util/misc/cornerRadius.ts +269 -0
- /package/debug/{konva → konva-master}/LICENSE +0 -0
- /package/debug/{konva → konva-master}/gulpfile.mjs +0 -0
- /package/debug/{konva → konva-master}/resources/doc-includes/ContainerParams.txt +0 -0
- /package/debug/{konva → konva-master}/resources/doc-includes/NodeParams.txt +0 -0
- /package/debug/{konva → konva-master}/resources/doc-includes/ShapeParams.txt +0 -0
- /package/debug/{konva → konva-master}/resources/jsdoc.conf.json +0 -0
- /package/debug/{konva → konva-master}/rollup.config.mjs +0 -0
- /package/debug/{konva → konva-master}/src/Animation.ts +0 -0
- /package/debug/{konva → konva-master}/src/BezierFunctions.ts +0 -0
- /package/debug/{konva → konva-master}/src/Container.ts +0 -0
- /package/debug/{konva → konva-master}/src/Context.ts +0 -0
- /package/debug/{konva → konva-master}/src/Core.ts +0 -0
- /package/debug/{konva → konva-master}/src/DragAndDrop.ts +0 -0
- /package/debug/{konva → konva-master}/src/Factory.ts +0 -0
- /package/debug/{konva → konva-master}/src/FastLayer.ts +0 -0
- /package/debug/{konva → konva-master}/src/Global.ts +0 -0
- /package/debug/{konva → konva-master}/src/Group.ts +0 -0
- /package/debug/{konva → konva-master}/src/Layer.ts +0 -0
- /package/debug/{konva → konva-master}/src/Node.ts +0 -0
- /package/debug/{konva → konva-master}/src/PointerEvents.ts +0 -0
- /package/debug/{konva → konva-master}/src/Shape.ts +0 -0
- /package/debug/{konva → konva-master}/src/Stage.ts +0 -0
- /package/debug/{konva → konva-master}/src/Tween.ts +0 -0
- /package/debug/{konva → konva-master}/src/Util.ts +0 -0
- /package/debug/{konva → konva-master}/src/Validators.ts +0 -0
- /package/debug/{konva → konva-master}/src/_CoreInternals.ts +0 -0
- /package/debug/{konva → konva-master}/src/_FullInternals.ts +0 -0
- /package/debug/{konva → konva-master}/src/canvas-backend.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Blur.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Brighten.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Brightness.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Contrast.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Emboss.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Enhance.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Grayscale.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/HSL.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/HSV.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Invert.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Kaleidoscope.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Mask.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Noise.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Pixelate.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Posterize.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/RGB.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/RGBA.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Sepia.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Solarize.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Threshold.ts +0 -0
- /package/debug/{konva → konva-master}/src/index.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Arc.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Arrow.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Circle.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Ellipse.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Image.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Label.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Line.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Path.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Rect.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/RegularPolygon.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Ring.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Sprite.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Star.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/TextPath.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Transformer.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Wedge.ts +0 -0
- /package/debug/{konva → konva-master}/src/skia-backend.ts +0 -0
- /package/debug/{konva → konva-master}/src/types.ts +0 -0
- /package/debug/{konva → konva-master}/tsconfig.json +0 -0
- /package/debug/{konva → konva-master}/tsconfig.test.json +0 -0
package/src/shapes/Polyline.ts
CHANGED
|
@@ -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
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
const
|
|
368
|
-
|
|
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
|
-
|
|
411
|
+
|
|
371
412
|
this._renderPaintInOrder(ctx);
|
|
372
413
|
}
|
|
373
414
|
|
package/src/shapes/Text/Text.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|