@nasser-sw/fabric 7.0.1-beta7 → 7.0.1-beta9
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/debug/konva-master/CHANGELOG.md +1475 -0
- package/debug/konva-master/LICENSE +22 -0
- package/debug/konva-master/README.md +209 -0
- package/debug/konva-master/gulpfile.mjs +110 -0
- package/debug/konva-master/package.json +139 -0
- package/debug/konva-master/release.sh +62 -0
- package/debug/konva-master/resources/doc-includes/ContainerParams.txt +6 -0
- package/debug/konva-master/resources/doc-includes/NodeParams.txt +20 -0
- package/debug/konva-master/resources/doc-includes/ShapeParams.txt +53 -0
- package/debug/konva-master/resources/jsdoc.conf.json +28 -0
- package/debug/konva-master/rollup.config.mjs +32 -0
- package/debug/konva-master/src/Animation.ts +237 -0
- package/debug/konva-master/src/BezierFunctions.ts +826 -0
- package/debug/konva-master/src/Canvas.ts +230 -0
- package/debug/konva-master/src/Container.ts +649 -0
- package/debug/konva-master/src/Context.ts +1017 -0
- package/debug/konva-master/src/Core.ts +5 -0
- package/debug/konva-master/src/DragAndDrop.ts +173 -0
- package/debug/konva-master/src/Factory.ts +246 -0
- package/debug/konva-master/src/FastLayer.ts +29 -0
- package/debug/konva-master/src/Global.ts +210 -0
- package/debug/konva-master/src/Group.ts +31 -0
- package/debug/konva-master/src/Layer.ts +546 -0
- package/debug/konva-master/src/Node.ts +3477 -0
- package/debug/konva-master/src/PointerEvents.ts +67 -0
- package/debug/konva-master/src/Shape.ts +2081 -0
- package/debug/konva-master/src/Stage.ts +1000 -0
- package/debug/konva-master/src/Tween.ts +811 -0
- package/debug/konva-master/src/Util.ts +1123 -0
- package/debug/konva-master/src/Validators.ts +210 -0
- package/debug/konva-master/src/_CoreInternals.ts +85 -0
- package/debug/konva-master/src/_FullInternals.ts +171 -0
- package/debug/konva-master/src/canvas-backend.ts +36 -0
- package/debug/konva-master/src/filters/Blur.ts +388 -0
- package/debug/konva-master/src/filters/Brighten.ts +48 -0
- package/debug/konva-master/src/filters/Brightness.ts +30 -0
- package/debug/konva-master/src/filters/Contrast.ts +75 -0
- package/debug/konva-master/src/filters/Emboss.ts +207 -0
- package/debug/konva-master/src/filters/Enhance.ts +154 -0
- package/debug/konva-master/src/filters/Grayscale.ts +25 -0
- package/debug/konva-master/src/filters/HSL.ts +108 -0
- package/debug/konva-master/src/filters/HSV.ts +106 -0
- package/debug/konva-master/src/filters/Invert.ts +23 -0
- package/debug/konva-master/src/filters/Kaleidoscope.ts +274 -0
- package/debug/konva-master/src/filters/Mask.ts +220 -0
- package/debug/konva-master/src/filters/Noise.ts +44 -0
- package/debug/konva-master/src/filters/Pixelate.ts +107 -0
- package/debug/konva-master/src/filters/Posterize.ts +46 -0
- package/debug/konva-master/src/filters/RGB.ts +82 -0
- package/debug/konva-master/src/filters/RGBA.ts +103 -0
- package/debug/konva-master/src/filters/Sepia.ts +27 -0
- package/debug/konva-master/src/filters/Solarize.ts +29 -0
- package/debug/konva-master/src/filters/Threshold.ts +44 -0
- package/debug/konva-master/src/index.ts +3 -0
- package/debug/konva-master/src/shapes/Arc.ts +176 -0
- package/debug/konva-master/src/shapes/Arrow.ts +231 -0
- package/debug/konva-master/src/shapes/Circle.ts +76 -0
- package/debug/konva-master/src/shapes/Ellipse.ts +121 -0
- package/debug/konva-master/src/shapes/Image.ts +319 -0
- package/debug/konva-master/src/shapes/Label.ts +386 -0
- package/debug/konva-master/src/shapes/Line.ts +364 -0
- package/debug/konva-master/src/shapes/Path.ts +1013 -0
- package/debug/konva-master/src/shapes/Rect.ts +79 -0
- package/debug/konva-master/src/shapes/RegularPolygon.ts +167 -0
- package/debug/konva-master/src/shapes/Ring.ts +94 -0
- package/debug/konva-master/src/shapes/Sprite.ts +370 -0
- package/debug/konva-master/src/shapes/Star.ts +125 -0
- package/debug/konva-master/src/shapes/Text.ts +1065 -0
- package/debug/konva-master/src/shapes/TextPath.ts +583 -0
- package/debug/konva-master/src/shapes/Transformer.ts +1889 -0
- package/debug/konva-master/src/shapes/Wedge.ts +129 -0
- package/debug/konva-master/src/skia-backend.ts +35 -0
- package/debug/konva-master/src/types.ts +84 -0
- package/debug/konva-master/tsconfig.json +31 -0
- package/debug/konva-master/tsconfig.test.json +7 -0
- package/dist/index.js +977 -29
- 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 +977 -29
- package/dist/index.mjs.map +1 -1
- package/dist/index.node.cjs +977 -29
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.mjs +977 -29
- 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 +1 -0
- 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 +63 -6
- package/dist/src/shapes/Line.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 +238 -4
- package/dist/src/shapes/Text/Text.mjs.map +1 -1
- package/dist/src/shapes/Textbox.d.ts +38 -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 +497 -15
- package/dist/src/shapes/Textbox.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.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 +7 -0
- 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-extensions/src/shapes/Line.d.ts +1 -0
- package/dist-extensions/src/shapes/Line.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 +38 -1
- package/dist-extensions/src/shapes/Textbox.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.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/fabric-test-editor.html +2401 -46
- package/fabric-test2.html +43 -0
- package/fonts/STV Bold.ttf +0 -0
- package/fonts/STV Light.ttf +0 -0
- package/fonts/STV Regular.ttf +0 -0
- package/package.json +1 -1
- package/src/shapes/Line.ts +132 -46
- package/src/shapes/Text/Text.ts +238 -5
- package/src/shapes/Textbox.ts +521 -11
- package/src/text/measure.ts +200 -50
- package/src/text/overlayEditor.ts +7 -0
package/dist/index.node.mjs
CHANGED
|
@@ -410,7 +410,7 @@ class Cache {
|
|
|
410
410
|
}
|
|
411
411
|
const cache = new Cache();
|
|
412
412
|
|
|
413
|
-
var version = "7.0.1-
|
|
413
|
+
var version = "7.0.1-beta8";
|
|
414
414
|
|
|
415
415
|
// use this syntax so babel plugin see this import here
|
|
416
416
|
const VERSION = version;
|
|
@@ -17636,6 +17636,7 @@ class Line extends FabricObject {
|
|
|
17636
17636
|
_defineProperty(this, "hitStrokeWidth", 'auto');
|
|
17637
17637
|
_defineProperty(this, "_updatingEndpoints", false);
|
|
17638
17638
|
_defineProperty(this, "_useEndpointCoords", true);
|
|
17639
|
+
_defineProperty(this, "_exportingSVG", false);
|
|
17639
17640
|
this.setOptions(options);
|
|
17640
17641
|
this.x1 = x1;
|
|
17641
17642
|
this.x2 = x2;
|
|
@@ -17847,6 +17848,14 @@ class Line extends FabricObject {
|
|
|
17847
17848
|
this.x2 = newX;
|
|
17848
17849
|
this.y2 = newY;
|
|
17849
17850
|
}
|
|
17851
|
+
|
|
17852
|
+
// Update gradient coordinates if stroke is a gradient (but not during SVG export)
|
|
17853
|
+
if (this.stroke instanceof Gradient && !this._exportingSVG) {
|
|
17854
|
+
this.stroke.coords.x1 = this.x1;
|
|
17855
|
+
this.stroke.coords.y1 = this.y1;
|
|
17856
|
+
this.stroke.coords.x2 = this.x2;
|
|
17857
|
+
this.stroke.coords.y2 = this.y2;
|
|
17858
|
+
}
|
|
17850
17859
|
this.dirty = true;
|
|
17851
17860
|
this.setCoords();
|
|
17852
17861
|
(_this$canvas3 = this.canvas) === null || _this$canvas3 === void 0 || _this$canvas3.requestRenderAll();
|
|
@@ -17917,6 +17926,14 @@ class Line extends FabricObject {
|
|
|
17917
17926
|
if (coordProps.includes(key)) {
|
|
17918
17927
|
this._setWidthHeight();
|
|
17919
17928
|
this.dirty = true;
|
|
17929
|
+
|
|
17930
|
+
// Update gradient coordinates if stroke is a gradient (but not during SVG export)
|
|
17931
|
+
if (this.stroke instanceof Gradient && !this._exportingSVG) {
|
|
17932
|
+
this.stroke.coords.x1 = this.x1;
|
|
17933
|
+
this.stroke.coords.y1 = this.y1;
|
|
17934
|
+
this.stroke.coords.x2 = this.x2;
|
|
17935
|
+
this.stroke.coords.y2 = this.y2;
|
|
17936
|
+
}
|
|
17920
17937
|
}
|
|
17921
17938
|
if ((key === 'left' || key === 'top') && this.canvas && !this._updatingEndpoints) {
|
|
17922
17939
|
const deltaX = this.left - oldLeft;
|
|
@@ -17927,6 +17944,14 @@ class Line extends FabricObject {
|
|
|
17927
17944
|
this.y1 += deltaY;
|
|
17928
17945
|
this.x2 += deltaX;
|
|
17929
17946
|
this.y2 += deltaY;
|
|
17947
|
+
|
|
17948
|
+
// Update gradient coordinates if stroke is a gradient
|
|
17949
|
+
if (this.stroke instanceof Gradient) {
|
|
17950
|
+
this.stroke.coords.x1 = this.x1;
|
|
17951
|
+
this.stroke.coords.y1 = this.y1;
|
|
17952
|
+
this.stroke.coords.x2 = this.x2;
|
|
17953
|
+
this.stroke.coords.y2 = this.y2;
|
|
17954
|
+
}
|
|
17930
17955
|
this._updatingEndpoints = false;
|
|
17931
17956
|
}
|
|
17932
17957
|
}
|
|
@@ -17940,17 +17965,23 @@ class Line extends FabricObject {
|
|
|
17940
17965
|
super.render(ctx);
|
|
17941
17966
|
}
|
|
17942
17967
|
_renderDirectly(ctx) {
|
|
17943
|
-
var _this$stroke;
|
|
17944
17968
|
if (!this.visible) return;
|
|
17945
17969
|
ctx.save();
|
|
17946
17970
|
ctx.globalAlpha = this.opacity;
|
|
17947
|
-
ctx.strokeStyle = ((_this$stroke = this.stroke) === null || _this$stroke === void 0 ? void 0 : _this$stroke.toString()) || '#000';
|
|
17948
17971
|
ctx.lineWidth = this.strokeWidth;
|
|
17949
17972
|
ctx.lineCap = this.strokeLineCap || 'butt';
|
|
17950
17973
|
ctx.beginPath();
|
|
17951
17974
|
ctx.moveTo(this.x1, this.y1);
|
|
17952
17975
|
ctx.lineTo(this.x2, this.y2);
|
|
17976
|
+
const origStrokeStyle = ctx.strokeStyle;
|
|
17977
|
+
if (isFiller(this.stroke)) {
|
|
17978
|
+
ctx.strokeStyle = this.stroke.toLive(ctx);
|
|
17979
|
+
} else {
|
|
17980
|
+
var _this$stroke;
|
|
17981
|
+
ctx.strokeStyle = ((_this$stroke = this.stroke) === null || _this$stroke === void 0 ? void 0 : _this$stroke.toString()) || '#000';
|
|
17982
|
+
}
|
|
17953
17983
|
ctx.stroke();
|
|
17984
|
+
ctx.strokeStyle = origStrokeStyle;
|
|
17954
17985
|
ctx.restore();
|
|
17955
17986
|
}
|
|
17956
17987
|
_render(ctx) {
|
|
@@ -18025,7 +18056,15 @@ class Line extends FabricObject {
|
|
|
18025
18056
|
_toSVG() {
|
|
18026
18057
|
if (this._useEndpointCoords) {
|
|
18027
18058
|
// Use absolute coordinates to bypass all Fabric.js transforms
|
|
18028
|
-
|
|
18059
|
+
// Handle gradients manually for proper SVG export
|
|
18060
|
+
let strokeAttr = '';
|
|
18061
|
+
if (this.stroke instanceof Gradient) {
|
|
18062
|
+
// Let Fabric.js handle gradient definition, but we'll use the reference
|
|
18063
|
+
strokeAttr = `stroke="url(#${this.stroke.id})"`;
|
|
18064
|
+
} else {
|
|
18065
|
+
strokeAttr = `stroke="${this.stroke || 'none'}"`;
|
|
18066
|
+
}
|
|
18067
|
+
return [`<line ${strokeAttr} stroke-width="${this.strokeWidth}" stroke-linecap="${this.strokeLineCap}" `, `stroke-dasharray="${this.strokeDashArray ? this.strokeDashArray.join(' ') : 'none'}" `, `stroke-dashoffset="${this.strokeDashOffset}" stroke-linejoin="${this.strokeLineJoin}" `, `stroke-miterlimit="${this.strokeMiterLimit}" fill="${this.fill || 'none'}" `, `fill-rule="${this.fillRule}" opacity="${this.opacity}" `, `x1="${this.x1}" y1="${this.y1}" x2="${this.x2}" y2="${this.y2}" />\n`];
|
|
18029
18068
|
} else {
|
|
18030
18069
|
// Use standard calcLinePoints for legacy mode
|
|
18031
18070
|
const {
|
|
@@ -18039,9 +18078,26 @@ class Line extends FabricObject {
|
|
|
18039
18078
|
}
|
|
18040
18079
|
toSVG(reviver) {
|
|
18041
18080
|
if (this._useEndpointCoords) {
|
|
18042
|
-
//
|
|
18043
|
-
|
|
18044
|
-
|
|
18081
|
+
// For endpoint coords, we need to bypass transforms but still allow gradients
|
|
18082
|
+
// Let's temporarily disable transforms during SVG generation
|
|
18083
|
+
const originalLeft = this.left;
|
|
18084
|
+
const originalTop = this.top;
|
|
18085
|
+
|
|
18086
|
+
// Set position to center of line for gradient calculation
|
|
18087
|
+
this.left = (this.x1 + this.x2) / 2;
|
|
18088
|
+
this.top = (this.y1 + this.y2) / 2;
|
|
18089
|
+
|
|
18090
|
+
// Get the SVG with standard system (for gradient handling)
|
|
18091
|
+
const standardSVG = super.toSVG(reviver);
|
|
18092
|
+
|
|
18093
|
+
// Restore original position
|
|
18094
|
+
this.left = originalLeft;
|
|
18095
|
+
this.top = originalTop;
|
|
18096
|
+
|
|
18097
|
+
// Extract gradient definition and clean up the line element
|
|
18098
|
+
// Remove the transform wrapper and update coordinates
|
|
18099
|
+
const cleanSVG = standardSVG.replace(/<g transform="[^"]*"[^>]*>/g, '').replace(/<\/g>/g, '').replace(/x1="[^"]*"/g, `x1="${this.x1}"`).replace(/y1="[^"]*"/g, `y1="${this.y1}"`).replace(/x2="[^"]*"/g, `x2="${this.x2}"`).replace(/y2="[^"]*"/g, `y2="${this.y2}"`);
|
|
18100
|
+
return cleanSVG;
|
|
18045
18101
|
}
|
|
18046
18102
|
// Use default behavior for legacy mode
|
|
18047
18103
|
return super.toSVG(reviver);
|
|
@@ -19273,6 +19329,97 @@ function measureGraphemeWithKerning(grapheme, previousGrapheme, options, ctx) {
|
|
|
19273
19329
|
};
|
|
19274
19330
|
}
|
|
19275
19331
|
|
|
19332
|
+
/**
|
|
19333
|
+
* Get a representative character for font metrics measurement
|
|
19334
|
+
* Uses canvas to test which scripts the font actually supports
|
|
19335
|
+
*/
|
|
19336
|
+
function getRepresentativeCharacter(fontFamily) {
|
|
19337
|
+
const context = getMeasurementContext();
|
|
19338
|
+
|
|
19339
|
+
// Wait for font to be ready if possible
|
|
19340
|
+
if (typeof document !== 'undefined' && 'fonts' in document) {
|
|
19341
|
+
try {
|
|
19342
|
+
// Check if font is ready, if not, use fallback immediately
|
|
19343
|
+
if (!document.fonts.check(`16px ${fontFamily}`)) {
|
|
19344
|
+
return 'M'; // Use safe fallback while font loads
|
|
19345
|
+
}
|
|
19346
|
+
} catch (e) {
|
|
19347
|
+
// Font check failed, use fallback
|
|
19348
|
+
return 'M';
|
|
19349
|
+
}
|
|
19350
|
+
}
|
|
19351
|
+
|
|
19352
|
+
// Test characters for different scripts
|
|
19353
|
+
const testChars = [{
|
|
19354
|
+
char: 'م',
|
|
19355
|
+
script: 'Arabic'
|
|
19356
|
+
},
|
|
19357
|
+
// Arabic
|
|
19358
|
+
{
|
|
19359
|
+
char: 'א',
|
|
19360
|
+
script: 'Hebrew'
|
|
19361
|
+
},
|
|
19362
|
+
// Hebrew
|
|
19363
|
+
{
|
|
19364
|
+
char: 'अ',
|
|
19365
|
+
script: 'Devanagari'
|
|
19366
|
+
},
|
|
19367
|
+
// Hindi/Sanskrit
|
|
19368
|
+
{
|
|
19369
|
+
char: 'ا',
|
|
19370
|
+
script: 'Urdu'
|
|
19371
|
+
},
|
|
19372
|
+
// Urdu
|
|
19373
|
+
{
|
|
19374
|
+
char: 'ک',
|
|
19375
|
+
script: 'Persian'
|
|
19376
|
+
},
|
|
19377
|
+
// Persian
|
|
19378
|
+
{
|
|
19379
|
+
char: 'த',
|
|
19380
|
+
script: 'Tamil'
|
|
19381
|
+
},
|
|
19382
|
+
// Tamil
|
|
19383
|
+
{
|
|
19384
|
+
char: 'ก',
|
|
19385
|
+
script: 'Thai'
|
|
19386
|
+
},
|
|
19387
|
+
// Thai
|
|
19388
|
+
{
|
|
19389
|
+
char: 'М',
|
|
19390
|
+
script: 'Cyrillic'
|
|
19391
|
+
},
|
|
19392
|
+
// Cyrillic
|
|
19393
|
+
{
|
|
19394
|
+
char: 'Ω',
|
|
19395
|
+
script: 'Greek'
|
|
19396
|
+
},
|
|
19397
|
+
// Greek
|
|
19398
|
+
{
|
|
19399
|
+
char: 'M',
|
|
19400
|
+
script: 'Latin'
|
|
19401
|
+
} // Latin (fallback)
|
|
19402
|
+
];
|
|
19403
|
+
|
|
19404
|
+
// Set the font
|
|
19405
|
+
context.font = `16px ${fontFamily}`;
|
|
19406
|
+
|
|
19407
|
+
// Test each character to see which ones render properly
|
|
19408
|
+
// Use a more robust width check to avoid false positives
|
|
19409
|
+
const fallbackWidth = context.measureText('M').width;
|
|
19410
|
+
for (const test of testChars) {
|
|
19411
|
+
const metrics = context.measureText(test.char);
|
|
19412
|
+
|
|
19413
|
+
// Character is valid if it has width and isn't just a fallback glyph
|
|
19414
|
+
if (metrics.width > 0 && Math.abs(metrics.width - fallbackWidth) > 0.1) {
|
|
19415
|
+
return test.char;
|
|
19416
|
+
}
|
|
19417
|
+
}
|
|
19418
|
+
|
|
19419
|
+
// Fallback to Latin 'M'
|
|
19420
|
+
return 'M';
|
|
19421
|
+
}
|
|
19422
|
+
|
|
19276
19423
|
/**
|
|
19277
19424
|
* Get font metrics for layout calculations
|
|
19278
19425
|
*/
|
|
@@ -19286,8 +19433,9 @@ function getFontMetrics(options) {
|
|
|
19286
19433
|
const context = getMeasurementContext();
|
|
19287
19434
|
applyFontStyle(context, options);
|
|
19288
19435
|
|
|
19289
|
-
// Use
|
|
19290
|
-
const
|
|
19436
|
+
// Use representative character based on font's primary script
|
|
19437
|
+
const sample = getRepresentativeCharacter(options.fontFamily);
|
|
19438
|
+
const metrics = context.measureText(sample);
|
|
19291
19439
|
const fontSize = options.fontSize;
|
|
19292
19440
|
|
|
19293
19441
|
// Calculate metrics with fallbacks
|
|
@@ -19339,7 +19487,11 @@ function getFontDeclaration(options) {
|
|
|
19339
19487
|
} = options;
|
|
19340
19488
|
|
|
19341
19489
|
// Normalize font family (add quotes if needed)
|
|
19342
|
-
|
|
19490
|
+
let normalizedFamily = fontFamily.includes(' ') && !fontFamily.includes('"') && !fontFamily.includes("'") ? `"${fontFamily}"` : fontFamily;
|
|
19491
|
+
|
|
19492
|
+
// Note: Font fallbacks are handled in the rendering phase only
|
|
19493
|
+
// to avoid affecting measurement calculations for text wrapping
|
|
19494
|
+
|
|
19343
19495
|
return `${fontStyle} ${fontWeight} ${fontSize}px ${normalizedFamily}`;
|
|
19344
19496
|
}
|
|
19345
19497
|
|
|
@@ -19491,6 +19643,81 @@ const measurementCache = new MeasurementCache();
|
|
|
19491
19643
|
const kerningCache = new KerningCache();
|
|
19492
19644
|
const fontMetricsCache = new FontMetricsCache();
|
|
19493
19645
|
|
|
19646
|
+
// Set up font loading listener to clear caches when fonts change
|
|
19647
|
+
if (typeof document !== 'undefined' && 'fonts' in document) {
|
|
19648
|
+
document.fonts.addEventListener('loadingdone', () => {
|
|
19649
|
+
// Clear all caches when fonts finish loading
|
|
19650
|
+
clearAllCaches();
|
|
19651
|
+
});
|
|
19652
|
+
}
|
|
19653
|
+
|
|
19654
|
+
/**
|
|
19655
|
+
* Clear all measurement caches
|
|
19656
|
+
*/
|
|
19657
|
+
function clearAllCaches() {
|
|
19658
|
+
measurementCache.clear();
|
|
19659
|
+
kerningCache.clear();
|
|
19660
|
+
fontMetricsCache.clear();
|
|
19661
|
+
}
|
|
19662
|
+
|
|
19663
|
+
/**
|
|
19664
|
+
* Detect if a font lacks English glyph support
|
|
19665
|
+
* These fonts should use browser-native measurement instead of Fabric's character-by-character measurement
|
|
19666
|
+
*/
|
|
19667
|
+
function fontLacksEnglishGlyphs(fontFamily) {
|
|
19668
|
+
if (typeof document === 'undefined') return false;
|
|
19669
|
+
|
|
19670
|
+
// Known fonts that lack English glyphs
|
|
19671
|
+
const knownNonEnglishFonts = ['stv', 'arabic', 'naskh', 'thuluth', 'kufi', 'diwani', 'nastaliq', 'kufic', 'hijazi', 'madinah', 'makkah'];
|
|
19672
|
+
const lowerFontFamily = fontFamily.toLowerCase();
|
|
19673
|
+
|
|
19674
|
+
// Check known list first
|
|
19675
|
+
if (knownNonEnglishFonts.some(font => lowerFontFamily.includes(font))) {
|
|
19676
|
+
return true;
|
|
19677
|
+
}
|
|
19678
|
+
|
|
19679
|
+
// Dynamic glyph support detection
|
|
19680
|
+
const context = getMeasurementContext();
|
|
19681
|
+
context.font = `16px ${fontFamily}`;
|
|
19682
|
+
|
|
19683
|
+
// Test English characters
|
|
19684
|
+
const englishChars = ['A', 'B', 'C', 'a', 'b', 'c', 'M', 'W'];
|
|
19685
|
+
const fallbackFont = 'Arial, sans-serif';
|
|
19686
|
+
|
|
19687
|
+
// Measure with target font
|
|
19688
|
+
const targetWidths = englishChars.map(char => context.measureText(char).width);
|
|
19689
|
+
|
|
19690
|
+
// Measure with fallback font
|
|
19691
|
+
context.font = `16px ${fallbackFont}`;
|
|
19692
|
+
const fallbackWidths = englishChars.map(char => context.measureText(char).width);
|
|
19693
|
+
|
|
19694
|
+
// If most measurements are identical, the font likely doesn't have English glyphs
|
|
19695
|
+
let identicalCount = 0;
|
|
19696
|
+
for (let i = 0; i < englishChars.length; i++) {
|
|
19697
|
+
if (Math.abs(targetWidths[i] - fallbackWidths[i]) < 0.5) {
|
|
19698
|
+
identicalCount++;
|
|
19699
|
+
}
|
|
19700
|
+
}
|
|
19701
|
+
const lacksSupportThreshold = englishChars.length * 0.7; // 70% identical = lacks support
|
|
19702
|
+
const lacksSupport = identicalCount >= lacksSupportThreshold;
|
|
19703
|
+
return lacksSupport;
|
|
19704
|
+
}
|
|
19705
|
+
|
|
19706
|
+
// Cache for font glyph detection results
|
|
19707
|
+
const fontGlyphCache = new Map();
|
|
19708
|
+
|
|
19709
|
+
/**
|
|
19710
|
+
* Cached version of font glyph detection
|
|
19711
|
+
*/
|
|
19712
|
+
function fontLacksEnglishGlyphsCached(fontFamily) {
|
|
19713
|
+
if (fontGlyphCache.has(fontFamily)) {
|
|
19714
|
+
return fontGlyphCache.get(fontFamily);
|
|
19715
|
+
}
|
|
19716
|
+
const result = fontLacksEnglishGlyphs(fontFamily);
|
|
19717
|
+
fontGlyphCache.set(fontFamily, result);
|
|
19718
|
+
return result;
|
|
19719
|
+
}
|
|
19720
|
+
|
|
19494
19721
|
/**
|
|
19495
19722
|
* Unicode and Internationalization Support
|
|
19496
19723
|
*
|
|
@@ -20674,6 +20901,15 @@ class FabricText extends StyledText {
|
|
|
20674
20901
|
* Does not return dimensions.
|
|
20675
20902
|
*/
|
|
20676
20903
|
initDimensions() {
|
|
20904
|
+
// Check if font is ready for accurate measurements
|
|
20905
|
+
// Only block initialization if it's a critical font loading situation
|
|
20906
|
+
const fontReady = this._isFontReady();
|
|
20907
|
+
if (!fontReady && !this.initialized) {
|
|
20908
|
+
// Only schedule font loading on first initialization
|
|
20909
|
+
this._scheduleInitAfterFontLoad();
|
|
20910
|
+
// Continue with fallback measurements for now
|
|
20911
|
+
}
|
|
20912
|
+
|
|
20677
20913
|
// Use advanced layout if enabled
|
|
20678
20914
|
if (this.enableAdvancedLayout && !this.path) {
|
|
20679
20915
|
return this.initDimensionsAdvanced();
|
|
@@ -20690,7 +20926,21 @@ class FabricText extends StyledText {
|
|
|
20690
20926
|
}
|
|
20691
20927
|
if (this.textAlign.includes(JUSTIFY)) {
|
|
20692
20928
|
// once text is measured we need to make space fatter to make justified text.
|
|
20693
|
-
|
|
20929
|
+
// Ensure __charBounds exists before calling enlargeSpaces
|
|
20930
|
+
if (this.__charBounds && this.__charBounds.length > 0) {
|
|
20931
|
+
this.enlargeSpaces();
|
|
20932
|
+
} else {
|
|
20933
|
+
console.warn('⚠️ __charBounds not ready for justify alignment, deferring enlargeSpaces');
|
|
20934
|
+
// Defer the justify calculation until the next frame
|
|
20935
|
+
setTimeout(() => {
|
|
20936
|
+
if (this.__charBounds && this.__charBounds.length > 0 && this.enlargeSpaces) {
|
|
20937
|
+
var _this$canvas;
|
|
20938
|
+
console.log('🔧 Applying deferred justify alignment');
|
|
20939
|
+
this.enlargeSpaces();
|
|
20940
|
+
(_this$canvas = this.canvas) === null || _this$canvas === void 0 || _this$canvas.requestRenderAll();
|
|
20941
|
+
}
|
|
20942
|
+
}, 0);
|
|
20943
|
+
}
|
|
20694
20944
|
}
|
|
20695
20945
|
}
|
|
20696
20946
|
|
|
@@ -20701,7 +20951,7 @@ class FabricText extends StyledText {
|
|
|
20701
20951
|
let diffSpace, currentLineWidth, numberOfSpaces, accumulatedSpace, line, charBound, spaces;
|
|
20702
20952
|
const isRtl = this.direction === 'rtl';
|
|
20703
20953
|
for (let i = 0, len = this._textLines.length; i < len; i++) {
|
|
20704
|
-
if (this.textAlign
|
|
20954
|
+
if (!this.textAlign.includes('justify') && (i === len - 1 || this.isEndOfWrapping(i))) {
|
|
20705
20955
|
continue;
|
|
20706
20956
|
}
|
|
20707
20957
|
accumulatedSpace = 0;
|
|
@@ -20710,6 +20960,9 @@ class FabricText extends StyledText {
|
|
|
20710
20960
|
if (currentLineWidth < this.width && (spaces = this.textLines[i].match(this._reSpacesAndTabs))) {
|
|
20711
20961
|
numberOfSpaces = spaces.length;
|
|
20712
20962
|
diffSpace = (this.width - currentLineWidth) / numberOfSpaces;
|
|
20963
|
+
console.log(`🔧 EnlargeSpaces Line ${i}:`);
|
|
20964
|
+
console.log(` Current width: ${currentLineWidth}, Target: ${this.width}`);
|
|
20965
|
+
console.log(` Spaces: ${numberOfSpaces}, diffSpace: ${diffSpace.toFixed(2)}`);
|
|
20713
20966
|
if (isRtl) {
|
|
20714
20967
|
for (let j = 0; j < line.length; j++) {
|
|
20715
20968
|
if (this._reSpaceAndTab.test(line[j])) ;
|
|
@@ -20825,6 +21078,18 @@ class FabricText extends StyledText {
|
|
|
20825
21078
|
|
|
20826
21079
|
// Convert layout to legacy format for compatibility
|
|
20827
21080
|
this._convertLayoutToLegacyFormat(layout);
|
|
21081
|
+
|
|
21082
|
+
// Ensure justify alignment is properly applied for compatibility with legacy rendering
|
|
21083
|
+
if (this.textAlign.includes(JUSTIFY)) {
|
|
21084
|
+
// Force enlarge spaces after advanced layout calculation
|
|
21085
|
+
setTimeout(() => {
|
|
21086
|
+
if (this.enlargeSpaces) {
|
|
21087
|
+
var _this$canvas2;
|
|
21088
|
+
this.enlargeSpaces();
|
|
21089
|
+
(_this$canvas2 = this.canvas) === null || _this$canvas2 === void 0 || _this$canvas2.renderAll();
|
|
21090
|
+
}
|
|
21091
|
+
}, 0);
|
|
21092
|
+
}
|
|
20828
21093
|
this.dirty = true;
|
|
20829
21094
|
}
|
|
20830
21095
|
|
|
@@ -21836,7 +22101,19 @@ class FabricText extends StyledText {
|
|
|
21836
22101
|
fontSize = this.fontSize
|
|
21837
22102
|
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
21838
22103
|
let forMeasuring = arguments.length > 1 ? arguments[1] : undefined;
|
|
21839
|
-
|
|
22104
|
+
let parsedFontFamily = fontFamily.includes("'") || fontFamily.includes('"') || fontFamily.includes(',') || FabricText.genericFonts.includes(fontFamily.toLowerCase()) ? fontFamily : `"${fontFamily}"`;
|
|
22105
|
+
|
|
22106
|
+
// For fonts like STV that don't support English/Latin characters,
|
|
22107
|
+
// add fallback fonts for consistent rendering of unsupported characters
|
|
22108
|
+
// Only add fallbacks during actual rendering, not for measurements
|
|
22109
|
+
if (!forMeasuring &&
|
|
22110
|
+
// Only during rendering, not measuring
|
|
22111
|
+
!fontFamily.includes(',') && (
|
|
22112
|
+
// Don't add fallbacks if already has them
|
|
22113
|
+
fontFamily.toLowerCase().includes('stv') || fontFamily.toLowerCase().includes('arabic') || fontFamily.toLowerCase().includes('naskh') || fontFamily.toLowerCase().includes('kufi'))) {
|
|
22114
|
+
// Add fallback fonts for unsupported characters (spaces, punctuation, etc.)
|
|
22115
|
+
parsedFontFamily = `${parsedFontFamily}, "Arial Unicode MS", Arial, sans-serif`;
|
|
22116
|
+
}
|
|
21840
22117
|
return [fontStyle, fontWeight, `${forMeasuring ? this.CACHE_FONT_SIZE : fontSize}px`, parsedFontFamily].join(' ');
|
|
21841
22118
|
}
|
|
21842
22119
|
|
|
@@ -21880,7 +22157,13 @@ class FabricText extends StyledText {
|
|
|
21880
22157
|
newLine = ['\n'];
|
|
21881
22158
|
let newText = [];
|
|
21882
22159
|
for (let i = 0; i < lines.length; i++) {
|
|
21883
|
-
|
|
22160
|
+
// Use BiDi-aware grapheme splitting for RTL text
|
|
22161
|
+
if (this.direction === 'rtl' || this._containsArabicText(lines[i])) {
|
|
22162
|
+
newLines[i] = segmentGraphemes(lines[i]);
|
|
22163
|
+
console.log(`🔤 BiDi-aware split line ${i}: "${lines[i]}" -> [${newLines[i].join(', ')}]`);
|
|
22164
|
+
} else {
|
|
22165
|
+
newLines[i] = this.graphemeSplit(lines[i]);
|
|
22166
|
+
}
|
|
21884
22167
|
newText = newText.concat(newLines[i], newLine);
|
|
21885
22168
|
}
|
|
21886
22169
|
newText.pop();
|
|
@@ -21892,6 +22175,14 @@ class FabricText extends StyledText {
|
|
|
21892
22175
|
};
|
|
21893
22176
|
}
|
|
21894
22177
|
|
|
22178
|
+
/**
|
|
22179
|
+
* Check if text contains Arabic characters
|
|
22180
|
+
* @private
|
|
22181
|
+
*/
|
|
22182
|
+
_containsArabicText(text) {
|
|
22183
|
+
return /[\u0600-\u06FF\u0750-\u077F\uFB50-\uFDFF\uFE70-\uFEFF]/.test(text);
|
|
22184
|
+
}
|
|
22185
|
+
|
|
21895
22186
|
/**
|
|
21896
22187
|
* Returns object representation of an instance
|
|
21897
22188
|
* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
|
|
@@ -22014,6 +22305,88 @@ class FabricText extends StyledText {
|
|
|
22014
22305
|
|
|
22015
22306
|
/* _FROM_SVG_END_ */
|
|
22016
22307
|
|
|
22308
|
+
/**
|
|
22309
|
+
* Check if the font is ready for accurate measurements
|
|
22310
|
+
* @private
|
|
22311
|
+
*/
|
|
22312
|
+
_isFontReady() {
|
|
22313
|
+
if (typeof document === 'undefined' || !('fonts' in document)) {
|
|
22314
|
+
return true; // Assume ready in non-browser environments
|
|
22315
|
+
}
|
|
22316
|
+
try {
|
|
22317
|
+
return document.fonts.check(`${this.fontSize}px ${this.fontFamily}`);
|
|
22318
|
+
} catch (e) {
|
|
22319
|
+
return true; // Fallback to assuming ready if check fails
|
|
22320
|
+
}
|
|
22321
|
+
}
|
|
22322
|
+
|
|
22323
|
+
/**
|
|
22324
|
+
* Schedule re-initialization after font loads
|
|
22325
|
+
* @private
|
|
22326
|
+
*/
|
|
22327
|
+
_scheduleInitAfterFontLoad() {
|
|
22328
|
+
if (typeof document === 'undefined' || !('fonts' in document)) {
|
|
22329
|
+
return;
|
|
22330
|
+
}
|
|
22331
|
+
|
|
22332
|
+
// Only schedule if not already waiting
|
|
22333
|
+
if (this._fontLoadScheduled) {
|
|
22334
|
+
return;
|
|
22335
|
+
}
|
|
22336
|
+
this._fontLoadScheduled = true;
|
|
22337
|
+
const fontSpec = `${this.fontSize}px ${this.fontFamily}`;
|
|
22338
|
+
document.fonts.load(fontSpec).then(() => {
|
|
22339
|
+
this._fontLoadScheduled = false;
|
|
22340
|
+
// Re-initialize dimensions with proper font metrics
|
|
22341
|
+
this.initDimensions();
|
|
22342
|
+
|
|
22343
|
+
// Extra step for justify alignment after font loading
|
|
22344
|
+
if (this.textAlign && this.textAlign.includes(JUSTIFY)) {
|
|
22345
|
+
setTimeout(() => {
|
|
22346
|
+
var _this$canvas3;
|
|
22347
|
+
if (this.enlargeSpaces) {
|
|
22348
|
+
this.enlargeSpaces();
|
|
22349
|
+
}
|
|
22350
|
+
(_this$canvas3 = this.canvas) === null || _this$canvas3 === void 0 || _this$canvas3.requestRenderAll();
|
|
22351
|
+
}, 10);
|
|
22352
|
+
} else {
|
|
22353
|
+
var _this$canvas4;
|
|
22354
|
+
(_this$canvas4 = this.canvas) === null || _this$canvas4 === void 0 || _this$canvas4.requestRenderAll();
|
|
22355
|
+
}
|
|
22356
|
+
}).catch(() => {
|
|
22357
|
+
this._fontLoadScheduled = false;
|
|
22358
|
+
});
|
|
22359
|
+
}
|
|
22360
|
+
|
|
22361
|
+
/**
|
|
22362
|
+
* Force complete text re-initialization (useful after JSON loading)
|
|
22363
|
+
*/
|
|
22364
|
+
forceTextReinitialization() {
|
|
22365
|
+
console.log('🔄 Force reinitializing text object');
|
|
22366
|
+
|
|
22367
|
+
// Clear all caches
|
|
22368
|
+
this._clearCache();
|
|
22369
|
+
this.dirty = true;
|
|
22370
|
+
|
|
22371
|
+
// Force text splitting to rebuild internal structures
|
|
22372
|
+
this._splitText();
|
|
22373
|
+
|
|
22374
|
+
// Re-initialize dimensions
|
|
22375
|
+
this.initDimensions();
|
|
22376
|
+
|
|
22377
|
+
// Special handling for justify alignment
|
|
22378
|
+
if (this.textAlign && this.textAlign.includes(JUSTIFY)) {
|
|
22379
|
+
// Ensure justify is applied after dimensions are set
|
|
22380
|
+
setTimeout(() => {
|
|
22381
|
+
if (this.__charBounds && this.__charBounds.length > 0 && this.enlargeSpaces) {
|
|
22382
|
+
var _this$canvas5;
|
|
22383
|
+
this.enlargeSpaces();
|
|
22384
|
+
(_this$canvas5 = this.canvas) === null || _this$canvas5 === void 0 || _this$canvas5.requestRenderAll();
|
|
22385
|
+
}
|
|
22386
|
+
}, 10);
|
|
22387
|
+
}
|
|
22388
|
+
}
|
|
22389
|
+
|
|
22017
22390
|
/**
|
|
22018
22391
|
* Returns FabricText instance from an object representation
|
|
22019
22392
|
* @param {Object} object plain js Object to create an instance from
|
|
@@ -22025,6 +22398,93 @@ class FabricText extends StyledText {
|
|
|
22025
22398
|
styles: stylesFromArray(object.styles || {}, object.text)
|
|
22026
22399
|
}, {
|
|
22027
22400
|
extraParam: 'text'
|
|
22401
|
+
}).then(textObject => {
|
|
22402
|
+
// Ensure text object is properly initialized after JSON deserialization
|
|
22403
|
+
// This is critical for justify alignment and other text layout features
|
|
22404
|
+
textObject.initialized = true;
|
|
22405
|
+
|
|
22406
|
+
// Force reinitialization to ensure proper layout
|
|
22407
|
+
if (textObject._clearCache) {
|
|
22408
|
+
textObject._clearCache();
|
|
22409
|
+
}
|
|
22410
|
+
textObject.dirty = true;
|
|
22411
|
+
|
|
22412
|
+
// Check if we need to wait for font loading (especially for custom fonts like STV)
|
|
22413
|
+
const fontSpec = `${textObject.fontSize}px ${textObject.fontFamily}`;
|
|
22414
|
+
|
|
22415
|
+
// For custom fonts, ensure they're loaded before initializing dimensions
|
|
22416
|
+
if (typeof document !== 'undefined' && 'fonts' in document && textObject.fontFamily !== 'Arial' && textObject.fontFamily !== 'Times New Roman') {
|
|
22417
|
+
return document.fonts.load(fontSpec).then(() => {
|
|
22418
|
+
var _textObject$fontFamil;
|
|
22419
|
+
console.log(`🔤 Font loaded for JSON object: ${fontSpec}`);
|
|
22420
|
+
// Ensure initialized flag is set again (in case constructor reset it)
|
|
22421
|
+
textObject.initialized = true;
|
|
22422
|
+
|
|
22423
|
+
// Special handling for STV fonts which have measurement issues
|
|
22424
|
+
const isStvFont = (_textObject$fontFamil = textObject.fontFamily) === null || _textObject$fontFamil === void 0 ? void 0 : _textObject$fontFamil.toLowerCase().includes('stv');
|
|
22425
|
+
if (isStvFont) {
|
|
22426
|
+
console.log(`🔤 STV font detected, using enhanced reinitialization`);
|
|
22427
|
+
|
|
22428
|
+
// Clear all cached state that might interfere with browser wrapping
|
|
22429
|
+
textObject._browserWrapCache = null;
|
|
22430
|
+
textObject._lastDimensionState = null;
|
|
22431
|
+
textObject._browserWrapInitialized = false;
|
|
22432
|
+
console.log(`🔤 STV font: Cleared all cached states for fresh initialization`);
|
|
22433
|
+
|
|
22434
|
+
// Force browser wrapping flag for STV fonts
|
|
22435
|
+
textObject._usingBrowserWrapping = true;
|
|
22436
|
+
console.log(`🔤 STV font: Forcing browser wrapping flag during JSON load`);
|
|
22437
|
+
|
|
22438
|
+
// Multiple initialization attempts for STV fonts
|
|
22439
|
+
const reinitWithDelay = attempt => {
|
|
22440
|
+
if (textObject.forceTextReinitialization) {
|
|
22441
|
+
textObject.forceTextReinitialization();
|
|
22442
|
+
} else {
|
|
22443
|
+
textObject.initDimensions();
|
|
22444
|
+
}
|
|
22445
|
+
|
|
22446
|
+
// Check if width is still problematic after initialization
|
|
22447
|
+
if (textObject.width < 50 && attempt < 3) {
|
|
22448
|
+
console.log(`🔤 STV font width still ${textObject.width}px, retrying in ${100 * attempt}ms (attempt ${attempt + 1}/3)`);
|
|
22449
|
+
setTimeout(() => reinitWithDelay(attempt + 1), 100 * attempt);
|
|
22450
|
+
}
|
|
22451
|
+
};
|
|
22452
|
+
reinitWithDelay(0);
|
|
22453
|
+
} else {
|
|
22454
|
+
// Use specialized reinitialization for Textbox objects
|
|
22455
|
+
if (textObject.forceTextReinitialization) {
|
|
22456
|
+
console.log(`🔤 Using Textbox specialized reinitialization`);
|
|
22457
|
+
textObject.forceTextReinitialization();
|
|
22458
|
+
} else {
|
|
22459
|
+
// Reinitialize dimensions with proper font metrics
|
|
22460
|
+
textObject.initDimensions();
|
|
22461
|
+
}
|
|
22462
|
+
}
|
|
22463
|
+
return textObject;
|
|
22464
|
+
}).catch(() => {
|
|
22465
|
+
console.warn(`⚠️ Font loading failed for ${fontSpec}, proceeding with fallback`);
|
|
22466
|
+
// Ensure initialized flag is set again
|
|
22467
|
+
textObject.initialized = true;
|
|
22468
|
+
|
|
22469
|
+
// Still initialize dimensions even if font loading fails
|
|
22470
|
+
if (textObject.forceTextReinitialization) {
|
|
22471
|
+
textObject.forceTextReinitialization();
|
|
22472
|
+
} else {
|
|
22473
|
+
textObject.initDimensions();
|
|
22474
|
+
}
|
|
22475
|
+
return textObject;
|
|
22476
|
+
});
|
|
22477
|
+
} else {
|
|
22478
|
+
// Standard fonts - ensure initialized and use appropriate method
|
|
22479
|
+
textObject.initialized = true;
|
|
22480
|
+
if (textObject.forceTextReinitialization) {
|
|
22481
|
+
console.log(`🔤 Using Textbox specialized reinitialization for standard font`);
|
|
22482
|
+
textObject.forceTextReinitialization();
|
|
22483
|
+
} else {
|
|
22484
|
+
textObject.initDimensions();
|
|
22485
|
+
}
|
|
22486
|
+
return textObject;
|
|
22487
|
+
}
|
|
22028
22488
|
});
|
|
22029
22489
|
}
|
|
22030
22490
|
}
|
|
@@ -22668,6 +23128,13 @@ class OverlayEditor {
|
|
|
22668
23128
|
|
|
22669
23129
|
// Apply all other font and text styles to match Fabric
|
|
22670
23130
|
const letterSpacingPx = (target.charSpacing || 0) / 1000 * finalFontSize;
|
|
23131
|
+
|
|
23132
|
+
// Special handling for text objects loaded from JSON - ensure they're properly initialized
|
|
23133
|
+
if (target.dirty !== false && target.initDimensions) {
|
|
23134
|
+
console.log('🔧 Ensuring text object is properly initialized before overlay editing');
|
|
23135
|
+
// Force re-initialization if the text object seems to be in a dirty state
|
|
23136
|
+
target.initDimensions();
|
|
23137
|
+
}
|
|
22671
23138
|
this.textarea.style.fontSize = `${finalFontSize}px`;
|
|
22672
23139
|
this.textarea.style.lineHeight = String(fabricLineHeight);
|
|
22673
23140
|
this.textarea.style.fontFamily = target.fontFamily || 'Arial';
|
|
@@ -26158,8 +26625,27 @@ class Textbox extends IText {
|
|
|
26158
26625
|
*/
|
|
26159
26626
|
initDimensions() {
|
|
26160
26627
|
if (!this.initialized) {
|
|
26628
|
+
this.initialized = true;
|
|
26629
|
+
}
|
|
26630
|
+
|
|
26631
|
+
// Prevent rapid recalculations during moves
|
|
26632
|
+
if (this._usingBrowserWrapping) {
|
|
26633
|
+
const now = Date.now();
|
|
26634
|
+
const lastCall = this._lastInitDimensionsTime || 0;
|
|
26635
|
+
const isRapidCall = now - lastCall < 100;
|
|
26636
|
+
const isDuringLoading = this._jsonLoading || !this._browserWrapInitialized;
|
|
26637
|
+
if (isRapidCall && !isDuringLoading) {
|
|
26638
|
+
return;
|
|
26639
|
+
}
|
|
26640
|
+
this._lastInitDimensionsTime = now;
|
|
26641
|
+
}
|
|
26642
|
+
|
|
26643
|
+
// Skip if nothing changed
|
|
26644
|
+
const currentState = `${this.text}|${this.width}|${this.fontSize}|${this.fontFamily}|${this.textAlign}`;
|
|
26645
|
+
if (this._lastDimensionState === currentState && this._textLines && this._textLines.length > 0) {
|
|
26161
26646
|
return;
|
|
26162
26647
|
}
|
|
26648
|
+
this._lastDimensionState = currentState;
|
|
26163
26649
|
|
|
26164
26650
|
// Use advanced layout if enabled
|
|
26165
26651
|
if (this.enableAdvancedLayout) {
|
|
@@ -26170,17 +26656,142 @@ class Textbox extends IText {
|
|
|
26170
26656
|
// clear dynamicMinWidth as it will be different after we re-wrap line
|
|
26171
26657
|
this.dynamicMinWidth = 0;
|
|
26172
26658
|
// wrap lines
|
|
26173
|
-
|
|
26174
|
-
|
|
26175
|
-
|
|
26659
|
+
const splitTextResult = this._splitText();
|
|
26660
|
+
this._styleMap = this._generateStyleMap(splitTextResult);
|
|
26661
|
+
|
|
26662
|
+
// For browser wrapping, ensure _textLines is set from browser results
|
|
26663
|
+
if (this._usingBrowserWrapping && splitTextResult && splitTextResult.lines) {
|
|
26664
|
+
this._textLines = splitTextResult.lines.map(line => line.split(''));
|
|
26665
|
+
|
|
26666
|
+
// Store justify measurements and browser height
|
|
26667
|
+
const justifyMeasurements = splitTextResult.justifySpaceMeasurements;
|
|
26668
|
+
if (justifyMeasurements) {
|
|
26669
|
+
this._styleMap.justifySpaceMeasurements = justifyMeasurements;
|
|
26670
|
+
}
|
|
26671
|
+
const actualHeight = splitTextResult.actualBrowserHeight;
|
|
26672
|
+
if (actualHeight) {
|
|
26673
|
+
this._actualBrowserHeight = actualHeight;
|
|
26674
|
+
}
|
|
26675
|
+
}
|
|
26676
|
+
// Don't auto-resize width when using browser wrapping to prevent width increases during moves
|
|
26677
|
+
if (!this._usingBrowserWrapping && this.dynamicMinWidth > this.width) {
|
|
26176
26678
|
this._set('width', this.dynamicMinWidth);
|
|
26177
26679
|
}
|
|
26680
|
+
|
|
26681
|
+
// For browser wrapping fonts (like STV), ensure minimum width for new textboxes
|
|
26682
|
+
// since these fonts can't measure English characters properly
|
|
26683
|
+
if (this._usingBrowserWrapping && this.width < 50) {
|
|
26684
|
+
console.log(`🔤 BROWSER WRAP: Font ${this.fontFamily} has width ${this.width}px, setting to 300px for usability`);
|
|
26685
|
+
this.width = 300;
|
|
26686
|
+
}
|
|
26687
|
+
|
|
26688
|
+
// Mark browser wrapping as initialized when complete
|
|
26689
|
+
if (this._usingBrowserWrapping) {
|
|
26690
|
+
this._browserWrapInitialized = true;
|
|
26691
|
+
}
|
|
26178
26692
|
if (this.textAlign.includes(JUSTIFY)) {
|
|
26693
|
+
// For browser wrapping fonts, apply browser-calculated justify spaces
|
|
26694
|
+
if (this._usingBrowserWrapping) {
|
|
26695
|
+
console.log('🔤 BROWSER WRAP: Applying browser-calculated justify spaces');
|
|
26696
|
+
this._applyBrowserJustifySpaces();
|
|
26697
|
+
return;
|
|
26698
|
+
}
|
|
26699
|
+
|
|
26700
|
+
// Don't apply justify alignment during drag operations to prevent snapping
|
|
26701
|
+
const now = Date.now();
|
|
26702
|
+
const lastDragTime = this._lastInitDimensionsTime || 0;
|
|
26703
|
+
const isDuringDrag = now - lastDragTime < 200; // 200ms window for drag detection
|
|
26704
|
+
|
|
26705
|
+
if (isDuringDrag) {
|
|
26706
|
+
console.log('🔤 Skipping justify during drag operation to prevent snapping');
|
|
26707
|
+
return;
|
|
26708
|
+
}
|
|
26709
|
+
|
|
26710
|
+
// For non-browser-wrapping fonts, use Fabric's justify system
|
|
26179
26711
|
// once text is measured we need to make space fatter to make justified text.
|
|
26180
|
-
|
|
26712
|
+
// Ensure __charBounds exists and fonts are ready before applying justify
|
|
26713
|
+
if (this.__charBounds && this.__charBounds.length > 0) {
|
|
26714
|
+
// Check if font is ready for accurate justify calculations
|
|
26715
|
+
const fontReady = this._isFontReady ? this._isFontReady() : true;
|
|
26716
|
+
if (fontReady) {
|
|
26717
|
+
this.enlargeSpaces();
|
|
26718
|
+
} else {
|
|
26719
|
+
console.warn('⚠️ Textbox: Font not ready for justify, deferring enlargeSpaces');
|
|
26720
|
+
// Defer justify calculation until font is ready
|
|
26721
|
+
this._scheduleJustifyAfterFontLoad();
|
|
26722
|
+
}
|
|
26723
|
+
} else {
|
|
26724
|
+
console.warn('⚠️ Textbox: __charBounds not ready for justify alignment, deferring enlargeSpaces');
|
|
26725
|
+
// Defer the justify calculation until the next frame
|
|
26726
|
+
setTimeout(() => {
|
|
26727
|
+
if (this.__charBounds && this.__charBounds.length > 0 && this.enlargeSpaces) {
|
|
26728
|
+
var _this$canvas;
|
|
26729
|
+
console.log('🔧 Applying deferred Textbox justify alignment');
|
|
26730
|
+
this.enlargeSpaces();
|
|
26731
|
+
(_this$canvas = this.canvas) === null || _this$canvas === void 0 || _this$canvas.requestRenderAll();
|
|
26732
|
+
}
|
|
26733
|
+
}, 0);
|
|
26734
|
+
}
|
|
26735
|
+
}
|
|
26736
|
+
// Calculate height - use Fabric's calculation for proper text rendering space
|
|
26737
|
+
if (this._usingBrowserWrapping && this._textLines && this._textLines.length > 0) {
|
|
26738
|
+
const actualBrowserHeight = this._actualBrowserHeight;
|
|
26739
|
+
const oldHeight = this.height;
|
|
26740
|
+
// Use Fabric's height calculation since it knows how much space text rendering needs
|
|
26741
|
+
this.height = this.calcTextHeight();
|
|
26742
|
+
|
|
26743
|
+
// Force canvas refresh and control update if height changed significantly
|
|
26744
|
+
if (Math.abs(this.height - oldHeight) > 1) {
|
|
26745
|
+
var _this$canvas2, _this$_textLines;
|
|
26746
|
+
this.setCoords();
|
|
26747
|
+
(_this$canvas2 = this.canvas) === null || _this$canvas2 === void 0 || _this$canvas2.requestRenderAll();
|
|
26748
|
+
|
|
26749
|
+
// DEBUG: Log exact positioning details
|
|
26750
|
+
console.log(`🎯 POSITIONING DEBUG:`);
|
|
26751
|
+
console.log(` Textbox height: ${this.height}px`);
|
|
26752
|
+
console.log(` Textbox top: ${this.top}px`);
|
|
26753
|
+
console.log(` Textbox left: ${this.left}px`);
|
|
26754
|
+
console.log(` Text lines: ${((_this$_textLines = this._textLines) === null || _this$_textLines === void 0 ? void 0 : _this$_textLines.length) || 0}`);
|
|
26755
|
+
console.log(` Font size: ${this.fontSize}px`);
|
|
26756
|
+
console.log(` Line height: ${this.lineHeight || 1.16}`);
|
|
26757
|
+
console.log(` Calculated line height: ${this.fontSize * (this.lineHeight || 1.16)}px`);
|
|
26758
|
+
console.log(` _getTopOffset(): ${this._getTopOffset()}px`);
|
|
26759
|
+
console.log(` calcTextHeight(): ${this.calcTextHeight()}px`);
|
|
26760
|
+
console.log(` Browser height: ${actualBrowserHeight}px`);
|
|
26761
|
+
console.log(` Height difference: ${this.height - this.calcTextHeight()}px`);
|
|
26762
|
+
}
|
|
26763
|
+
} else {
|
|
26764
|
+
this.height = this.calcTextHeight();
|
|
26765
|
+
}
|
|
26766
|
+
}
|
|
26767
|
+
|
|
26768
|
+
/**
|
|
26769
|
+
* Schedule justify calculation after font loads (Textbox-specific)
|
|
26770
|
+
* @private
|
|
26771
|
+
*/
|
|
26772
|
+
_scheduleJustifyAfterFontLoad() {
|
|
26773
|
+
if (typeof document === 'undefined' || !('fonts' in document)) {
|
|
26774
|
+
return;
|
|
26181
26775
|
}
|
|
26182
|
-
|
|
26183
|
-
|
|
26776
|
+
|
|
26777
|
+
// Only schedule if not already waiting
|
|
26778
|
+
if (this._fontJustifyScheduled) {
|
|
26779
|
+
return;
|
|
26780
|
+
}
|
|
26781
|
+
this._fontJustifyScheduled = true;
|
|
26782
|
+
const fontSpec = `${this.fontSize}px ${this.fontFamily}`;
|
|
26783
|
+
document.fonts.load(fontSpec).then(() => {
|
|
26784
|
+
var _this$canvas3;
|
|
26785
|
+
this._fontJustifyScheduled = false;
|
|
26786
|
+
console.log('🔧 Textbox: Font loaded, applying justify alignment');
|
|
26787
|
+
|
|
26788
|
+
// Re-run initDimensions to ensure proper justify calculation
|
|
26789
|
+
this.initDimensions();
|
|
26790
|
+
(_this$canvas3 = this.canvas) === null || _this$canvas3 === void 0 || _this$canvas3.requestRenderAll();
|
|
26791
|
+
}).catch(() => {
|
|
26792
|
+
this._fontJustifyScheduled = false;
|
|
26793
|
+
console.warn('⚠️ Textbox: Font loading failed, justify may be incorrect');
|
|
26794
|
+
});
|
|
26184
26795
|
}
|
|
26185
26796
|
|
|
26186
26797
|
/**
|
|
@@ -26547,19 +27158,33 @@ class Textbox extends IText {
|
|
|
26547
27158
|
width: wordWidth
|
|
26548
27159
|
} = data[i];
|
|
26549
27160
|
offset += word.length;
|
|
26550
|
-
|
|
26551
|
-
if
|
|
27161
|
+
|
|
27162
|
+
// Predictive wrapping: check if adding this word would exceed the width
|
|
27163
|
+
const potentialLineWidth = lineWidth + infixWidth + wordWidth - additionalSpace;
|
|
27164
|
+
// Use exact width to match overlay editor behavior
|
|
27165
|
+
const conservativeMaxWidth = maxWidth; // No artificial buffer
|
|
27166
|
+
|
|
27167
|
+
// Debug logging for wrapping decisions
|
|
27168
|
+
const currentLineText = line.join('');
|
|
27169
|
+
console.log(`🔧 FABRIC WRAP CHECK: "${data[i].word}" -> potential: ${potentialLineWidth.toFixed(1)}px vs limit: ${conservativeMaxWidth.toFixed(1)}px`);
|
|
27170
|
+
if (potentialLineWidth > conservativeMaxWidth && !lineJustStarted) {
|
|
27171
|
+
// This word would exceed the width, wrap before adding it
|
|
27172
|
+
console.log(`🔧 FABRIC WRAP! Line: "${currentLineText}" (${lineWidth.toFixed(1)}px)`);
|
|
26552
27173
|
graphemeLines.push(line);
|
|
26553
27174
|
line = [];
|
|
26554
|
-
lineWidth = wordWidth;
|
|
27175
|
+
lineWidth = wordWidth; // Start new line with just this word
|
|
26555
27176
|
lineJustStarted = true;
|
|
26556
27177
|
} else {
|
|
26557
|
-
|
|
27178
|
+
// Word fits, add it to current line
|
|
27179
|
+
lineWidth = potentialLineWidth + additionalSpace;
|
|
26558
27180
|
}
|
|
26559
27181
|
if (!lineJustStarted && !splitByGrapheme) {
|
|
26560
27182
|
line.push(infix);
|
|
26561
27183
|
}
|
|
26562
27184
|
line = line.concat(word);
|
|
27185
|
+
|
|
27186
|
+
// Debug: show current line after adding word
|
|
27187
|
+
console.log(`🔧 FABRIC AFTER ADD: Line now: "${line.join('')}" (${line.length} chars)`);
|
|
26563
27188
|
infixWidth = splitByGrapheme ? 0 : this._measureWord([infix], lineIndex, offset);
|
|
26564
27189
|
offset++;
|
|
26565
27190
|
lineJustStarted = false;
|
|
@@ -26569,9 +27194,19 @@ class Textbox extends IText {
|
|
|
26569
27194
|
// TODO: this code is probably not necessary anymore.
|
|
26570
27195
|
// it can be moved out of this function since largestWordWidth is now
|
|
26571
27196
|
// known in advance
|
|
26572
|
-
|
|
27197
|
+
// Don't modify dynamicMinWidth when using browser wrapping to prevent width increases
|
|
27198
|
+
if (!this._usingBrowserWrapping && largestWordWidth + reservedSpace > this.dynamicMinWidth) {
|
|
27199
|
+
console.log(`🔧 FABRIC updating dynamicMinWidth: ${this.dynamicMinWidth} -> ${largestWordWidth - additionalSpace + reservedSpace}`);
|
|
26573
27200
|
this.dynamicMinWidth = largestWordWidth - additionalSpace + reservedSpace;
|
|
27201
|
+
} else if (this._usingBrowserWrapping) {
|
|
27202
|
+
console.log(`🔤 BROWSER WRAP: Skipping dynamicMinWidth update to prevent width increase`);
|
|
26574
27203
|
}
|
|
27204
|
+
|
|
27205
|
+
// Debug: show final wrapped lines
|
|
27206
|
+
console.log(`🔧 FABRIC FINAL LINES: ${graphemeLines.length} lines`);
|
|
27207
|
+
graphemeLines.forEach((line, i) => {
|
|
27208
|
+
console.log(` Line ${i + 1}: "${line.join('')}" (${line.length} chars)`);
|
|
27209
|
+
});
|
|
26575
27210
|
return graphemeLines;
|
|
26576
27211
|
}
|
|
26577
27212
|
|
|
@@ -26615,6 +27250,260 @@ class Textbox extends IText {
|
|
|
26615
27250
|
* @override
|
|
26616
27251
|
*/
|
|
26617
27252
|
_splitTextIntoLines(text) {
|
|
27253
|
+
// Check if we need browser wrapping using smart font detection
|
|
27254
|
+
const needsBrowserWrapping = this.fontFamily && fontLacksEnglishGlyphsCached(this.fontFamily);
|
|
27255
|
+
if (needsBrowserWrapping) {
|
|
27256
|
+
// Cache key based on text content, width, font properties, AND text alignment
|
|
27257
|
+
const textHash = text.length + text.slice(0, 50); // Include text content in cache key
|
|
27258
|
+
const cacheKey = `${textHash}|${this.width}|${this.fontSize}|${this.fontFamily}|${this.textAlign}`;
|
|
27259
|
+
|
|
27260
|
+
// Check if we have a cached result and nothing has changed
|
|
27261
|
+
if (this._browserWrapCache && this._browserWrapCache.key === cacheKey) {
|
|
27262
|
+
const cachedResult = this._browserWrapCache.result;
|
|
27263
|
+
|
|
27264
|
+
// For justify alignment, ensure we have the measurements
|
|
27265
|
+
if (this.textAlign.includes('justify') && !cachedResult.justifySpaceMeasurements) ; else {
|
|
27266
|
+
return cachedResult;
|
|
27267
|
+
}
|
|
27268
|
+
}
|
|
27269
|
+
const result = this._splitTextIntoLinesWithBrowser(text);
|
|
27270
|
+
|
|
27271
|
+
// Cache the result
|
|
27272
|
+
this._browserWrapCache = {
|
|
27273
|
+
key: cacheKey,
|
|
27274
|
+
result
|
|
27275
|
+
};
|
|
27276
|
+
|
|
27277
|
+
// Mark that we used browser wrapping to prevent dynamicMinWidth modifications
|
|
27278
|
+
this._usingBrowserWrapping = true;
|
|
27279
|
+
return result;
|
|
27280
|
+
}
|
|
27281
|
+
|
|
27282
|
+
// Clear the browser wrapping flag when using regular wrapping
|
|
27283
|
+
this._usingBrowserWrapping = false;
|
|
27284
|
+
|
|
27285
|
+
// Default Fabric wrapping for other fonts
|
|
27286
|
+
const newText = super._splitTextIntoLines(text),
|
|
27287
|
+
graphemeLines = this._wrapText(newText.lines, this.width),
|
|
27288
|
+
lines = new Array(graphemeLines.length);
|
|
27289
|
+
for (let i = 0; i < graphemeLines.length; i++) {
|
|
27290
|
+
lines[i] = graphemeLines[i].join('');
|
|
27291
|
+
}
|
|
27292
|
+
newText.lines = lines;
|
|
27293
|
+
newText.graphemeLines = graphemeLines;
|
|
27294
|
+
return newText;
|
|
27295
|
+
}
|
|
27296
|
+
|
|
27297
|
+
/**
|
|
27298
|
+
* Use browser's native text wrapping for accurate handling of fonts without English glyphs
|
|
27299
|
+
* @private
|
|
27300
|
+
*/
|
|
27301
|
+
_splitTextIntoLinesWithBrowser(text) {
|
|
27302
|
+
if (typeof document === 'undefined') {
|
|
27303
|
+
// Fallback to regular wrapping in Node.js
|
|
27304
|
+
return this._splitTextIntoLinesDefault(text);
|
|
27305
|
+
}
|
|
27306
|
+
|
|
27307
|
+
// Create a hidden element that mimics the overlay editor
|
|
27308
|
+
const testElement = document.createElement('div');
|
|
27309
|
+
testElement.style.position = 'absolute';
|
|
27310
|
+
testElement.style.left = '-9999px';
|
|
27311
|
+
testElement.style.visibility = 'hidden';
|
|
27312
|
+
testElement.style.fontSize = `${this.fontSize}px`;
|
|
27313
|
+
testElement.style.fontFamily = `"${this.fontFamily}"`;
|
|
27314
|
+
testElement.style.fontWeight = String(this.fontWeight || 'normal');
|
|
27315
|
+
testElement.style.fontStyle = String(this.fontStyle || 'normal');
|
|
27316
|
+
testElement.style.lineHeight = String(this.lineHeight || 1.16);
|
|
27317
|
+
testElement.style.width = `${this.width}px`;
|
|
27318
|
+
testElement.style.direction = this.direction || 'ltr';
|
|
27319
|
+
testElement.style.whiteSpace = 'pre-wrap';
|
|
27320
|
+
testElement.style.wordBreak = 'normal';
|
|
27321
|
+
testElement.style.overflowWrap = 'break-word';
|
|
27322
|
+
|
|
27323
|
+
// Set browser-native text alignment (including justify)
|
|
27324
|
+
if (this.textAlign.includes('justify')) {
|
|
27325
|
+
testElement.style.textAlign = 'justify';
|
|
27326
|
+
testElement.style.textAlignLast = 'auto'; // Let browser decide last line alignment
|
|
27327
|
+
} else {
|
|
27328
|
+
testElement.style.textAlign = this.textAlign;
|
|
27329
|
+
}
|
|
27330
|
+
testElement.textContent = text;
|
|
27331
|
+
document.body.appendChild(testElement);
|
|
27332
|
+
|
|
27333
|
+
// Get the browser's natural line breaks
|
|
27334
|
+
const range = document.createRange();
|
|
27335
|
+
const lines = [];
|
|
27336
|
+
const graphemeLines = [];
|
|
27337
|
+
try {
|
|
27338
|
+
// Simple approach: split by measuring character positions
|
|
27339
|
+
const textNode = testElement.firstChild;
|
|
27340
|
+
if (textNode && textNode.nodeType === Node.TEXT_NODE) {
|
|
27341
|
+
let currentLineStart = 0;
|
|
27342
|
+
const textLength = text.length;
|
|
27343
|
+
let previousBottom = 0;
|
|
27344
|
+
for (let i = 0; i <= textLength; i++) {
|
|
27345
|
+
range.setStart(textNode, currentLineStart);
|
|
27346
|
+
range.setEnd(textNode, i);
|
|
27347
|
+
const rect = range.getBoundingClientRect();
|
|
27348
|
+
if (i > currentLineStart && (rect.bottom > previousBottom + 5 || i === textLength)) {
|
|
27349
|
+
// New line detected or end of text
|
|
27350
|
+
const lineEnd = i === textLength ? i : i - 1;
|
|
27351
|
+
const lineText = text.substring(currentLineStart, lineEnd).trim();
|
|
27352
|
+
if (lineText) {
|
|
27353
|
+
lines.push(lineText);
|
|
27354
|
+
// Convert to graphemes for compatibility
|
|
27355
|
+
const graphemeLine = lineText.split('');
|
|
27356
|
+
graphemeLines.push(graphemeLine);
|
|
27357
|
+
}
|
|
27358
|
+
currentLineStart = lineEnd;
|
|
27359
|
+
previousBottom = rect.bottom;
|
|
27360
|
+
}
|
|
27361
|
+
}
|
|
27362
|
+
}
|
|
27363
|
+
} catch (error) {
|
|
27364
|
+
console.warn('Browser wrapping failed, using fallback:', error);
|
|
27365
|
+
document.body.removeChild(testElement);
|
|
27366
|
+
return this._splitTextIntoLinesDefault(text);
|
|
27367
|
+
}
|
|
27368
|
+
|
|
27369
|
+
// Extract actual browser height BEFORE removing element
|
|
27370
|
+
const actualBrowserHeight = testElement.scrollHeight;
|
|
27371
|
+
const offsetHeight = testElement.offsetHeight;
|
|
27372
|
+
const clientHeight = testElement.clientHeight;
|
|
27373
|
+
const boundingRect = testElement.getBoundingClientRect();
|
|
27374
|
+
console.log(`🔤 Browser element measurements:`);
|
|
27375
|
+
console.log(` scrollHeight: ${actualBrowserHeight}px (content + padding + hidden overflow)`);
|
|
27376
|
+
console.log(` offsetHeight: ${offsetHeight}px (content + padding + border)`);
|
|
27377
|
+
console.log(` clientHeight: ${clientHeight}px (content + padding, no border/scrollbar)`);
|
|
27378
|
+
console.log(` boundingRect.height: ${boundingRect.height}px (actual rendered height)`);
|
|
27379
|
+
console.log(` Font size: ${this.fontSize}px, Line height: ${this.lineHeight || 1.16}, Lines: ${lines.length}`);
|
|
27380
|
+
|
|
27381
|
+
// For justify alignment, extract space measurements from browser BEFORE removing element
|
|
27382
|
+
let justifySpaceMeasurements = null;
|
|
27383
|
+
if (this.textAlign.includes('justify')) {
|
|
27384
|
+
justifySpaceMeasurements = this._extractJustifySpaceMeasurements(testElement, lines);
|
|
27385
|
+
}
|
|
27386
|
+
document.body.removeChild(testElement);
|
|
27387
|
+
console.log(`🔤 Browser wrapping result: ${lines.length} lines`);
|
|
27388
|
+
|
|
27389
|
+
// Try different height measurements to find the most accurate
|
|
27390
|
+
let bestHeight = actualBrowserHeight;
|
|
27391
|
+
|
|
27392
|
+
// If scrollHeight and offsetHeight differ significantly, investigate
|
|
27393
|
+
if (Math.abs(actualBrowserHeight - offsetHeight) > 2) {
|
|
27394
|
+
console.log(`🔤 Height discrepancy detected: scrollHeight=${actualBrowserHeight}px vs offsetHeight=${offsetHeight}px`);
|
|
27395
|
+
}
|
|
27396
|
+
|
|
27397
|
+
// Consider using boundingRect height if it's larger (sometimes more accurate for visible content)
|
|
27398
|
+
if (boundingRect.height > bestHeight) {
|
|
27399
|
+
console.log(`🔤 Using boundingRect height (${boundingRect.height}px) instead of scrollHeight (${bestHeight}px)`);
|
|
27400
|
+
bestHeight = boundingRect.height;
|
|
27401
|
+
}
|
|
27402
|
+
|
|
27403
|
+
// Font-specific height adjustments for accurate bounding box
|
|
27404
|
+
let adjustedHeight = bestHeight;
|
|
27405
|
+
|
|
27406
|
+
// Fonts without English glyphs need additional height buffer due to different font metrics
|
|
27407
|
+
const lacksEnglishGlyphs = fontLacksEnglishGlyphsCached(this.fontFamily);
|
|
27408
|
+
if (lacksEnglishGlyphs) {
|
|
27409
|
+
const glyphBuffer = this.fontSize * 0.25; // 25% of font size for non-English fonts
|
|
27410
|
+
adjustedHeight = bestHeight + glyphBuffer;
|
|
27411
|
+
console.log(`🔤 Non-English font detected (${this.fontFamily}): Adding ${glyphBuffer}px buffer (${bestHeight}px + ${glyphBuffer}px = ${adjustedHeight}px)`);
|
|
27412
|
+
} else {
|
|
27413
|
+
console.log(`🔤 Standard font (${this.fontFamily}): Using browser height directly (${bestHeight}px)`);
|
|
27414
|
+
}
|
|
27415
|
+
return {
|
|
27416
|
+
_unwrappedLines: [text.split('')],
|
|
27417
|
+
lines: lines,
|
|
27418
|
+
graphemeText: text.split(''),
|
|
27419
|
+
graphemeLines: graphemeLines,
|
|
27420
|
+
justifySpaceMeasurements: justifySpaceMeasurements,
|
|
27421
|
+
actualBrowserHeight: adjustedHeight
|
|
27422
|
+
};
|
|
27423
|
+
}
|
|
27424
|
+
|
|
27425
|
+
/**
|
|
27426
|
+
* Extract justify space measurements from browser
|
|
27427
|
+
* @private
|
|
27428
|
+
*/
|
|
27429
|
+
_extractJustifySpaceMeasurements(element, lines) {
|
|
27430
|
+
console.log(`🔤 Extracting browser justify space measurements for ${lines.length} lines`);
|
|
27431
|
+
|
|
27432
|
+
// For now, we'll use a simplified approach:
|
|
27433
|
+
// Apply uniform space expansion to match the line width
|
|
27434
|
+
const spaceWidths = [];
|
|
27435
|
+
lines.forEach((line, lineIndex) => {
|
|
27436
|
+
const lineSpaces = [];
|
|
27437
|
+
const spaceCount = (line.match(/\s/g) || []).length;
|
|
27438
|
+
if (spaceCount > 0 && lineIndex < lines.length - 1) {
|
|
27439
|
+
// Don't justify last line
|
|
27440
|
+
// Calculate how much space expansion is needed
|
|
27441
|
+
const normalSpaceWidth = 6.4; // Default space width for STV font
|
|
27442
|
+
const lineWidth = this.width;
|
|
27443
|
+
|
|
27444
|
+
// Estimate natural line width
|
|
27445
|
+
const charCount = line.length - spaceCount;
|
|
27446
|
+
const avgCharWidth = 12; // Approximate for STV font
|
|
27447
|
+
|
|
27448
|
+
// Calculate expanded space width
|
|
27449
|
+
const remainingSpace = lineWidth - charCount * avgCharWidth;
|
|
27450
|
+
const expandedSpaceWidth = remainingSpace / spaceCount;
|
|
27451
|
+
console.log(`🔤 Line ${lineIndex}: ${spaceCount} spaces, natural: ${normalSpaceWidth}px -> justified: ${expandedSpaceWidth.toFixed(1)}px`);
|
|
27452
|
+
|
|
27453
|
+
// Fill array with expanded space widths for this line
|
|
27454
|
+
for (let i = 0; i < spaceCount; i++) {
|
|
27455
|
+
lineSpaces.push(expandedSpaceWidth);
|
|
27456
|
+
}
|
|
27457
|
+
}
|
|
27458
|
+
spaceWidths.push(lineSpaces);
|
|
27459
|
+
});
|
|
27460
|
+
return spaceWidths;
|
|
27461
|
+
}
|
|
27462
|
+
|
|
27463
|
+
/**
|
|
27464
|
+
* Apply browser-calculated justify space measurements
|
|
27465
|
+
* @private
|
|
27466
|
+
*/
|
|
27467
|
+
_applyBrowserJustifySpaces() {
|
|
27468
|
+
if (!this._textLines || !this.__charBounds) {
|
|
27469
|
+
console.warn('🔤 BROWSER JUSTIFY: _textLines or __charBounds not ready');
|
|
27470
|
+
return;
|
|
27471
|
+
}
|
|
27472
|
+
|
|
27473
|
+
// Get space measurements from browser wrapping result
|
|
27474
|
+
const styleMap = this._styleMap;
|
|
27475
|
+
if (!styleMap || !styleMap.justifySpaceMeasurements) {
|
|
27476
|
+
console.warn('🔤 BROWSER JUSTIFY: No justify space measurements available');
|
|
27477
|
+
return;
|
|
27478
|
+
}
|
|
27479
|
+
const spaceWidths = styleMap.justifySpaceMeasurements;
|
|
27480
|
+
console.log('🔤 BROWSER JUSTIFY: Applying space measurements to __charBounds');
|
|
27481
|
+
|
|
27482
|
+
// Apply space widths to character bounds
|
|
27483
|
+
this._textLines.forEach((line, lineIndex) => {
|
|
27484
|
+
if (!this.__charBounds || !this.__charBounds[lineIndex] || !spaceWidths[lineIndex]) return;
|
|
27485
|
+
const lineBounds = this.__charBounds[lineIndex];
|
|
27486
|
+
const lineSpaceWidths = spaceWidths[lineIndex];
|
|
27487
|
+
let spaceIndex = 0;
|
|
27488
|
+
for (let charIndex = 0; charIndex < line.length; charIndex++) {
|
|
27489
|
+
if (/\s/.test(line[charIndex]) && spaceIndex < lineSpaceWidths.length) {
|
|
27490
|
+
const expandedWidth = lineSpaceWidths[spaceIndex];
|
|
27491
|
+
if (lineBounds[charIndex]) {
|
|
27492
|
+
const oldWidth = lineBounds[charIndex].width;
|
|
27493
|
+
lineBounds[charIndex].width = expandedWidth;
|
|
27494
|
+
console.log(`🔤 Line ${lineIndex} space ${spaceIndex}: ${oldWidth.toFixed(1)}px -> ${expandedWidth.toFixed(1)}px`);
|
|
27495
|
+
}
|
|
27496
|
+
spaceIndex++;
|
|
27497
|
+
}
|
|
27498
|
+
}
|
|
27499
|
+
});
|
|
27500
|
+
}
|
|
27501
|
+
|
|
27502
|
+
/**
|
|
27503
|
+
* Fallback to default Fabric wrapping
|
|
27504
|
+
* @private
|
|
27505
|
+
*/
|
|
27506
|
+
_splitTextIntoLinesDefault(text) {
|
|
26618
27507
|
const newText = super._splitTextIntoLines(text),
|
|
26619
27508
|
graphemeLines = this._wrapText(newText.lines, this.width),
|
|
26620
27509
|
lines = new Array(graphemeLines.length);
|
|
@@ -26649,7 +27538,7 @@ class Textbox extends IText {
|
|
|
26649
27538
|
* @private
|
|
26650
27539
|
*/
|
|
26651
27540
|
initializeEventListeners() {
|
|
26652
|
-
var _this$
|
|
27541
|
+
var _this$canvas4;
|
|
26653
27542
|
// Track which side is being used for resize to handle position compensation
|
|
26654
27543
|
let resizeOrigin = null;
|
|
26655
27544
|
|
|
@@ -26680,7 +27569,7 @@ class Textbox extends IText {
|
|
|
26680
27569
|
});
|
|
26681
27570
|
|
|
26682
27571
|
// Also listen to canvas-level modified event as backup
|
|
26683
|
-
(_this$
|
|
27572
|
+
(_this$canvas4 = this.canvas) === null || _this$canvas4 === void 0 || _this$canvas4.on('object:modified', e => {
|
|
26684
27573
|
if (e.target === this) {
|
|
26685
27574
|
const currentResizeOrigin = resizeOrigin; // Capture the value before reset
|
|
26686
27575
|
setTimeout(() => this.safetySnapWidth(currentResizeOrigin), 10);
|
|
@@ -26724,7 +27613,7 @@ class Textbox extends IText {
|
|
|
26724
27613
|
const safetyThreshold = 2; // px - very subtle trigger
|
|
26725
27614
|
|
|
26726
27615
|
if (maxRequiredWidth > this.width - safetyThreshold) {
|
|
26727
|
-
var _this$
|
|
27616
|
+
var _this$canvas5;
|
|
26728
27617
|
// Set width to exactly what's needed + minimal safety margin
|
|
26729
27618
|
const newWidth = maxRequiredWidth + 1; // Add just 1px safety margin
|
|
26730
27619
|
|
|
@@ -26757,7 +27646,66 @@ class Textbox extends IText {
|
|
|
26757
27646
|
this.__overlayEditor.refresh();
|
|
26758
27647
|
}, 0);
|
|
26759
27648
|
}
|
|
26760
|
-
(_this$
|
|
27649
|
+
(_this$canvas5 = this.canvas) === null || _this$canvas5 === void 0 || _this$canvas5.requestRenderAll();
|
|
27650
|
+
}
|
|
27651
|
+
}
|
|
27652
|
+
|
|
27653
|
+
/**
|
|
27654
|
+
* Force complete textbox re-initialization (useful after JSON loading)
|
|
27655
|
+
* Overrides Text version with Textbox-specific logic
|
|
27656
|
+
*/
|
|
27657
|
+
forceTextReinitialization() {
|
|
27658
|
+
console.log('🔄 Force reinitializing Textbox object');
|
|
27659
|
+
|
|
27660
|
+
// CRITICAL: Ensure textbox is marked as initialized
|
|
27661
|
+
this.initialized = true;
|
|
27662
|
+
|
|
27663
|
+
// Clear all caches and force dirty state
|
|
27664
|
+
this._clearCache();
|
|
27665
|
+
this.dirty = true;
|
|
27666
|
+
this.dynamicMinWidth = 0;
|
|
27667
|
+
|
|
27668
|
+
// Force isEditing false to ensure clean state
|
|
27669
|
+
this.isEditing = false;
|
|
27670
|
+
console.log(' → Set initialized=true, dirty=true, cleared caches');
|
|
27671
|
+
|
|
27672
|
+
// Re-initialize dimensions (this will handle justify properly)
|
|
27673
|
+
this.initDimensions();
|
|
27674
|
+
|
|
27675
|
+
// Double-check that justify was applied by checking space widths
|
|
27676
|
+
if (this.textAlign.includes('justify') && this.__charBounds) {
|
|
27677
|
+
setTimeout(() => {
|
|
27678
|
+
var _this$canvas6;
|
|
27679
|
+
// Verify justify was applied by checking if space widths vary
|
|
27680
|
+
let hasVariableSpaces = false;
|
|
27681
|
+
this.__charBounds.forEach((lineBounds, i) => {
|
|
27682
|
+
if (lineBounds && this._textLines && this._textLines[i]) {
|
|
27683
|
+
const spaces = lineBounds.filter((bound, j) => /\s/.test(this._textLines[i][j]));
|
|
27684
|
+
if (spaces.length > 1) {
|
|
27685
|
+
const firstSpaceWidth = spaces[0].width;
|
|
27686
|
+
hasVariableSpaces = spaces.some(space => Math.abs(space.width - firstSpaceWidth) > 0.1);
|
|
27687
|
+
}
|
|
27688
|
+
}
|
|
27689
|
+
});
|
|
27690
|
+
if (!hasVariableSpaces && this.__charBounds.length > 0) {
|
|
27691
|
+
console.warn(' ⚠️ Justify spaces still uniform - forcing enlargeSpaces again');
|
|
27692
|
+
if (this.enlargeSpaces) {
|
|
27693
|
+
this.enlargeSpaces();
|
|
27694
|
+
}
|
|
27695
|
+
} else {
|
|
27696
|
+
console.log(' ✅ Justify spaces properly expanded');
|
|
27697
|
+
}
|
|
27698
|
+
|
|
27699
|
+
// Ensure height is recalculated - use browser height if available
|
|
27700
|
+
if (this._usingBrowserWrapping && this._actualBrowserHeight) {
|
|
27701
|
+
this.height = this._actualBrowserHeight;
|
|
27702
|
+
console.log(`🔤 JUSTIFY: Preserved browser height: ${this.height}px`);
|
|
27703
|
+
} else {
|
|
27704
|
+
this.height = this.calcTextHeight();
|
|
27705
|
+
console.log(`🔧 JUSTIFY: Used calcTextHeight: ${this.height}px`);
|
|
27706
|
+
}
|
|
27707
|
+
(_this$canvas6 = this.canvas) === null || _this$canvas6 === void 0 || _this$canvas6.requestRenderAll();
|
|
27708
|
+
}, 10);
|
|
26761
27709
|
}
|
|
26762
27710
|
}
|
|
26763
27711
|
|