@nasser-sw/fabric 7.0.1-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 +1853 -288
- 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 +1853 -288
- package/dist/index.mjs.map +1 -1
- package/dist/index.node.cjs +1853 -288
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.mjs +1853 -288
- 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 +43 -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 +521 -67
- 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.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 +155 -9
- 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 +43 -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.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 +544 -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 +164 -12
- 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/dist/index.node.cjs
CHANGED
|
@@ -412,7 +412,7 @@ class Cache {
|
|
|
412
412
|
}
|
|
413
413
|
const cache = new Cache();
|
|
414
414
|
|
|
415
|
-
var version = "7.0.
|
|
415
|
+
var version = "7.0.1-beta9";
|
|
416
416
|
|
|
417
417
|
// use this syntax so babel plugin see this import here
|
|
418
418
|
const VERSION = version;
|
|
@@ -17629,33 +17629,30 @@ class PatternBrush extends PencilBrush {
|
|
|
17629
17629
|
}
|
|
17630
17630
|
}
|
|
17631
17631
|
|
|
17632
|
-
// @TODO this code is terrible and Line should be a special case of polyline.
|
|
17633
|
-
|
|
17634
17632
|
const coordProps = ['x1', 'x2', 'y1', 'y2'];
|
|
17635
|
-
/**
|
|
17636
|
-
* A Class to draw a line
|
|
17637
|
-
* A bunch of methods will be added to Polyline to handle the line case
|
|
17638
|
-
* The line class is very strange to work with, is all special, it hardly aligns
|
|
17639
|
-
* to what a developer want everytime there is an angle
|
|
17640
|
-
* @deprecated
|
|
17641
|
-
*/
|
|
17642
17633
|
class Line extends FabricObject {
|
|
17643
|
-
/**
|
|
17644
|
-
* Constructor
|
|
17645
|
-
* @param {Array} [points] Array of points
|
|
17646
|
-
* @param {Object} [options] Options object
|
|
17647
|
-
* @return {Line} thisArg
|
|
17648
|
-
*/
|
|
17649
17634
|
constructor() {
|
|
17650
|
-
let [x1, y1, x2, y2] = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [0, 0,
|
|
17635
|
+
let [x1, y1, x2, y2] = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [0, 0, 100, 0];
|
|
17651
17636
|
let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
17652
17637
|
super();
|
|
17653
|
-
|
|
17638
|
+
_defineProperty(this, "hitStrokeWidth", 'auto');
|
|
17639
|
+
_defineProperty(this, "_updatingEndpoints", false);
|
|
17640
|
+
_defineProperty(this, "_useEndpointCoords", true);
|
|
17641
|
+
_defineProperty(this, "_exportingSVG", false);
|
|
17654
17642
|
this.setOptions(options);
|
|
17655
17643
|
this.x1 = x1;
|
|
17656
17644
|
this.x2 = x2;
|
|
17657
17645
|
this.y1 = y1;
|
|
17658
17646
|
this.y2 = y2;
|
|
17647
|
+
if (options.hitStrokeWidth !== undefined) {
|
|
17648
|
+
this.hitStrokeWidth = options.hitStrokeWidth;
|
|
17649
|
+
}
|
|
17650
|
+
this.hasBorders = false;
|
|
17651
|
+
this.hasControls = true;
|
|
17652
|
+
this.selectable = true;
|
|
17653
|
+
this.hoverCursor = 'move';
|
|
17654
|
+
this.perPixelTargetFind = false;
|
|
17655
|
+
this.strokeLineCap = 'butt';
|
|
17659
17656
|
this._setWidthHeight();
|
|
17660
17657
|
const {
|
|
17661
17658
|
left,
|
|
@@ -17663,129 +17660,384 @@ class Line extends FabricObject {
|
|
|
17663
17660
|
} = options;
|
|
17664
17661
|
typeof left === 'number' && this.set(LEFT, left);
|
|
17665
17662
|
typeof top === 'number' && this.set(TOP, top);
|
|
17663
|
+
this._setupLineControls();
|
|
17664
|
+
}
|
|
17665
|
+
_setupLineControls() {
|
|
17666
|
+
this.controls = {
|
|
17667
|
+
p1: new Control({
|
|
17668
|
+
x: 0,
|
|
17669
|
+
y: 0,
|
|
17670
|
+
cursorStyle: 'move',
|
|
17671
|
+
actionHandler: this._endpointActionHandler.bind(this),
|
|
17672
|
+
positionHandler: this._p1PositionHandler.bind(this),
|
|
17673
|
+
render: this._renderEndpointControl.bind(this),
|
|
17674
|
+
sizeX: 12,
|
|
17675
|
+
sizeY: 12
|
|
17676
|
+
}),
|
|
17677
|
+
p2: new Control({
|
|
17678
|
+
x: 0,
|
|
17679
|
+
y: 0,
|
|
17680
|
+
cursorStyle: 'move',
|
|
17681
|
+
actionHandler: this._endpointActionHandler.bind(this),
|
|
17682
|
+
positionHandler: this._p2PositionHandler.bind(this),
|
|
17683
|
+
render: this._renderEndpointControl.bind(this),
|
|
17684
|
+
sizeX: 12,
|
|
17685
|
+
sizeY: 12
|
|
17686
|
+
})
|
|
17687
|
+
};
|
|
17688
|
+
}
|
|
17689
|
+
_p1PositionHandler() {
|
|
17690
|
+
return new Point(this.x1, this.y1).transform(this.getViewportTransform());
|
|
17691
|
+
}
|
|
17692
|
+
_p2PositionHandler() {
|
|
17693
|
+
return new Point(this.x2, this.y2).transform(this.getViewportTransform());
|
|
17694
|
+
}
|
|
17695
|
+
_renderEndpointControl(ctx, left, top) {
|
|
17696
|
+
const size = 12;
|
|
17697
|
+
ctx.save();
|
|
17698
|
+
ctx.fillStyle = '#007bff';
|
|
17699
|
+
ctx.strokeStyle = '#ffffff';
|
|
17700
|
+
ctx.lineWidth = 2;
|
|
17701
|
+
ctx.beginPath();
|
|
17702
|
+
ctx.arc(left, top, size / 2, 0, 2 * Math.PI);
|
|
17703
|
+
ctx.fill();
|
|
17704
|
+
ctx.stroke();
|
|
17705
|
+
ctx.restore();
|
|
17706
|
+
}
|
|
17707
|
+
drawBorders(ctx) {
|
|
17708
|
+
let styleOverride = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
17709
|
+
if (this._useEndpointCoords) {
|
|
17710
|
+
this._drawLineBorders(ctx, styleOverride);
|
|
17711
|
+
return this;
|
|
17712
|
+
}
|
|
17713
|
+
return super.drawBorders(ctx, styleOverride, {});
|
|
17714
|
+
}
|
|
17715
|
+
_drawLineBorders(ctx) {
|
|
17716
|
+
var _this$canvas;
|
|
17717
|
+
let styleOverride = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
17718
|
+
const vpt = ((_this$canvas = this.canvas) === null || _this$canvas === void 0 ? void 0 : _this$canvas.viewportTransform) || [1, 0, 0, 1, 0, 0];
|
|
17719
|
+
ctx.save();
|
|
17720
|
+
ctx.setTransform(vpt[0], vpt[1], vpt[2], vpt[3], vpt[4], vpt[5]);
|
|
17721
|
+
ctx.strokeStyle = styleOverride.borderColor || this.borderColor || 'rgba(100, 200, 200, 0.5)';
|
|
17722
|
+
ctx.lineWidth = (this.strokeWidth || 1) + 5;
|
|
17723
|
+
ctx.lineCap = this.strokeLineCap || 'butt';
|
|
17724
|
+
ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
|
|
17725
|
+
ctx.beginPath();
|
|
17726
|
+
ctx.moveTo(this.x1, this.y1);
|
|
17727
|
+
ctx.lineTo(this.x2, this.y2);
|
|
17728
|
+
ctx.stroke();
|
|
17729
|
+
ctx.restore();
|
|
17730
|
+
}
|
|
17731
|
+
_renderControls(ctx) {
|
|
17732
|
+
let styleOverride = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
17733
|
+
ctx.save();
|
|
17734
|
+
ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
|
|
17735
|
+
this.drawControls(ctx, styleOverride);
|
|
17736
|
+
ctx.restore();
|
|
17737
|
+
}
|
|
17738
|
+
getBoundingRect() {
|
|
17739
|
+
if (this._useEndpointCoords) {
|
|
17740
|
+
const {
|
|
17741
|
+
x1,
|
|
17742
|
+
y1,
|
|
17743
|
+
x2,
|
|
17744
|
+
y2
|
|
17745
|
+
} = this;
|
|
17746
|
+
const effectiveStrokeWidth = this.hitStrokeWidth === 'auto' ? this.strokeWidth : this.hitStrokeWidth;
|
|
17747
|
+
const padding = Math.max(effectiveStrokeWidth / 2 + 5, 10);
|
|
17748
|
+
return {
|
|
17749
|
+
left: Math.min(x1, x2) - padding,
|
|
17750
|
+
top: Math.min(y1, y2) - padding,
|
|
17751
|
+
width: Math.abs(x2 - x1) + padding * 2 || padding * 2,
|
|
17752
|
+
height: Math.abs(y2 - y1) + padding * 2 || padding * 2
|
|
17753
|
+
};
|
|
17754
|
+
}
|
|
17755
|
+
return super.getBoundingRect();
|
|
17666
17756
|
}
|
|
17757
|
+
setCoords() {
|
|
17758
|
+
if (this._useEndpointCoords) {
|
|
17759
|
+
// Set width and height for hit detection and bounding box
|
|
17760
|
+
const effectiveStrokeWidth = this.hitStrokeWidth === 'auto' ? this.strokeWidth : this.hitStrokeWidth;
|
|
17761
|
+
const hitPadding = Math.max(effectiveStrokeWidth / 2 + 5, 10);
|
|
17762
|
+
this.width = Math.abs(this.x2 - this.x1) + hitPadding * 2;
|
|
17763
|
+
this.height = Math.abs(this.y2 - this.y1) + hitPadding * 2;
|
|
17667
17764
|
|
|
17668
|
-
|
|
17669
|
-
|
|
17670
|
-
|
|
17671
|
-
|
|
17672
|
-
|
|
17673
|
-
|
|
17674
|
-
|
|
17675
|
-
|
|
17676
|
-
x2,
|
|
17677
|
-
y2
|
|
17678
|
-
} = this;
|
|
17679
|
-
this.width = Math.abs(x2 - x1);
|
|
17680
|
-
this.height = Math.abs(y2 - y1);
|
|
17681
|
-
const {
|
|
17682
|
-
left,
|
|
17683
|
-
top,
|
|
17684
|
-
width,
|
|
17685
|
-
height
|
|
17686
|
-
} = makeBoundingBoxFromPoints([{
|
|
17687
|
-
x: x1,
|
|
17688
|
-
y: y1
|
|
17689
|
-
}, {
|
|
17690
|
-
x: x2,
|
|
17691
|
-
y: y2
|
|
17692
|
-
}]);
|
|
17693
|
-
const position = new Point(left + width / 2, top + height / 2);
|
|
17694
|
-
this.setPositionByOrigin(position, CENTER, CENTER);
|
|
17765
|
+
// Only update left/top if they haven't been explicitly set (e.g., during loading)
|
|
17766
|
+
if (this.left === 0 && this.top === 0) {
|
|
17767
|
+
const center = this._findCenterFromElement();
|
|
17768
|
+
this.left = center.x;
|
|
17769
|
+
this.top = center.y;
|
|
17770
|
+
}
|
|
17771
|
+
}
|
|
17772
|
+
super.setCoords();
|
|
17695
17773
|
}
|
|
17774
|
+
getCoords() {
|
|
17775
|
+
if (this._useEndpointCoords) {
|
|
17776
|
+
const deltaX = this.x2 - this.x1;
|
|
17777
|
+
const deltaY = this.y2 - this.y1;
|
|
17778
|
+
const length = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
|
17779
|
+
if (length === 0) {
|
|
17780
|
+
return super.getCoords();
|
|
17781
|
+
}
|
|
17782
|
+
const effectiveStrokeWidth = this.hitStrokeWidth === 'auto' ? this.strokeWidth : this.hitStrokeWidth;
|
|
17783
|
+
const halfWidth = Math.max(effectiveStrokeWidth / 2 + 2, 5);
|
|
17696
17784
|
|
|
17697
|
-
|
|
17698
|
-
|
|
17699
|
-
|
|
17700
|
-
|
|
17701
|
-
|
|
17785
|
+
// Unit vector perpendicular to line
|
|
17786
|
+
const perpX = -deltaY / length;
|
|
17787
|
+
const perpY = deltaX / length;
|
|
17788
|
+
|
|
17789
|
+
// Four corners of oriented rectangle
|
|
17790
|
+
return [new Point(this.x1 + perpX * halfWidth, this.y1 + perpY * halfWidth), new Point(this.x2 + perpX * halfWidth, this.y2 + perpY * halfWidth), new Point(this.x2 - perpX * halfWidth, this.y2 - perpY * halfWidth), new Point(this.x1 - perpX * halfWidth, this.y1 - perpY * halfWidth)];
|
|
17791
|
+
}
|
|
17792
|
+
return super.getCoords();
|
|
17793
|
+
}
|
|
17794
|
+
containsPoint(point) {
|
|
17795
|
+
if (this._useEndpointCoords) {
|
|
17796
|
+
var _this$canvas2;
|
|
17797
|
+
if (((_this$canvas2 = this.canvas) === null || _this$canvas2 === void 0 ? void 0 : _this$canvas2.getActiveObject()) === this) {
|
|
17798
|
+
return super.containsPoint(point);
|
|
17799
|
+
}
|
|
17800
|
+
const distance = this._distanceToLineSegment(point.x, point.y);
|
|
17801
|
+
const effectiveStrokeWidth = this.hitStrokeWidth === 'auto' ? this.strokeWidth : this.hitStrokeWidth || 1;
|
|
17802
|
+
const tolerance = Math.max(effectiveStrokeWidth / 2 + 2, 5);
|
|
17803
|
+
return distance <= tolerance;
|
|
17804
|
+
}
|
|
17805
|
+
return super.containsPoint(point);
|
|
17806
|
+
}
|
|
17807
|
+
_distanceToLineSegment(px, py) {
|
|
17808
|
+
const x1 = this.x1,
|
|
17809
|
+
y1 = this.y1,
|
|
17810
|
+
x2 = this.x2,
|
|
17811
|
+
y2 = this.y2;
|
|
17812
|
+
const pd2 = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2);
|
|
17813
|
+
if (pd2 === 0) {
|
|
17814
|
+
return Math.sqrt((px - x1) * (px - x1) + (py - y1) * (py - y1));
|
|
17815
|
+
}
|
|
17816
|
+
const u = ((px - x1) * (x2 - x1) + (py - y1) * (y2 - y1)) / pd2;
|
|
17817
|
+
let closestX, closestY;
|
|
17818
|
+
if (u < 0) {
|
|
17819
|
+
closestX = x1;
|
|
17820
|
+
closestY = y1;
|
|
17821
|
+
} else if (u > 1) {
|
|
17822
|
+
closestX = x2;
|
|
17823
|
+
closestY = y2;
|
|
17824
|
+
} else {
|
|
17825
|
+
closestX = x1 + u * (x2 - x1);
|
|
17826
|
+
closestY = y1 + u * (y2 - y1);
|
|
17827
|
+
}
|
|
17828
|
+
return Math.sqrt((px - closestX) * (px - closestX) + (py - closestY) * (py - closestY));
|
|
17829
|
+
}
|
|
17830
|
+
_endpointActionHandler(eventData, transformData, x, y) {
|
|
17831
|
+
var _this$canvas4;
|
|
17832
|
+
const controlKey = transformData.corner;
|
|
17833
|
+
const pointer = new Point(x, y);
|
|
17834
|
+
let newX = pointer.x;
|
|
17835
|
+
let newY = pointer.y;
|
|
17836
|
+
if (eventData.shiftKey) {
|
|
17837
|
+
const otherControl = controlKey === 'p1' ? 'p2' : 'p1';
|
|
17838
|
+
const otherX = this[otherControl === 'p1' ? 'x1' : 'x2'];
|
|
17839
|
+
const otherY = this[otherControl === 'p1' ? 'y1' : 'y2'];
|
|
17840
|
+
const snapped = this._snapToAngle(otherX, otherY, newX, newY);
|
|
17841
|
+
newX = snapped.x;
|
|
17842
|
+
newY = snapped.y;
|
|
17843
|
+
}
|
|
17844
|
+
if (this._useEndpointCoords) {
|
|
17845
|
+
var _this$canvas3;
|
|
17846
|
+
if (controlKey === 'p1') {
|
|
17847
|
+
this.x1 = newX;
|
|
17848
|
+
this.y1 = newY;
|
|
17849
|
+
} else if (controlKey === 'p2') {
|
|
17850
|
+
this.x2 = newX;
|
|
17851
|
+
this.y2 = newY;
|
|
17852
|
+
}
|
|
17853
|
+
|
|
17854
|
+
// Update gradient coordinates if stroke is a gradient (but not during SVG export)
|
|
17855
|
+
if (this.stroke instanceof Gradient && !this._exportingSVG) {
|
|
17856
|
+
this.stroke.coords.x1 = this.x1;
|
|
17857
|
+
this.stroke.coords.y1 = this.y1;
|
|
17858
|
+
this.stroke.coords.x2 = this.x2;
|
|
17859
|
+
this.stroke.coords.y2 = this.y2;
|
|
17860
|
+
}
|
|
17861
|
+
this.dirty = true;
|
|
17862
|
+
this.setCoords();
|
|
17863
|
+
(_this$canvas3 = this.canvas) === null || _this$canvas3 === void 0 || _this$canvas3.requestRenderAll();
|
|
17864
|
+
return true;
|
|
17865
|
+
}
|
|
17866
|
+
|
|
17867
|
+
// Fallback for old system
|
|
17868
|
+
this._updatingEndpoints = true;
|
|
17869
|
+
if (controlKey === 'p1') {
|
|
17870
|
+
this.x1 = newX;
|
|
17871
|
+
this.y1 = newY;
|
|
17872
|
+
} else if (controlKey === 'p2') {
|
|
17873
|
+
this.x2 = newX;
|
|
17874
|
+
this.y2 = newY;
|
|
17875
|
+
}
|
|
17876
|
+
this._setWidthHeight();
|
|
17877
|
+
this.dirty = true;
|
|
17878
|
+
this._updatingEndpoints = false;
|
|
17879
|
+
(_this$canvas4 = this.canvas) === null || _this$canvas4 === void 0 || _this$canvas4.requestRenderAll();
|
|
17880
|
+
this.fire('modified', {
|
|
17881
|
+
transform: transformData,
|
|
17882
|
+
target: this,
|
|
17883
|
+
e: eventData
|
|
17884
|
+
});
|
|
17885
|
+
return true;
|
|
17886
|
+
}
|
|
17887
|
+
_snapToAngle(fromX, fromY, toX, toY) {
|
|
17888
|
+
const deltaX = toX - fromX;
|
|
17889
|
+
const deltaY = toY - fromY;
|
|
17890
|
+
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
|
17891
|
+
if (distance === 0) return {
|
|
17892
|
+
x: toX,
|
|
17893
|
+
y: toY
|
|
17894
|
+
};
|
|
17895
|
+
let angle = Math.atan2(deltaY, deltaX) * (180 / Math.PI);
|
|
17896
|
+
const snapIncrement = 15;
|
|
17897
|
+
const snappedAngle = Math.round(angle / snapIncrement) * snapIncrement;
|
|
17898
|
+
const snappedRadians = snappedAngle * (Math.PI / 180);
|
|
17899
|
+
return {
|
|
17900
|
+
x: fromX + Math.cos(snappedRadians) * distance,
|
|
17901
|
+
y: fromY + Math.sin(snappedRadians) * distance
|
|
17902
|
+
};
|
|
17903
|
+
}
|
|
17904
|
+
_setWidthHeight() {
|
|
17905
|
+
let skipReposition = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
|
|
17906
|
+
this.width = Math.abs(this.x2 - this.x1) || 1;
|
|
17907
|
+
this.height = Math.abs(this.y2 - this.y1) || 1;
|
|
17908
|
+
if (!skipReposition && !this._updatingEndpoints) {
|
|
17909
|
+
const {
|
|
17910
|
+
left,
|
|
17911
|
+
top,
|
|
17912
|
+
width,
|
|
17913
|
+
height
|
|
17914
|
+
} = makeBoundingBoxFromPoints([{
|
|
17915
|
+
x: this.x1,
|
|
17916
|
+
y: this.y1
|
|
17917
|
+
}, {
|
|
17918
|
+
x: this.x2,
|
|
17919
|
+
y: this.y2
|
|
17920
|
+
}]);
|
|
17921
|
+
this.setPositionByOrigin(new Point(left + width / 2, top + height / 2), CENTER, CENTER);
|
|
17922
|
+
}
|
|
17923
|
+
}
|
|
17702
17924
|
_set(key, value) {
|
|
17925
|
+
const oldLeft = this.left;
|
|
17926
|
+
const oldTop = this.top;
|
|
17703
17927
|
super._set(key, value);
|
|
17704
17928
|
if (coordProps.includes(key)) {
|
|
17705
|
-
// this doesn't make sense very much, since setting x1 when top or left
|
|
17706
|
-
// are already set, is just going to show a strange result since the
|
|
17707
|
-
// line will move way more than the developer expect.
|
|
17708
|
-
// in fabric5 it worked only when the line didn't have extra transformations,
|
|
17709
|
-
// in fabric6 too. With extra transform they behave bad in different ways.
|
|
17710
|
-
// This needs probably a good rework or a tutorial if you have to create a dynamic line
|
|
17711
17929
|
this._setWidthHeight();
|
|
17930
|
+
this.dirty = true;
|
|
17931
|
+
|
|
17932
|
+
// Update gradient coordinates if stroke is a gradient (but not during SVG export)
|
|
17933
|
+
if (this.stroke instanceof Gradient && !this._exportingSVG) {
|
|
17934
|
+
this.stroke.coords.x1 = this.x1;
|
|
17935
|
+
this.stroke.coords.y1 = this.y1;
|
|
17936
|
+
this.stroke.coords.x2 = this.x2;
|
|
17937
|
+
this.stroke.coords.y2 = this.y2;
|
|
17938
|
+
}
|
|
17939
|
+
}
|
|
17940
|
+
if ((key === 'left' || key === 'top') && this.canvas && !this._updatingEndpoints) {
|
|
17941
|
+
const deltaX = this.left - oldLeft;
|
|
17942
|
+
const deltaY = this.top - oldTop;
|
|
17943
|
+
if (deltaX !== 0 || deltaY !== 0) {
|
|
17944
|
+
this._updatingEndpoints = true;
|
|
17945
|
+
this.x1 += deltaX;
|
|
17946
|
+
this.y1 += deltaY;
|
|
17947
|
+
this.x2 += deltaX;
|
|
17948
|
+
this.y2 += deltaY;
|
|
17949
|
+
|
|
17950
|
+
// Update gradient coordinates if stroke is a gradient
|
|
17951
|
+
if (this.stroke instanceof Gradient) {
|
|
17952
|
+
this.stroke.coords.x1 = this.x1;
|
|
17953
|
+
this.stroke.coords.y1 = this.y1;
|
|
17954
|
+
this.stroke.coords.x2 = this.x2;
|
|
17955
|
+
this.stroke.coords.y2 = this.y2;
|
|
17956
|
+
}
|
|
17957
|
+
this._updatingEndpoints = false;
|
|
17958
|
+
}
|
|
17712
17959
|
}
|
|
17713
17960
|
return this;
|
|
17714
17961
|
}
|
|
17715
|
-
|
|
17716
|
-
|
|
17717
|
-
|
|
17718
|
-
|
|
17719
|
-
|
|
17962
|
+
render(ctx) {
|
|
17963
|
+
if (this._useEndpointCoords) {
|
|
17964
|
+
this._renderDirectly(ctx);
|
|
17965
|
+
return;
|
|
17966
|
+
}
|
|
17967
|
+
super.render(ctx);
|
|
17968
|
+
}
|
|
17969
|
+
_renderDirectly(ctx) {
|
|
17970
|
+
if (!this.visible) return;
|
|
17971
|
+
ctx.save();
|
|
17972
|
+
ctx.globalAlpha = this.opacity;
|
|
17973
|
+
ctx.lineWidth = this.strokeWidth;
|
|
17974
|
+
ctx.lineCap = this.strokeLineCap || 'butt';
|
|
17975
|
+
ctx.beginPath();
|
|
17976
|
+
ctx.moveTo(this.x1, this.y1);
|
|
17977
|
+
ctx.lineTo(this.x2, this.y2);
|
|
17978
|
+
const origStrokeStyle = ctx.strokeStyle;
|
|
17979
|
+
if (isFiller(this.stroke)) {
|
|
17980
|
+
ctx.strokeStyle = this.stroke.toLive(ctx);
|
|
17981
|
+
} else {
|
|
17982
|
+
var _this$stroke;
|
|
17983
|
+
ctx.strokeStyle = ((_this$stroke = this.stroke) === null || _this$stroke === void 0 ? void 0 : _this$stroke.toString()) || '#000';
|
|
17984
|
+
}
|
|
17985
|
+
ctx.stroke();
|
|
17986
|
+
ctx.strokeStyle = origStrokeStyle;
|
|
17987
|
+
ctx.restore();
|
|
17988
|
+
}
|
|
17720
17989
|
_render(ctx) {
|
|
17990
|
+
if (this._useEndpointCoords) return;
|
|
17721
17991
|
ctx.beginPath();
|
|
17722
17992
|
const p = this.calcLinePoints();
|
|
17723
17993
|
ctx.moveTo(p.x1, p.y1);
|
|
17724
17994
|
ctx.lineTo(p.x2, p.y2);
|
|
17725
17995
|
ctx.lineWidth = this.strokeWidth;
|
|
17726
|
-
|
|
17727
|
-
// TODO: test this
|
|
17728
|
-
// make sure setting "fill" changes color of a line
|
|
17729
|
-
// (by copying fillStyle to strokeStyle, since line is stroked, not filled)
|
|
17730
17996
|
const origStrokeStyle = ctx.strokeStyle;
|
|
17731
17997
|
if (isFiller(this.stroke)) {
|
|
17732
17998
|
ctx.strokeStyle = this.stroke.toLive(ctx);
|
|
17733
|
-
} else {
|
|
17734
|
-
var _this$stroke;
|
|
17735
|
-
ctx.strokeStyle = (_this$stroke = this.stroke) !== null && _this$stroke !== void 0 ? _this$stroke : ctx.fillStyle;
|
|
17736
17999
|
}
|
|
17737
18000
|
this.stroke && this._renderStroke(ctx);
|
|
17738
18001
|
ctx.strokeStyle = origStrokeStyle;
|
|
17739
18002
|
}
|
|
17740
|
-
|
|
17741
|
-
/**
|
|
17742
|
-
* This function is an helper for svg import. it returns the center of the object in the svg
|
|
17743
|
-
* untransformed coordinates
|
|
17744
|
-
* @private
|
|
17745
|
-
* @return {Point} center point from element coordinates
|
|
17746
|
-
*/
|
|
17747
18003
|
_findCenterFromElement() {
|
|
17748
18004
|
return new Point((this.x1 + this.x2) / 2, (this.y1 + this.y2) / 2);
|
|
17749
18005
|
}
|
|
17750
|
-
|
|
17751
|
-
/**
|
|
17752
|
-
* Returns object representation of an instance
|
|
17753
|
-
* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
|
|
17754
|
-
* @return {Object} object representation of an instance
|
|
17755
|
-
*/
|
|
17756
18006
|
toObject() {
|
|
17757
18007
|
let propertiesToInclude = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
|
|
18008
|
+
if (this._useEndpointCoords) {
|
|
18009
|
+
return {
|
|
18010
|
+
...super.toObject(propertiesToInclude),
|
|
18011
|
+
x1: this.x1,
|
|
18012
|
+
y1: this.y1,
|
|
18013
|
+
x2: this.x2,
|
|
18014
|
+
y2: this.y2
|
|
18015
|
+
};
|
|
18016
|
+
}
|
|
17758
18017
|
return {
|
|
17759
18018
|
...super.toObject(propertiesToInclude),
|
|
17760
18019
|
...this.calcLinePoints()
|
|
17761
18020
|
};
|
|
17762
18021
|
}
|
|
17763
|
-
|
|
17764
|
-
/*
|
|
17765
|
-
* Calculate object dimensions from its properties
|
|
17766
|
-
* @private
|
|
17767
|
-
*/
|
|
17768
18022
|
_getNonTransformedDimensions() {
|
|
17769
18023
|
const dim = super._getNonTransformedDimensions();
|
|
17770
|
-
if (this.strokeLineCap === '
|
|
17771
|
-
|
|
17772
|
-
|
|
17773
|
-
}
|
|
17774
|
-
if (this.height === 0) {
|
|
17775
|
-
dim.x -= this.strokeWidth;
|
|
17776
|
-
}
|
|
18024
|
+
if (this.strokeLineCap === 'round') {
|
|
18025
|
+
dim.x += this.strokeWidth;
|
|
18026
|
+
dim.y += this.strokeWidth;
|
|
17777
18027
|
}
|
|
17778
18028
|
return dim;
|
|
17779
18029
|
}
|
|
17780
|
-
|
|
17781
|
-
/**
|
|
17782
|
-
* Recalculates line points given width and height
|
|
17783
|
-
* Those points are simply placed around the center,
|
|
17784
|
-
* This is not useful outside internal render functions and svg output
|
|
17785
|
-
* Is not meant to be for the developer.
|
|
17786
|
-
* @private
|
|
17787
|
-
*/
|
|
17788
18030
|
calcLinePoints() {
|
|
18031
|
+
if (this._updatingEndpoints) {
|
|
18032
|
+
const centerX = (this.x1 + this.x2) / 2;
|
|
18033
|
+
const centerY = (this.y1 + this.y2) / 2;
|
|
18034
|
+
return {
|
|
18035
|
+
x1: this.x1 - centerX,
|
|
18036
|
+
y1: this.y1 - centerY,
|
|
18037
|
+
x2: this.x2 - centerX,
|
|
18038
|
+
y2: this.y2 - centerY
|
|
18039
|
+
};
|
|
18040
|
+
}
|
|
17789
18041
|
const {
|
|
17790
18042
|
x1: _x1,
|
|
17791
18043
|
x2: _x2,
|
|
@@ -17794,48 +18046,64 @@ class Line extends FabricObject {
|
|
|
17794
18046
|
width,
|
|
17795
18047
|
height
|
|
17796
18048
|
} = this;
|
|
17797
|
-
const xMult = _x1 <= _x2 ? -1 : 1
|
|
17798
|
-
|
|
17799
|
-
x1 = xMult * width / 2,
|
|
17800
|
-
y1 = yMult * height / 2,
|
|
17801
|
-
x2 = xMult * -width / 2,
|
|
17802
|
-
y2 = yMult * -height / 2;
|
|
18049
|
+
const xMult = _x1 <= _x2 ? -1 : 1;
|
|
18050
|
+
const yMult = _y1 <= _y2 ? -1 : 1;
|
|
17803
18051
|
return {
|
|
17804
|
-
x1,
|
|
17805
|
-
|
|
17806
|
-
|
|
17807
|
-
y2
|
|
18052
|
+
x1: xMult * width / 2,
|
|
18053
|
+
y1: yMult * height / 2,
|
|
18054
|
+
x2: xMult * -width / 2,
|
|
18055
|
+
y2: yMult * -height / 2
|
|
17808
18056
|
};
|
|
17809
18057
|
}
|
|
17810
|
-
|
|
17811
|
-
/* _FROM_SVG_START_ */
|
|
17812
|
-
|
|
17813
|
-
/**
|
|
17814
|
-
* Returns svg representation of an instance
|
|
17815
|
-
* @return {Array} an array of strings with the specific svg representation
|
|
17816
|
-
* of the instance
|
|
17817
|
-
*/
|
|
17818
18058
|
_toSVG() {
|
|
17819
|
-
|
|
17820
|
-
|
|
17821
|
-
|
|
17822
|
-
|
|
17823
|
-
|
|
17824
|
-
|
|
17825
|
-
|
|
18059
|
+
if (this._useEndpointCoords) {
|
|
18060
|
+
// Use absolute coordinates to bypass all Fabric.js transforms
|
|
18061
|
+
// Handle gradients manually for proper SVG export
|
|
18062
|
+
let strokeAttr = '';
|
|
18063
|
+
if (this.stroke instanceof Gradient) {
|
|
18064
|
+
// Let Fabric.js handle gradient definition, but we'll use the reference
|
|
18065
|
+
strokeAttr = `stroke="url(#${this.stroke.id})"`;
|
|
18066
|
+
} else {
|
|
18067
|
+
strokeAttr = `stroke="${this.stroke || 'none'}"`;
|
|
18068
|
+
}
|
|
18069
|
+
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`];
|
|
18070
|
+
} else {
|
|
18071
|
+
// Use standard calcLinePoints for legacy mode
|
|
18072
|
+
const {
|
|
18073
|
+
x1,
|
|
18074
|
+
x2,
|
|
18075
|
+
y1,
|
|
18076
|
+
y2
|
|
18077
|
+
} = this.calcLinePoints();
|
|
18078
|
+
return ['<line ', 'COMMON_PARTS', `x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" />\n`];
|
|
18079
|
+
}
|
|
17826
18080
|
}
|
|
18081
|
+
toSVG(reviver) {
|
|
18082
|
+
if (this._useEndpointCoords) {
|
|
18083
|
+
// For endpoint coords, we need to bypass transforms but still allow gradients
|
|
18084
|
+
// Let's temporarily disable transforms during SVG generation
|
|
18085
|
+
const originalLeft = this.left;
|
|
18086
|
+
const originalTop = this.top;
|
|
17827
18087
|
|
|
17828
|
-
|
|
17829
|
-
|
|
17830
|
-
|
|
17831
|
-
*/
|
|
18088
|
+
// Set position to center of line for gradient calculation
|
|
18089
|
+
this.left = (this.x1 + this.x2) / 2;
|
|
18090
|
+
this.top = (this.y1 + this.y2) / 2;
|
|
17832
18091
|
|
|
17833
|
-
|
|
17834
|
-
|
|
17835
|
-
|
|
17836
|
-
|
|
17837
|
-
|
|
17838
|
-
|
|
18092
|
+
// Get the SVG with standard system (for gradient handling)
|
|
18093
|
+
const standardSVG = super.toSVG(reviver);
|
|
18094
|
+
|
|
18095
|
+
// Restore original position
|
|
18096
|
+
this.left = originalLeft;
|
|
18097
|
+
this.top = originalTop;
|
|
18098
|
+
|
|
18099
|
+
// Extract gradient definition and clean up the line element
|
|
18100
|
+
// Remove the transform wrapper and update coordinates
|
|
18101
|
+
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}"`);
|
|
18102
|
+
return cleanSVG;
|
|
18103
|
+
}
|
|
18104
|
+
// Use default behavior for legacy mode
|
|
18105
|
+
return super.toSVG(reviver);
|
|
18106
|
+
}
|
|
17839
18107
|
static async fromElement(element, options, cssRules) {
|
|
17840
18108
|
const {
|
|
17841
18109
|
x1 = 0,
|
|
@@ -17846,14 +18114,6 @@ class Line extends FabricObject {
|
|
|
17846
18114
|
} = parseAttributes(element, this.ATTRIBUTE_NAMES, cssRules);
|
|
17847
18115
|
return new this([x1, y1, x2, y2], parsedAttributes);
|
|
17848
18116
|
}
|
|
17849
|
-
|
|
17850
|
-
/* _FROM_SVG_END_ */
|
|
17851
|
-
|
|
17852
|
-
/**
|
|
17853
|
-
* Returns Line instance from an object representation
|
|
17854
|
-
* @param {Object} object Object to create an instance from
|
|
17855
|
-
* @returns {Promise<Line>}
|
|
17856
|
-
*/
|
|
17857
18117
|
static fromObject(_ref) {
|
|
17858
18118
|
let {
|
|
17859
18119
|
x1,
|
|
@@ -17870,32 +18130,195 @@ class Line extends FabricObject {
|
|
|
17870
18130
|
});
|
|
17871
18131
|
}
|
|
17872
18132
|
}
|
|
18133
|
+
_defineProperty(Line, "type", 'Line');
|
|
18134
|
+
_defineProperty(Line, "cacheProperties", [...cacheProperties, ...coordProps]);
|
|
18135
|
+
_defineProperty(Line, "ATTRIBUTE_NAMES", SHARED_ATTRIBUTES.concat(coordProps));
|
|
18136
|
+
classRegistry.setClass(Line);
|
|
18137
|
+
classRegistry.setSVGClass(Line);
|
|
18138
|
+
|
|
18139
|
+
/**
|
|
18140
|
+
* Calculate the distance between two points
|
|
18141
|
+
*/
|
|
18142
|
+
function pointDistance(p1, p2) {
|
|
18143
|
+
return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
|
|
18144
|
+
}
|
|
18145
|
+
|
|
18146
|
+
/**
|
|
18147
|
+
* Normalize a vector
|
|
18148
|
+
*/
|
|
18149
|
+
function normalizeVector(vector) {
|
|
18150
|
+
const length = Math.sqrt(vector.x * vector.x + vector.y * vector.y);
|
|
18151
|
+
if (length === 0) return {
|
|
18152
|
+
x: 0,
|
|
18153
|
+
y: 0
|
|
18154
|
+
};
|
|
18155
|
+
return {
|
|
18156
|
+
x: vector.x / length,
|
|
18157
|
+
y: vector.y / length
|
|
18158
|
+
};
|
|
18159
|
+
}
|
|
18160
|
+
|
|
17873
18161
|
/**
|
|
17874
|
-
*
|
|
17875
|
-
* @type number
|
|
18162
|
+
* Get the maximum allowed radius for a corner based on adjacent edge lengths
|
|
17876
18163
|
*/
|
|
18164
|
+
function getMaxRadius(prevPoint, currentPoint, nextPoint) {
|
|
18165
|
+
const dist1 = pointDistance(prevPoint, currentPoint);
|
|
18166
|
+
const dist2 = pointDistance(currentPoint, nextPoint);
|
|
18167
|
+
return Math.min(dist1, dist2) / 2;
|
|
18168
|
+
}
|
|
18169
|
+
|
|
17877
18170
|
/**
|
|
17878
|
-
*
|
|
17879
|
-
* @type number
|
|
18171
|
+
* Calculate rounded corner data for a single corner
|
|
17880
18172
|
*/
|
|
18173
|
+
function calculateRoundedCorner(prevPoint, currentPoint, nextPoint, radius) {
|
|
18174
|
+
// Calculate edge vectors
|
|
18175
|
+
const edge1 = {
|
|
18176
|
+
x: currentPoint.x - prevPoint.x,
|
|
18177
|
+
y: currentPoint.y - prevPoint.y
|
|
18178
|
+
};
|
|
18179
|
+
const edge2 = {
|
|
18180
|
+
x: nextPoint.x - currentPoint.x,
|
|
18181
|
+
y: nextPoint.y - currentPoint.y
|
|
18182
|
+
};
|
|
18183
|
+
|
|
18184
|
+
// Normalize edge vectors
|
|
18185
|
+
const norm1 = normalizeVector(edge1);
|
|
18186
|
+
const norm2 = normalizeVector(edge2);
|
|
18187
|
+
|
|
18188
|
+
// Calculate the maximum allowed radius
|
|
18189
|
+
const maxRadius = getMaxRadius(prevPoint, currentPoint, nextPoint);
|
|
18190
|
+
const actualRadius = Math.min(radius, maxRadius);
|
|
18191
|
+
|
|
18192
|
+
// Calculate start and end points of the rounded corner
|
|
18193
|
+
const startPoint = {
|
|
18194
|
+
x: currentPoint.x - norm1.x * actualRadius,
|
|
18195
|
+
y: currentPoint.y - norm1.y * actualRadius
|
|
18196
|
+
};
|
|
18197
|
+
const endPoint = {
|
|
18198
|
+
x: currentPoint.x + norm2.x * actualRadius,
|
|
18199
|
+
y: currentPoint.y + norm2.y * actualRadius
|
|
18200
|
+
};
|
|
18201
|
+
|
|
18202
|
+
// Calculate control points for bezier curve
|
|
18203
|
+
// Using the magic number kRect for optimal circular approximation
|
|
18204
|
+
const controlOffset = actualRadius * kRect;
|
|
18205
|
+
const cp1 = {
|
|
18206
|
+
x: startPoint.x + norm1.x * controlOffset,
|
|
18207
|
+
y: startPoint.y + norm1.y * controlOffset
|
|
18208
|
+
};
|
|
18209
|
+
const cp2 = {
|
|
18210
|
+
x: endPoint.x - norm2.x * controlOffset,
|
|
18211
|
+
y: endPoint.y - norm2.y * controlOffset
|
|
18212
|
+
};
|
|
18213
|
+
return {
|
|
18214
|
+
corner: currentPoint,
|
|
18215
|
+
start: startPoint,
|
|
18216
|
+
end: endPoint,
|
|
18217
|
+
cp1,
|
|
18218
|
+
cp2,
|
|
18219
|
+
actualRadius
|
|
18220
|
+
};
|
|
18221
|
+
}
|
|
18222
|
+
|
|
17881
18223
|
/**
|
|
17882
|
-
*
|
|
17883
|
-
|
|
18224
|
+
* Apply corner radius to a polygon defined by points
|
|
18225
|
+
*/
|
|
18226
|
+
function applyCornerRadiusToPolygon(points, radius) {
|
|
18227
|
+
let radiusAsPercentage = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
18228
|
+
if (points.length < 3) {
|
|
18229
|
+
throw new Error('Polygon must have at least 3 points');
|
|
18230
|
+
}
|
|
18231
|
+
|
|
18232
|
+
// Calculate bounding box if radius is percentage-based
|
|
18233
|
+
let actualRadius = radius;
|
|
18234
|
+
if (radiusAsPercentage) {
|
|
18235
|
+
const minX = Math.min(...points.map(p => p.x));
|
|
18236
|
+
const maxX = Math.max(...points.map(p => p.x));
|
|
18237
|
+
const minY = Math.min(...points.map(p => p.y));
|
|
18238
|
+
const maxY = Math.max(...points.map(p => p.y));
|
|
18239
|
+
const width = maxX - minX;
|
|
18240
|
+
const height = maxY - minY;
|
|
18241
|
+
const minDimension = Math.min(width, height);
|
|
18242
|
+
actualRadius = radius / 100 * minDimension;
|
|
18243
|
+
}
|
|
18244
|
+
const roundedCorners = [];
|
|
18245
|
+
for (let i = 0; i < points.length; i++) {
|
|
18246
|
+
const prevIndex = (i - 1 + points.length) % points.length;
|
|
18247
|
+
const nextIndex = (i + 1) % points.length;
|
|
18248
|
+
const prevPoint = points[prevIndex];
|
|
18249
|
+
const currentPoint = points[i];
|
|
18250
|
+
const nextPoint = points[nextIndex];
|
|
18251
|
+
const roundedCorner = calculateRoundedCorner(prevPoint, currentPoint, nextPoint, actualRadius);
|
|
18252
|
+
roundedCorners.push(roundedCorner);
|
|
18253
|
+
}
|
|
18254
|
+
return roundedCorners;
|
|
18255
|
+
}
|
|
18256
|
+
|
|
18257
|
+
/**
|
|
18258
|
+
* Render a rounded polygon to a canvas context
|
|
17884
18259
|
*/
|
|
18260
|
+
function renderRoundedPolygon(ctx, roundedCorners) {
|
|
18261
|
+
let closed = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
|
|
18262
|
+
if (roundedCorners.length === 0) return;
|
|
18263
|
+
ctx.beginPath();
|
|
18264
|
+
|
|
18265
|
+
// Start at the first corner's start point
|
|
18266
|
+
const firstCorner = roundedCorners[0];
|
|
18267
|
+
ctx.moveTo(firstCorner.start.x, firstCorner.start.y);
|
|
18268
|
+
for (let i = 0; i < roundedCorners.length; i++) {
|
|
18269
|
+
const corner = roundedCorners[i];
|
|
18270
|
+
const nextIndex = (i + 1) % roundedCorners.length;
|
|
18271
|
+
const nextCorner = roundedCorners[nextIndex];
|
|
18272
|
+
|
|
18273
|
+
// Draw the rounded corner using bezier curve
|
|
18274
|
+
ctx.bezierCurveTo(corner.cp1.x, corner.cp1.y, corner.cp2.x, corner.cp2.y, corner.end.x, corner.end.y);
|
|
18275
|
+
|
|
18276
|
+
// Draw line to next corner's start point (if not the last segment in open path)
|
|
18277
|
+
if (i < roundedCorners.length - 1 || closed) {
|
|
18278
|
+
ctx.lineTo(nextCorner.start.x, nextCorner.start.y);
|
|
18279
|
+
}
|
|
18280
|
+
}
|
|
18281
|
+
if (closed) {
|
|
18282
|
+
ctx.closePath();
|
|
18283
|
+
}
|
|
18284
|
+
}
|
|
18285
|
+
|
|
17885
18286
|
/**
|
|
17886
|
-
*
|
|
17887
|
-
* @type number
|
|
18287
|
+
* Generate SVG path data for a rounded polygon
|
|
17888
18288
|
*/
|
|
17889
|
-
|
|
17890
|
-
|
|
17891
|
-
|
|
17892
|
-
|
|
17893
|
-
|
|
18289
|
+
function generateRoundedPolygonPath(roundedCorners) {
|
|
18290
|
+
let closed = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
|
|
18291
|
+
if (roundedCorners.length === 0) return '';
|
|
18292
|
+
const pathData = [];
|
|
18293
|
+
const firstCorner = roundedCorners[0];
|
|
18294
|
+
|
|
18295
|
+
// Move to first corner's start point
|
|
18296
|
+
pathData.push(`M ${firstCorner.start.x} ${firstCorner.start.y}`);
|
|
18297
|
+
for (let i = 0; i < roundedCorners.length; i++) {
|
|
18298
|
+
const corner = roundedCorners[i];
|
|
18299
|
+
const nextIndex = (i + 1) % roundedCorners.length;
|
|
18300
|
+
const nextCorner = roundedCorners[nextIndex];
|
|
18301
|
+
|
|
18302
|
+
// Add bezier curve for the rounded corner
|
|
18303
|
+
pathData.push(`C ${corner.cp1.x} ${corner.cp1.y} ${corner.cp2.x} ${corner.cp2.y} ${corner.end.x} ${corner.end.y}`);
|
|
18304
|
+
|
|
18305
|
+
// Add line to next corner's start point (if not the last segment in open path)
|
|
18306
|
+
if (i < roundedCorners.length - 1 || closed) {
|
|
18307
|
+
pathData.push(`L ${nextCorner.start.x} ${nextCorner.start.y}`);
|
|
18308
|
+
}
|
|
18309
|
+
}
|
|
18310
|
+
if (closed) {
|
|
18311
|
+
pathData.push('Z');
|
|
18312
|
+
}
|
|
18313
|
+
return pathData.join(' ');
|
|
18314
|
+
}
|
|
17894
18315
|
|
|
17895
18316
|
const triangleDefaultValues = {
|
|
17896
18317
|
width: 100,
|
|
17897
|
-
height: 100
|
|
18318
|
+
height: 100,
|
|
18319
|
+
cornerRadius: 0
|
|
17898
18320
|
};
|
|
18321
|
+
const TRIANGLE_PROPS = ['cornerRadius'];
|
|
17899
18322
|
class Triangle extends FabricObject {
|
|
17900
18323
|
static getDefaults() {
|
|
17901
18324
|
return {
|
|
@@ -17914,34 +18337,90 @@ class Triangle extends FabricObject {
|
|
|
17914
18337
|
this.setOptions(options);
|
|
17915
18338
|
}
|
|
17916
18339
|
|
|
18340
|
+
/**
|
|
18341
|
+
* Get triangle points as an array of XY coordinates
|
|
18342
|
+
* @private
|
|
18343
|
+
*/
|
|
18344
|
+
_getTrianglePoints() {
|
|
18345
|
+
const widthBy2 = this.width / 2;
|
|
18346
|
+
const heightBy2 = this.height / 2;
|
|
18347
|
+
return [{
|
|
18348
|
+
x: -widthBy2,
|
|
18349
|
+
y: heightBy2
|
|
18350
|
+
},
|
|
18351
|
+
// bottom left
|
|
18352
|
+
{
|
|
18353
|
+
x: 0,
|
|
18354
|
+
y: -heightBy2
|
|
18355
|
+
},
|
|
18356
|
+
// top center
|
|
18357
|
+
{
|
|
18358
|
+
x: widthBy2,
|
|
18359
|
+
y: heightBy2
|
|
18360
|
+
} // bottom right
|
|
18361
|
+
];
|
|
18362
|
+
}
|
|
18363
|
+
|
|
17917
18364
|
/**
|
|
17918
18365
|
* @private
|
|
17919
18366
|
* @param {CanvasRenderingContext2D} ctx Context to render on
|
|
17920
18367
|
*/
|
|
17921
18368
|
_render(ctx) {
|
|
17922
|
-
|
|
17923
|
-
|
|
17924
|
-
|
|
17925
|
-
|
|
17926
|
-
|
|
17927
|
-
|
|
17928
|
-
|
|
18369
|
+
if (this.cornerRadius > 0) {
|
|
18370
|
+
// Render rounded triangle
|
|
18371
|
+
const points = this._getTrianglePoints();
|
|
18372
|
+
const roundedCorners = applyCornerRadiusToPolygon(points, this.cornerRadius);
|
|
18373
|
+
renderRoundedPolygon(ctx, roundedCorners, true);
|
|
18374
|
+
} else {
|
|
18375
|
+
// Render sharp triangle (original implementation)
|
|
18376
|
+
const widthBy2 = this.width / 2;
|
|
18377
|
+
const heightBy2 = this.height / 2;
|
|
18378
|
+
ctx.beginPath();
|
|
18379
|
+
ctx.moveTo(-widthBy2, heightBy2);
|
|
18380
|
+
ctx.lineTo(0, -heightBy2);
|
|
18381
|
+
ctx.lineTo(widthBy2, heightBy2);
|
|
18382
|
+
ctx.closePath();
|
|
18383
|
+
}
|
|
17929
18384
|
this._renderPaintInOrder(ctx);
|
|
17930
18385
|
}
|
|
17931
18386
|
|
|
18387
|
+
/**
|
|
18388
|
+
* Returns object representation of an instance
|
|
18389
|
+
* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
|
|
18390
|
+
* @return {Object} object representation of an instance
|
|
18391
|
+
*/
|
|
18392
|
+
toObject() {
|
|
18393
|
+
let propertiesToInclude = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
|
|
18394
|
+
return super.toObject([...TRIANGLE_PROPS, ...propertiesToInclude]);
|
|
18395
|
+
}
|
|
18396
|
+
|
|
17932
18397
|
/**
|
|
17933
18398
|
* Returns svg representation of an instance
|
|
17934
18399
|
* @return {Array} an array of strings with the specific svg representation
|
|
17935
18400
|
* of the instance
|
|
17936
18401
|
*/
|
|
17937
18402
|
_toSVG() {
|
|
17938
|
-
|
|
17939
|
-
|
|
17940
|
-
points =
|
|
17941
|
-
|
|
18403
|
+
if (this.cornerRadius > 0) {
|
|
18404
|
+
// Generate rounded triangle as path
|
|
18405
|
+
const points = this._getTrianglePoints();
|
|
18406
|
+
const roundedCorners = applyCornerRadiusToPolygon(points, this.cornerRadius);
|
|
18407
|
+
const pathData = generateRoundedPolygonPath(roundedCorners, true);
|
|
18408
|
+
return ['<path ', 'COMMON_PARTS', `d="${pathData}" />`];
|
|
18409
|
+
} else {
|
|
18410
|
+
// Original sharp triangle implementation
|
|
18411
|
+
const widthBy2 = this.width / 2;
|
|
18412
|
+
const heightBy2 = this.height / 2;
|
|
18413
|
+
const points = `${-widthBy2} ${heightBy2},0 ${-heightBy2},${widthBy2} ${heightBy2}`;
|
|
18414
|
+
return ['<polygon ', 'COMMON_PARTS', 'points="', points, '" />'];
|
|
18415
|
+
}
|
|
17942
18416
|
}
|
|
17943
18417
|
}
|
|
18418
|
+
/**
|
|
18419
|
+
* Corner radius for rounded triangle corners
|
|
18420
|
+
* @type Number
|
|
18421
|
+
*/
|
|
17944
18422
|
_defineProperty(Triangle, "type", 'Triangle');
|
|
18423
|
+
_defineProperty(Triangle, "cacheProperties", [...cacheProperties, ...TRIANGLE_PROPS]);
|
|
17945
18424
|
_defineProperty(Triangle, "ownDefaults", triangleDefaultValues);
|
|
17946
18425
|
classRegistry.setClass(Triangle);
|
|
17947
18426
|
classRegistry.setSVGClass(Triangle);
|
|
@@ -18106,7 +18585,8 @@ const polylineDefaultValues = {
|
|
|
18106
18585
|
/**
|
|
18107
18586
|
* @deprecated transient option soon to be removed in favor of a different design
|
|
18108
18587
|
*/
|
|
18109
|
-
exactBoundingBox: false
|
|
18588
|
+
exactBoundingBox: false,
|
|
18589
|
+
cornerRadius: 0
|
|
18110
18590
|
};
|
|
18111
18591
|
class Polyline extends FabricObject {
|
|
18112
18592
|
static getDefaults() {
|
|
@@ -18320,7 +18800,7 @@ class Polyline extends FabricObject {
|
|
|
18320
18800
|
toObject() {
|
|
18321
18801
|
let propertiesToInclude = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
|
|
18322
18802
|
return {
|
|
18323
|
-
...super.toObject(propertiesToInclude),
|
|
18803
|
+
...super.toObject(['cornerRadius', ...propertiesToInclude]),
|
|
18324
18804
|
points: this.points.map(_ref => {
|
|
18325
18805
|
let {
|
|
18326
18806
|
x,
|
|
@@ -18340,14 +18820,28 @@ class Polyline extends FabricObject {
|
|
|
18340
18820
|
* of the instance
|
|
18341
18821
|
*/
|
|
18342
18822
|
_toSVG() {
|
|
18343
|
-
|
|
18344
|
-
|
|
18345
|
-
|
|
18346
|
-
|
|
18347
|
-
|
|
18348
|
-
|
|
18823
|
+
if (this.cornerRadius > 0 && this.points.length >= 3) {
|
|
18824
|
+
// Generate rounded polygon/polyline as path
|
|
18825
|
+
const diffX = this.pathOffset.x;
|
|
18826
|
+
const diffY = this.pathOffset.y;
|
|
18827
|
+
const adjustedPoints = this.points.map(point => ({
|
|
18828
|
+
x: point.x - diffX,
|
|
18829
|
+
y: point.y - diffY
|
|
18830
|
+
}));
|
|
18831
|
+
const roundedCorners = applyCornerRadiusToPolygon(adjustedPoints, this.cornerRadius);
|
|
18832
|
+
const pathData = generateRoundedPolygonPath(roundedCorners, !this.isOpen());
|
|
18833
|
+
return ['<path ', 'COMMON_PARTS', `d="${pathData}" />\n`];
|
|
18834
|
+
} else {
|
|
18835
|
+
// Original sharp corners implementation
|
|
18836
|
+
const points = [];
|
|
18837
|
+
const diffX = this.pathOffset.x;
|
|
18838
|
+
const diffY = this.pathOffset.y;
|
|
18839
|
+
const NUM_FRACTION_DIGITS = config.NUM_FRACTION_DIGITS;
|
|
18840
|
+
for (let i = 0, len = this.points.length; i < len; i++) {
|
|
18841
|
+
points.push(toFixed(this.points[i].x - diffX, NUM_FRACTION_DIGITS), ',', toFixed(this.points[i].y - diffY, NUM_FRACTION_DIGITS), ' ');
|
|
18842
|
+
}
|
|
18843
|
+
return [`<${this.constructor.type.toLowerCase()} `, 'COMMON_PARTS', `points="${points.join('')}" />\n`];
|
|
18349
18844
|
}
|
|
18350
|
-
return [`<${this.constructor.type.toLowerCase()} `, 'COMMON_PARTS', `points="${points.join('')}" />\n`];
|
|
18351
18845
|
}
|
|
18352
18846
|
|
|
18353
18847
|
/**
|
|
@@ -18363,13 +18857,24 @@ class Polyline extends FabricObject {
|
|
|
18363
18857
|
// NaN comes from parseFloat of a empty string in parser
|
|
18364
18858
|
return;
|
|
18365
18859
|
}
|
|
18366
|
-
|
|
18367
|
-
|
|
18368
|
-
|
|
18369
|
-
|
|
18370
|
-
|
|
18860
|
+
if (this.cornerRadius > 0 && len >= 3) {
|
|
18861
|
+
// Render with rounded corners
|
|
18862
|
+
const adjustedPoints = this.points.map(point => ({
|
|
18863
|
+
x: point.x - x,
|
|
18864
|
+
y: point.y - y
|
|
18865
|
+
}));
|
|
18866
|
+
const roundedCorners = applyCornerRadiusToPolygon(adjustedPoints, this.cornerRadius);
|
|
18867
|
+
renderRoundedPolygon(ctx, roundedCorners, !this.isOpen());
|
|
18868
|
+
} else {
|
|
18869
|
+
// Original sharp corners implementation
|
|
18870
|
+
ctx.beginPath();
|
|
18871
|
+
ctx.moveTo(this.points[0].x - x, this.points[0].y - y);
|
|
18872
|
+
for (let i = 0; i < len; i++) {
|
|
18873
|
+
const point = this.points[i];
|
|
18874
|
+
ctx.lineTo(point.x - x, point.y - y);
|
|
18875
|
+
}
|
|
18876
|
+
!this.isOpen() && ctx.closePath();
|
|
18371
18877
|
}
|
|
18372
|
-
!this.isOpen() && ctx.closePath();
|
|
18373
18878
|
this._renderPaintInOrder(ctx);
|
|
18374
18879
|
}
|
|
18375
18880
|
|
|
@@ -18434,10 +18939,15 @@ class Polyline extends FabricObject {
|
|
|
18434
18939
|
* @type Boolean
|
|
18435
18940
|
* @default false
|
|
18436
18941
|
*/
|
|
18942
|
+
/**
|
|
18943
|
+
* Corner radius for rounded corners
|
|
18944
|
+
* @type Number
|
|
18945
|
+
* @default 0
|
|
18946
|
+
*/
|
|
18437
18947
|
_defineProperty(Polyline, "ownDefaults", polylineDefaultValues);
|
|
18438
18948
|
_defineProperty(Polyline, "type", 'Polyline');
|
|
18439
18949
|
_defineProperty(Polyline, "layoutProperties", [SKEW_X, SKEW_Y, 'strokeLineCap', 'strokeLineJoin', 'strokeMiterLimit', 'strokeWidth', 'strokeUniform', 'points']);
|
|
18440
|
-
_defineProperty(Polyline, "cacheProperties", [...cacheProperties, 'points']);
|
|
18950
|
+
_defineProperty(Polyline, "cacheProperties", [...cacheProperties, 'points', 'cornerRadius']);
|
|
18441
18951
|
_defineProperty(Polyline, "ATTRIBUTE_NAMES", [...SHARED_ATTRIBUTES]);
|
|
18442
18952
|
classRegistry.setClass(Polyline);
|
|
18443
18953
|
classRegistry.setSVGClass(Polyline);
|
|
@@ -18821,6 +19331,97 @@ function measureGraphemeWithKerning(grapheme, previousGrapheme, options, ctx) {
|
|
|
18821
19331
|
};
|
|
18822
19332
|
}
|
|
18823
19333
|
|
|
19334
|
+
/**
|
|
19335
|
+
* Get a representative character for font metrics measurement
|
|
19336
|
+
* Uses canvas to test which scripts the font actually supports
|
|
19337
|
+
*/
|
|
19338
|
+
function getRepresentativeCharacter(fontFamily) {
|
|
19339
|
+
const context = getMeasurementContext();
|
|
19340
|
+
|
|
19341
|
+
// Wait for font to be ready if possible
|
|
19342
|
+
if (typeof document !== 'undefined' && 'fonts' in document) {
|
|
19343
|
+
try {
|
|
19344
|
+
// Check if font is ready, if not, use fallback immediately
|
|
19345
|
+
if (!document.fonts.check(`16px ${fontFamily}`)) {
|
|
19346
|
+
return 'M'; // Use safe fallback while font loads
|
|
19347
|
+
}
|
|
19348
|
+
} catch (e) {
|
|
19349
|
+
// Font check failed, use fallback
|
|
19350
|
+
return 'M';
|
|
19351
|
+
}
|
|
19352
|
+
}
|
|
19353
|
+
|
|
19354
|
+
// Test characters for different scripts
|
|
19355
|
+
const testChars = [{
|
|
19356
|
+
char: 'م',
|
|
19357
|
+
script: 'Arabic'
|
|
19358
|
+
},
|
|
19359
|
+
// Arabic
|
|
19360
|
+
{
|
|
19361
|
+
char: 'א',
|
|
19362
|
+
script: 'Hebrew'
|
|
19363
|
+
},
|
|
19364
|
+
// Hebrew
|
|
19365
|
+
{
|
|
19366
|
+
char: 'अ',
|
|
19367
|
+
script: 'Devanagari'
|
|
19368
|
+
},
|
|
19369
|
+
// Hindi/Sanskrit
|
|
19370
|
+
{
|
|
19371
|
+
char: 'ا',
|
|
19372
|
+
script: 'Urdu'
|
|
19373
|
+
},
|
|
19374
|
+
// Urdu
|
|
19375
|
+
{
|
|
19376
|
+
char: 'ک',
|
|
19377
|
+
script: 'Persian'
|
|
19378
|
+
},
|
|
19379
|
+
// Persian
|
|
19380
|
+
{
|
|
19381
|
+
char: 'த',
|
|
19382
|
+
script: 'Tamil'
|
|
19383
|
+
},
|
|
19384
|
+
// Tamil
|
|
19385
|
+
{
|
|
19386
|
+
char: 'ก',
|
|
19387
|
+
script: 'Thai'
|
|
19388
|
+
},
|
|
19389
|
+
// Thai
|
|
19390
|
+
{
|
|
19391
|
+
char: 'М',
|
|
19392
|
+
script: 'Cyrillic'
|
|
19393
|
+
},
|
|
19394
|
+
// Cyrillic
|
|
19395
|
+
{
|
|
19396
|
+
char: 'Ω',
|
|
19397
|
+
script: 'Greek'
|
|
19398
|
+
},
|
|
19399
|
+
// Greek
|
|
19400
|
+
{
|
|
19401
|
+
char: 'M',
|
|
19402
|
+
script: 'Latin'
|
|
19403
|
+
} // Latin (fallback)
|
|
19404
|
+
];
|
|
19405
|
+
|
|
19406
|
+
// Set the font
|
|
19407
|
+
context.font = `16px ${fontFamily}`;
|
|
19408
|
+
|
|
19409
|
+
// Test each character to see which ones render properly
|
|
19410
|
+
// Use a more robust width check to avoid false positives
|
|
19411
|
+
const fallbackWidth = context.measureText('M').width;
|
|
19412
|
+
for (const test of testChars) {
|
|
19413
|
+
const metrics = context.measureText(test.char);
|
|
19414
|
+
|
|
19415
|
+
// Character is valid if it has width and isn't just a fallback glyph
|
|
19416
|
+
if (metrics.width > 0 && Math.abs(metrics.width - fallbackWidth) > 0.1) {
|
|
19417
|
+
return test.char;
|
|
19418
|
+
}
|
|
19419
|
+
}
|
|
19420
|
+
|
|
19421
|
+
// Fallback to Latin 'M'
|
|
19422
|
+
return 'M';
|
|
19423
|
+
}
|
|
19424
|
+
|
|
18824
19425
|
/**
|
|
18825
19426
|
* Get font metrics for layout calculations
|
|
18826
19427
|
*/
|
|
@@ -18834,8 +19435,9 @@ function getFontMetrics(options) {
|
|
|
18834
19435
|
const context = getMeasurementContext();
|
|
18835
19436
|
applyFontStyle(context, options);
|
|
18836
19437
|
|
|
18837
|
-
// Use
|
|
18838
|
-
const
|
|
19438
|
+
// Use representative character based on font's primary script
|
|
19439
|
+
const sample = getRepresentativeCharacter(options.fontFamily);
|
|
19440
|
+
const metrics = context.measureText(sample);
|
|
18839
19441
|
const fontSize = options.fontSize;
|
|
18840
19442
|
|
|
18841
19443
|
// Calculate metrics with fallbacks
|
|
@@ -18887,7 +19489,11 @@ function getFontDeclaration(options) {
|
|
|
18887
19489
|
} = options;
|
|
18888
19490
|
|
|
18889
19491
|
// Normalize font family (add quotes if needed)
|
|
18890
|
-
|
|
19492
|
+
let normalizedFamily = fontFamily.includes(' ') && !fontFamily.includes('"') && !fontFamily.includes("'") ? `"${fontFamily}"` : fontFamily;
|
|
19493
|
+
|
|
19494
|
+
// Note: Font fallbacks are handled in the rendering phase only
|
|
19495
|
+
// to avoid affecting measurement calculations for text wrapping
|
|
19496
|
+
|
|
18891
19497
|
return `${fontStyle} ${fontWeight} ${fontSize}px ${normalizedFamily}`;
|
|
18892
19498
|
}
|
|
18893
19499
|
|
|
@@ -19039,6 +19645,81 @@ const measurementCache = new MeasurementCache();
|
|
|
19039
19645
|
const kerningCache = new KerningCache();
|
|
19040
19646
|
const fontMetricsCache = new FontMetricsCache();
|
|
19041
19647
|
|
|
19648
|
+
// Set up font loading listener to clear caches when fonts change
|
|
19649
|
+
if (typeof document !== 'undefined' && 'fonts' in document) {
|
|
19650
|
+
document.fonts.addEventListener('loadingdone', () => {
|
|
19651
|
+
// Clear all caches when fonts finish loading
|
|
19652
|
+
clearAllCaches();
|
|
19653
|
+
});
|
|
19654
|
+
}
|
|
19655
|
+
|
|
19656
|
+
/**
|
|
19657
|
+
* Clear all measurement caches
|
|
19658
|
+
*/
|
|
19659
|
+
function clearAllCaches() {
|
|
19660
|
+
measurementCache.clear();
|
|
19661
|
+
kerningCache.clear();
|
|
19662
|
+
fontMetricsCache.clear();
|
|
19663
|
+
}
|
|
19664
|
+
|
|
19665
|
+
/**
|
|
19666
|
+
* Detect if a font lacks English glyph support
|
|
19667
|
+
* These fonts should use browser-native measurement instead of Fabric's character-by-character measurement
|
|
19668
|
+
*/
|
|
19669
|
+
function fontLacksEnglishGlyphs(fontFamily) {
|
|
19670
|
+
if (typeof document === 'undefined') return false;
|
|
19671
|
+
|
|
19672
|
+
// Known fonts that lack English glyphs
|
|
19673
|
+
const knownNonEnglishFonts = ['stv', 'arabic', 'naskh', 'thuluth', 'kufi', 'diwani', 'nastaliq', 'kufic', 'hijazi', 'madinah', 'makkah'];
|
|
19674
|
+
const lowerFontFamily = fontFamily.toLowerCase();
|
|
19675
|
+
|
|
19676
|
+
// Check known list first
|
|
19677
|
+
if (knownNonEnglishFonts.some(font => lowerFontFamily.includes(font))) {
|
|
19678
|
+
return true;
|
|
19679
|
+
}
|
|
19680
|
+
|
|
19681
|
+
// Dynamic glyph support detection
|
|
19682
|
+
const context = getMeasurementContext();
|
|
19683
|
+
context.font = `16px ${fontFamily}`;
|
|
19684
|
+
|
|
19685
|
+
// Test English characters
|
|
19686
|
+
const englishChars = ['A', 'B', 'C', 'a', 'b', 'c', 'M', 'W'];
|
|
19687
|
+
const fallbackFont = 'Arial, sans-serif';
|
|
19688
|
+
|
|
19689
|
+
// Measure with target font
|
|
19690
|
+
const targetWidths = englishChars.map(char => context.measureText(char).width);
|
|
19691
|
+
|
|
19692
|
+
// Measure with fallback font
|
|
19693
|
+
context.font = `16px ${fallbackFont}`;
|
|
19694
|
+
const fallbackWidths = englishChars.map(char => context.measureText(char).width);
|
|
19695
|
+
|
|
19696
|
+
// If most measurements are identical, the font likely doesn't have English glyphs
|
|
19697
|
+
let identicalCount = 0;
|
|
19698
|
+
for (let i = 0; i < englishChars.length; i++) {
|
|
19699
|
+
if (Math.abs(targetWidths[i] - fallbackWidths[i]) < 0.5) {
|
|
19700
|
+
identicalCount++;
|
|
19701
|
+
}
|
|
19702
|
+
}
|
|
19703
|
+
const lacksSupportThreshold = englishChars.length * 0.7; // 70% identical = lacks support
|
|
19704
|
+
const lacksSupport = identicalCount >= lacksSupportThreshold;
|
|
19705
|
+
return lacksSupport;
|
|
19706
|
+
}
|
|
19707
|
+
|
|
19708
|
+
// Cache for font glyph detection results
|
|
19709
|
+
const fontGlyphCache = new Map();
|
|
19710
|
+
|
|
19711
|
+
/**
|
|
19712
|
+
* Cached version of font glyph detection
|
|
19713
|
+
*/
|
|
19714
|
+
function fontLacksEnglishGlyphsCached(fontFamily) {
|
|
19715
|
+
if (fontGlyphCache.has(fontFamily)) {
|
|
19716
|
+
return fontGlyphCache.get(fontFamily);
|
|
19717
|
+
}
|
|
19718
|
+
const result = fontLacksEnglishGlyphs(fontFamily);
|
|
19719
|
+
fontGlyphCache.set(fontFamily, result);
|
|
19720
|
+
return result;
|
|
19721
|
+
}
|
|
19722
|
+
|
|
19042
19723
|
/**
|
|
19043
19724
|
* Unicode and Internationalization Support
|
|
19044
19725
|
*
|
|
@@ -20222,6 +20903,15 @@ class FabricText extends StyledText {
|
|
|
20222
20903
|
* Does not return dimensions.
|
|
20223
20904
|
*/
|
|
20224
20905
|
initDimensions() {
|
|
20906
|
+
// Check if font is ready for accurate measurements
|
|
20907
|
+
// Only block initialization if it's a critical font loading situation
|
|
20908
|
+
const fontReady = this._isFontReady();
|
|
20909
|
+
if (!fontReady && !this.initialized) {
|
|
20910
|
+
// Only schedule font loading on first initialization
|
|
20911
|
+
this._scheduleInitAfterFontLoad();
|
|
20912
|
+
// Continue with fallback measurements for now
|
|
20913
|
+
}
|
|
20914
|
+
|
|
20225
20915
|
// Use advanced layout if enabled
|
|
20226
20916
|
if (this.enableAdvancedLayout && !this.path) {
|
|
20227
20917
|
return this.initDimensionsAdvanced();
|
|
@@ -20238,7 +20928,21 @@ class FabricText extends StyledText {
|
|
|
20238
20928
|
}
|
|
20239
20929
|
if (this.textAlign.includes(JUSTIFY)) {
|
|
20240
20930
|
// once text is measured we need to make space fatter to make justified text.
|
|
20241
|
-
|
|
20931
|
+
// Ensure __charBounds exists before calling enlargeSpaces
|
|
20932
|
+
if (this.__charBounds && this.__charBounds.length > 0) {
|
|
20933
|
+
this.enlargeSpaces();
|
|
20934
|
+
} else {
|
|
20935
|
+
console.warn('⚠️ __charBounds not ready for justify alignment, deferring enlargeSpaces');
|
|
20936
|
+
// Defer the justify calculation until the next frame
|
|
20937
|
+
setTimeout(() => {
|
|
20938
|
+
if (this.__charBounds && this.__charBounds.length > 0 && this.enlargeSpaces) {
|
|
20939
|
+
var _this$canvas;
|
|
20940
|
+
console.log('🔧 Applying deferred justify alignment');
|
|
20941
|
+
this.enlargeSpaces();
|
|
20942
|
+
(_this$canvas = this.canvas) === null || _this$canvas === void 0 || _this$canvas.requestRenderAll();
|
|
20943
|
+
}
|
|
20944
|
+
}, 0);
|
|
20945
|
+
}
|
|
20242
20946
|
}
|
|
20243
20947
|
}
|
|
20244
20948
|
|
|
@@ -20247,8 +20951,9 @@ class FabricText extends StyledText {
|
|
|
20247
20951
|
*/
|
|
20248
20952
|
enlargeSpaces() {
|
|
20249
20953
|
let diffSpace, currentLineWidth, numberOfSpaces, accumulatedSpace, line, charBound, spaces;
|
|
20954
|
+
const isRtl = this.direction === 'rtl';
|
|
20250
20955
|
for (let i = 0, len = this._textLines.length; i < len; i++) {
|
|
20251
|
-
if (this.textAlign
|
|
20956
|
+
if (!this.textAlign.includes('justify') && (i === len - 1 || this.isEndOfWrapping(i))) {
|
|
20252
20957
|
continue;
|
|
20253
20958
|
}
|
|
20254
20959
|
accumulatedSpace = 0;
|
|
@@ -20257,15 +20962,47 @@ class FabricText extends StyledText {
|
|
|
20257
20962
|
if (currentLineWidth < this.width && (spaces = this.textLines[i].match(this._reSpacesAndTabs))) {
|
|
20258
20963
|
numberOfSpaces = spaces.length;
|
|
20259
20964
|
diffSpace = (this.width - currentLineWidth) / numberOfSpaces;
|
|
20260
|
-
|
|
20261
|
-
|
|
20262
|
-
|
|
20263
|
-
|
|
20264
|
-
|
|
20265
|
-
|
|
20266
|
-
|
|
20267
|
-
|
|
20268
|
-
|
|
20965
|
+
console.log(`🔧 EnlargeSpaces Line ${i}:`);
|
|
20966
|
+
console.log(` Current width: ${currentLineWidth}, Target: ${this.width}`);
|
|
20967
|
+
console.log(` Spaces: ${numberOfSpaces}, diffSpace: ${diffSpace.toFixed(2)}`);
|
|
20968
|
+
if (isRtl) {
|
|
20969
|
+
for (let j = 0; j < line.length; j++) {
|
|
20970
|
+
if (this._reSpaceAndTab.test(line[j])) ;
|
|
20971
|
+
}
|
|
20972
|
+
|
|
20973
|
+
// For RTL, we need to work backwards through the visual positions
|
|
20974
|
+
// but still update logical positions correctly
|
|
20975
|
+
let spaceCount = 0;
|
|
20976
|
+
for (let j = 0; j <= line.length; j++) {
|
|
20977
|
+
charBound = this.__charBounds[i][j];
|
|
20978
|
+
if (charBound) {
|
|
20979
|
+
if (this._reSpaceAndTab.test(line[j])) {
|
|
20980
|
+
charBound.width += diffSpace;
|
|
20981
|
+
charBound.kernedWidth += diffSpace;
|
|
20982
|
+
spaceCount++;
|
|
20983
|
+
}
|
|
20984
|
+
|
|
20985
|
+
// For RTL, shift all characters to the right by the total expansion
|
|
20986
|
+
// minus the expansion that comes after this character
|
|
20987
|
+
const remainingSpaces = numberOfSpaces - spaceCount;
|
|
20988
|
+
const shiftAmount = remainingSpaces * diffSpace;
|
|
20989
|
+
charBound.left += shiftAmount;
|
|
20990
|
+
}
|
|
20991
|
+
}
|
|
20992
|
+
} else {
|
|
20993
|
+
// LTR processing (original logic)
|
|
20994
|
+
for (let j = 0; j <= line.length; j++) {
|
|
20995
|
+
charBound = this.__charBounds[i][j];
|
|
20996
|
+
if (charBound) {
|
|
20997
|
+
if (this._reSpaceAndTab.test(line[j])) {
|
|
20998
|
+
charBound.width += diffSpace;
|
|
20999
|
+
charBound.kernedWidth += diffSpace;
|
|
21000
|
+
charBound.left += accumulatedSpace;
|
|
21001
|
+
accumulatedSpace += diffSpace;
|
|
21002
|
+
} else {
|
|
21003
|
+
charBound.left += accumulatedSpace;
|
|
21004
|
+
}
|
|
21005
|
+
}
|
|
20269
21006
|
}
|
|
20270
21007
|
}
|
|
20271
21008
|
}
|
|
@@ -20343,6 +21080,18 @@ class FabricText extends StyledText {
|
|
|
20343
21080
|
|
|
20344
21081
|
// Convert layout to legacy format for compatibility
|
|
20345
21082
|
this._convertLayoutToLegacyFormat(layout);
|
|
21083
|
+
|
|
21084
|
+
// Ensure justify alignment is properly applied for compatibility with legacy rendering
|
|
21085
|
+
if (this.textAlign.includes(JUSTIFY)) {
|
|
21086
|
+
// Force enlarge spaces after advanced layout calculation
|
|
21087
|
+
setTimeout(() => {
|
|
21088
|
+
if (this.enlargeSpaces) {
|
|
21089
|
+
var _this$canvas2;
|
|
21090
|
+
this.enlargeSpaces();
|
|
21091
|
+
(_this$canvas2 = this.canvas) === null || _this$canvas2 === void 0 || _this$canvas2.renderAll();
|
|
21092
|
+
}
|
|
21093
|
+
}, 0);
|
|
21094
|
+
}
|
|
20346
21095
|
this.dirty = true;
|
|
20347
21096
|
}
|
|
20348
21097
|
|
|
@@ -20923,7 +21672,15 @@ class FabricText extends StyledText {
|
|
|
20923
21672
|
if (currentDirection !== this.direction) {
|
|
20924
21673
|
ctx.canvas.setAttribute('dir', isLtr ? 'ltr' : 'rtl');
|
|
20925
21674
|
ctx.direction = isLtr ? 'ltr' : 'rtl';
|
|
20926
|
-
|
|
21675
|
+
|
|
21676
|
+
// For justify alignments, we need to set the correct canvas text alignment
|
|
21677
|
+
// This is crucial for RTL text to render in the correct order
|
|
21678
|
+
if (isJustify) {
|
|
21679
|
+
// Justify uses LEFT alignment as a base, letting the character positioning handle justification
|
|
21680
|
+
ctx.textAlign = LEFT;
|
|
21681
|
+
} else {
|
|
21682
|
+
ctx.textAlign = isLtr ? LEFT : RIGHT;
|
|
21683
|
+
}
|
|
20927
21684
|
}
|
|
20928
21685
|
top -= lineHeight * this._fontSizeFraction / this.lineHeight;
|
|
20929
21686
|
if (shortCut) {
|
|
@@ -21159,9 +21916,21 @@ class FabricText extends StyledText {
|
|
|
21159
21916
|
direction = this.direction,
|
|
21160
21917
|
isEndOfWrapping = this.isEndOfWrapping(lineIndex);
|
|
21161
21918
|
let leftOffset = 0;
|
|
21162
|
-
|
|
21163
|
-
|
|
21919
|
+
|
|
21920
|
+
// Handle justify alignments (excluding last lines and wrapped line ends)
|
|
21921
|
+
const isJustifyLine = textAlign === JUSTIFY || textAlign === JUSTIFY_CENTER && !isEndOfWrapping || textAlign === JUSTIFY_RIGHT && !isEndOfWrapping || textAlign === JUSTIFY_LEFT && !isEndOfWrapping;
|
|
21922
|
+
if (isJustifyLine) {
|
|
21923
|
+
// Justify lines should start at the left edge for LTR and right edge for RTL
|
|
21924
|
+
// The space distribution is handled by enlargeSpaces()
|
|
21925
|
+
if (direction === 'rtl') {
|
|
21926
|
+
// For RTL justify, we need to account for the line being right-aligned
|
|
21927
|
+
return 0;
|
|
21928
|
+
} else {
|
|
21929
|
+
return 0;
|
|
21930
|
+
}
|
|
21164
21931
|
}
|
|
21932
|
+
|
|
21933
|
+
// Handle non-justify alignments
|
|
21165
21934
|
if (textAlign === CENTER) {
|
|
21166
21935
|
leftOffset = lineDiff / 2;
|
|
21167
21936
|
}
|
|
@@ -21174,6 +21943,8 @@ class FabricText extends StyledText {
|
|
|
21174
21943
|
if (textAlign === JUSTIFY_RIGHT) {
|
|
21175
21944
|
leftOffset = lineDiff;
|
|
21176
21945
|
}
|
|
21946
|
+
|
|
21947
|
+
// Apply RTL adjustments for non-justify alignments
|
|
21177
21948
|
if (direction === 'rtl') {
|
|
21178
21949
|
if (textAlign === RIGHT || textAlign === JUSTIFY || textAlign === JUSTIFY_RIGHT) {
|
|
21179
21950
|
leftOffset = 0;
|
|
@@ -21332,7 +22103,19 @@ class FabricText extends StyledText {
|
|
|
21332
22103
|
fontSize = this.fontSize
|
|
21333
22104
|
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
21334
22105
|
let forMeasuring = arguments.length > 1 ? arguments[1] : undefined;
|
|
21335
|
-
|
|
22106
|
+
let parsedFontFamily = fontFamily.includes("'") || fontFamily.includes('"') || fontFamily.includes(',') || FabricText.genericFonts.includes(fontFamily.toLowerCase()) ? fontFamily : `"${fontFamily}"`;
|
|
22107
|
+
|
|
22108
|
+
// For fonts like STV that don't support English/Latin characters,
|
|
22109
|
+
// add fallback fonts for consistent rendering of unsupported characters
|
|
22110
|
+
// Only add fallbacks during actual rendering, not for measurements
|
|
22111
|
+
if (!forMeasuring &&
|
|
22112
|
+
// Only during rendering, not measuring
|
|
22113
|
+
!fontFamily.includes(',') && (
|
|
22114
|
+
// Don't add fallbacks if already has them
|
|
22115
|
+
fontFamily.toLowerCase().includes('stv') || fontFamily.toLowerCase().includes('arabic') || fontFamily.toLowerCase().includes('naskh') || fontFamily.toLowerCase().includes('kufi'))) {
|
|
22116
|
+
// Add fallback fonts for unsupported characters (spaces, punctuation, etc.)
|
|
22117
|
+
parsedFontFamily = `${parsedFontFamily}, "Arial Unicode MS", Arial, sans-serif`;
|
|
22118
|
+
}
|
|
21336
22119
|
return [fontStyle, fontWeight, `${forMeasuring ? this.CACHE_FONT_SIZE : fontSize}px`, parsedFontFamily].join(' ');
|
|
21337
22120
|
}
|
|
21338
22121
|
|
|
@@ -21376,7 +22159,13 @@ class FabricText extends StyledText {
|
|
|
21376
22159
|
newLine = ['\n'];
|
|
21377
22160
|
let newText = [];
|
|
21378
22161
|
for (let i = 0; i < lines.length; i++) {
|
|
21379
|
-
|
|
22162
|
+
// Use BiDi-aware grapheme splitting for RTL text
|
|
22163
|
+
if (this.direction === 'rtl' || this._containsArabicText(lines[i])) {
|
|
22164
|
+
newLines[i] = segmentGraphemes(lines[i]);
|
|
22165
|
+
console.log(`🔤 BiDi-aware split line ${i}: "${lines[i]}" -> [${newLines[i].join(', ')}]`);
|
|
22166
|
+
} else {
|
|
22167
|
+
newLines[i] = this.graphemeSplit(lines[i]);
|
|
22168
|
+
}
|
|
21380
22169
|
newText = newText.concat(newLines[i], newLine);
|
|
21381
22170
|
}
|
|
21382
22171
|
newText.pop();
|
|
@@ -21388,6 +22177,14 @@ class FabricText extends StyledText {
|
|
|
21388
22177
|
};
|
|
21389
22178
|
}
|
|
21390
22179
|
|
|
22180
|
+
/**
|
|
22181
|
+
* Check if text contains Arabic characters
|
|
22182
|
+
* @private
|
|
22183
|
+
*/
|
|
22184
|
+
_containsArabicText(text) {
|
|
22185
|
+
return /[\u0600-\u06FF\u0750-\u077F\uFB50-\uFDFF\uFE70-\uFEFF]/.test(text);
|
|
22186
|
+
}
|
|
22187
|
+
|
|
21391
22188
|
/**
|
|
21392
22189
|
* Returns object representation of an instance
|
|
21393
22190
|
* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
|
|
@@ -21497,18 +22294,100 @@ class FabricText extends StyledText {
|
|
|
21497
22294
|
if (textAnchor === CENTER) {
|
|
21498
22295
|
offX = text.getScaledWidth() / 2;
|
|
21499
22296
|
}
|
|
21500
|
-
if (textAnchor === RIGHT) {
|
|
21501
|
-
offX = text.getScaledWidth();
|
|
22297
|
+
if (textAnchor === RIGHT) {
|
|
22298
|
+
offX = text.getScaledWidth();
|
|
22299
|
+
}
|
|
22300
|
+
text.set({
|
|
22301
|
+
left: text.left - offX,
|
|
22302
|
+
top: text.top - (textHeight - text.fontSize * (0.07 + text._fontSizeFraction)) / text.lineHeight,
|
|
22303
|
+
strokeWidth
|
|
22304
|
+
});
|
|
22305
|
+
return text;
|
|
22306
|
+
}
|
|
22307
|
+
|
|
22308
|
+
/* _FROM_SVG_END_ */
|
|
22309
|
+
|
|
22310
|
+
/**
|
|
22311
|
+
* Check if the font is ready for accurate measurements
|
|
22312
|
+
* @private
|
|
22313
|
+
*/
|
|
22314
|
+
_isFontReady() {
|
|
22315
|
+
if (typeof document === 'undefined' || !('fonts' in document)) {
|
|
22316
|
+
return true; // Assume ready in non-browser environments
|
|
22317
|
+
}
|
|
22318
|
+
try {
|
|
22319
|
+
return document.fonts.check(`${this.fontSize}px ${this.fontFamily}`);
|
|
22320
|
+
} catch (e) {
|
|
22321
|
+
return true; // Fallback to assuming ready if check fails
|
|
22322
|
+
}
|
|
22323
|
+
}
|
|
22324
|
+
|
|
22325
|
+
/**
|
|
22326
|
+
* Schedule re-initialization after font loads
|
|
22327
|
+
* @private
|
|
22328
|
+
*/
|
|
22329
|
+
_scheduleInitAfterFontLoad() {
|
|
22330
|
+
if (typeof document === 'undefined' || !('fonts' in document)) {
|
|
22331
|
+
return;
|
|
22332
|
+
}
|
|
22333
|
+
|
|
22334
|
+
// Only schedule if not already waiting
|
|
22335
|
+
if (this._fontLoadScheduled) {
|
|
22336
|
+
return;
|
|
21502
22337
|
}
|
|
21503
|
-
|
|
21504
|
-
|
|
21505
|
-
|
|
21506
|
-
|
|
22338
|
+
this._fontLoadScheduled = true;
|
|
22339
|
+
const fontSpec = `${this.fontSize}px ${this.fontFamily}`;
|
|
22340
|
+
document.fonts.load(fontSpec).then(() => {
|
|
22341
|
+
this._fontLoadScheduled = false;
|
|
22342
|
+
// Re-initialize dimensions with proper font metrics
|
|
22343
|
+
this.initDimensions();
|
|
22344
|
+
|
|
22345
|
+
// Extra step for justify alignment after font loading
|
|
22346
|
+
if (this.textAlign && this.textAlign.includes(JUSTIFY)) {
|
|
22347
|
+
setTimeout(() => {
|
|
22348
|
+
var _this$canvas3;
|
|
22349
|
+
if (this.enlargeSpaces) {
|
|
22350
|
+
this.enlargeSpaces();
|
|
22351
|
+
}
|
|
22352
|
+
(_this$canvas3 = this.canvas) === null || _this$canvas3 === void 0 || _this$canvas3.requestRenderAll();
|
|
22353
|
+
}, 10);
|
|
22354
|
+
} else {
|
|
22355
|
+
var _this$canvas4;
|
|
22356
|
+
(_this$canvas4 = this.canvas) === null || _this$canvas4 === void 0 || _this$canvas4.requestRenderAll();
|
|
22357
|
+
}
|
|
22358
|
+
}).catch(() => {
|
|
22359
|
+
this._fontLoadScheduled = false;
|
|
21507
22360
|
});
|
|
21508
|
-
return text;
|
|
21509
22361
|
}
|
|
21510
22362
|
|
|
21511
|
-
|
|
22363
|
+
/**
|
|
22364
|
+
* Force complete text re-initialization (useful after JSON loading)
|
|
22365
|
+
*/
|
|
22366
|
+
forceTextReinitialization() {
|
|
22367
|
+
console.log('🔄 Force reinitializing text object');
|
|
22368
|
+
|
|
22369
|
+
// Clear all caches
|
|
22370
|
+
this._clearCache();
|
|
22371
|
+
this.dirty = true;
|
|
22372
|
+
|
|
22373
|
+
// Force text splitting to rebuild internal structures
|
|
22374
|
+
this._splitText();
|
|
22375
|
+
|
|
22376
|
+
// Re-initialize dimensions
|
|
22377
|
+
this.initDimensions();
|
|
22378
|
+
|
|
22379
|
+
// Special handling for justify alignment
|
|
22380
|
+
if (this.textAlign && this.textAlign.includes(JUSTIFY)) {
|
|
22381
|
+
// Ensure justify is applied after dimensions are set
|
|
22382
|
+
setTimeout(() => {
|
|
22383
|
+
if (this.__charBounds && this.__charBounds.length > 0 && this.enlargeSpaces) {
|
|
22384
|
+
var _this$canvas5;
|
|
22385
|
+
this.enlargeSpaces();
|
|
22386
|
+
(_this$canvas5 = this.canvas) === null || _this$canvas5 === void 0 || _this$canvas5.requestRenderAll();
|
|
22387
|
+
}
|
|
22388
|
+
}, 10);
|
|
22389
|
+
}
|
|
22390
|
+
}
|
|
21512
22391
|
|
|
21513
22392
|
/**
|
|
21514
22393
|
* Returns FabricText instance from an object representation
|
|
@@ -21521,6 +22400,93 @@ class FabricText extends StyledText {
|
|
|
21521
22400
|
styles: stylesFromArray(object.styles || {}, object.text)
|
|
21522
22401
|
}, {
|
|
21523
22402
|
extraParam: 'text'
|
|
22403
|
+
}).then(textObject => {
|
|
22404
|
+
// Ensure text object is properly initialized after JSON deserialization
|
|
22405
|
+
// This is critical for justify alignment and other text layout features
|
|
22406
|
+
textObject.initialized = true;
|
|
22407
|
+
|
|
22408
|
+
// Force reinitialization to ensure proper layout
|
|
22409
|
+
if (textObject._clearCache) {
|
|
22410
|
+
textObject._clearCache();
|
|
22411
|
+
}
|
|
22412
|
+
textObject.dirty = true;
|
|
22413
|
+
|
|
22414
|
+
// Check if we need to wait for font loading (especially for custom fonts like STV)
|
|
22415
|
+
const fontSpec = `${textObject.fontSize}px ${textObject.fontFamily}`;
|
|
22416
|
+
|
|
22417
|
+
// For custom fonts, ensure they're loaded before initializing dimensions
|
|
22418
|
+
if (typeof document !== 'undefined' && 'fonts' in document && textObject.fontFamily !== 'Arial' && textObject.fontFamily !== 'Times New Roman') {
|
|
22419
|
+
return document.fonts.load(fontSpec).then(() => {
|
|
22420
|
+
var _textObject$fontFamil;
|
|
22421
|
+
console.log(`🔤 Font loaded for JSON object: ${fontSpec}`);
|
|
22422
|
+
// Ensure initialized flag is set again (in case constructor reset it)
|
|
22423
|
+
textObject.initialized = true;
|
|
22424
|
+
|
|
22425
|
+
// Special handling for STV fonts which have measurement issues
|
|
22426
|
+
const isStvFont = (_textObject$fontFamil = textObject.fontFamily) === null || _textObject$fontFamil === void 0 ? void 0 : _textObject$fontFamil.toLowerCase().includes('stv');
|
|
22427
|
+
if (isStvFont) {
|
|
22428
|
+
console.log(`🔤 STV font detected, using enhanced reinitialization`);
|
|
22429
|
+
|
|
22430
|
+
// Clear all cached state that might interfere with browser wrapping
|
|
22431
|
+
textObject._browserWrapCache = null;
|
|
22432
|
+
textObject._lastDimensionState = null;
|
|
22433
|
+
textObject._browserWrapInitialized = false;
|
|
22434
|
+
console.log(`🔤 STV font: Cleared all cached states for fresh initialization`);
|
|
22435
|
+
|
|
22436
|
+
// Force browser wrapping flag for STV fonts
|
|
22437
|
+
textObject._usingBrowserWrapping = true;
|
|
22438
|
+
console.log(`🔤 STV font: Forcing browser wrapping flag during JSON load`);
|
|
22439
|
+
|
|
22440
|
+
// Multiple initialization attempts for STV fonts
|
|
22441
|
+
const reinitWithDelay = attempt => {
|
|
22442
|
+
if (textObject.forceTextReinitialization) {
|
|
22443
|
+
textObject.forceTextReinitialization();
|
|
22444
|
+
} else {
|
|
22445
|
+
textObject.initDimensions();
|
|
22446
|
+
}
|
|
22447
|
+
|
|
22448
|
+
// Check if width is still problematic after initialization
|
|
22449
|
+
if (textObject.width < 50 && attempt < 3) {
|
|
22450
|
+
console.log(`🔤 STV font width still ${textObject.width}px, retrying in ${100 * attempt}ms (attempt ${attempt + 1}/3)`);
|
|
22451
|
+
setTimeout(() => reinitWithDelay(attempt + 1), 100 * attempt);
|
|
22452
|
+
}
|
|
22453
|
+
};
|
|
22454
|
+
reinitWithDelay(0);
|
|
22455
|
+
} else {
|
|
22456
|
+
// Use specialized reinitialization for Textbox objects
|
|
22457
|
+
if (textObject.forceTextReinitialization) {
|
|
22458
|
+
console.log(`🔤 Using Textbox specialized reinitialization`);
|
|
22459
|
+
textObject.forceTextReinitialization();
|
|
22460
|
+
} else {
|
|
22461
|
+
// Reinitialize dimensions with proper font metrics
|
|
22462
|
+
textObject.initDimensions();
|
|
22463
|
+
}
|
|
22464
|
+
}
|
|
22465
|
+
return textObject;
|
|
22466
|
+
}).catch(() => {
|
|
22467
|
+
console.warn(`⚠️ Font loading failed for ${fontSpec}, proceeding with fallback`);
|
|
22468
|
+
// Ensure initialized flag is set again
|
|
22469
|
+
textObject.initialized = true;
|
|
22470
|
+
|
|
22471
|
+
// Still initialize dimensions even if font loading fails
|
|
22472
|
+
if (textObject.forceTextReinitialization) {
|
|
22473
|
+
textObject.forceTextReinitialization();
|
|
22474
|
+
} else {
|
|
22475
|
+
textObject.initDimensions();
|
|
22476
|
+
}
|
|
22477
|
+
return textObject;
|
|
22478
|
+
});
|
|
22479
|
+
} else {
|
|
22480
|
+
// Standard fonts - ensure initialized and use appropriate method
|
|
22481
|
+
textObject.initialized = true;
|
|
22482
|
+
if (textObject.forceTextReinitialization) {
|
|
22483
|
+
console.log(`🔤 Using Textbox specialized reinitialization for standard font`);
|
|
22484
|
+
textObject.forceTextReinitialization();
|
|
22485
|
+
} else {
|
|
22486
|
+
textObject.initDimensions();
|
|
22487
|
+
}
|
|
22488
|
+
return textObject;
|
|
22489
|
+
}
|
|
21524
22490
|
});
|
|
21525
22491
|
}
|
|
21526
22492
|
}
|
|
@@ -22164,18 +23130,98 @@ class OverlayEditor {
|
|
|
22164
23130
|
|
|
22165
23131
|
// Apply all other font and text styles to match Fabric
|
|
22166
23132
|
const letterSpacingPx = (target.charSpacing || 0) / 1000 * finalFontSize;
|
|
23133
|
+
|
|
23134
|
+
// Special handling for text objects loaded from JSON - ensure they're properly initialized
|
|
23135
|
+
if (target.dirty !== false && target.initDimensions) {
|
|
23136
|
+
console.log('🔧 Ensuring text object is properly initialized before overlay editing');
|
|
23137
|
+
// Force re-initialization if the text object seems to be in a dirty state
|
|
23138
|
+
target.initDimensions();
|
|
23139
|
+
}
|
|
22167
23140
|
this.textarea.style.fontSize = `${finalFontSize}px`;
|
|
22168
23141
|
this.textarea.style.lineHeight = String(fabricLineHeight);
|
|
22169
23142
|
this.textarea.style.fontFamily = target.fontFamily || 'Arial';
|
|
22170
23143
|
this.textarea.style.fontWeight = String(target.fontWeight || 'normal');
|
|
22171
23144
|
this.textarea.style.fontStyle = target.fontStyle || 'normal';
|
|
22172
|
-
|
|
23145
|
+
// Handle text alignment and justification
|
|
23146
|
+
const textAlign = target.textAlign || 'left';
|
|
23147
|
+
let cssTextAlign = textAlign;
|
|
23148
|
+
|
|
23149
|
+
// Detect text direction from content for proper justify handling
|
|
23150
|
+
const autoDetectedDirection = this.firstStrongDir(this.textarea.value || '');
|
|
23151
|
+
|
|
23152
|
+
// DEBUG: Log alignment details
|
|
23153
|
+
console.log('🔍 ALIGNMENT DEBUG:');
|
|
23154
|
+
console.log(' Fabric textAlign:', textAlign);
|
|
23155
|
+
console.log(' Fabric direction:', target.direction);
|
|
23156
|
+
console.log(' Text content:', JSON.stringify(target.text));
|
|
23157
|
+
console.log(' Detected direction:', autoDetectedDirection);
|
|
23158
|
+
|
|
23159
|
+
// Map fabric.js justify to CSS
|
|
23160
|
+
if (textAlign.includes('justify')) {
|
|
23161
|
+
// Try to match fabric.js justify behavior more precisely
|
|
23162
|
+
try {
|
|
23163
|
+
// For justify, we need to replicate fabric.js space expansion
|
|
23164
|
+
// Use CSS justify but with specific settings to match fabric.js better
|
|
23165
|
+
cssTextAlign = 'justify';
|
|
23166
|
+
|
|
23167
|
+
// Set text-align-last based on justify type and detected direction
|
|
23168
|
+
// Smart justify: respect detected direction even when fabric alignment doesn't match
|
|
23169
|
+
if (textAlign === 'justify') {
|
|
23170
|
+
this.textarea.style.textAlignLast = autoDetectedDirection === 'rtl' ? 'right' : 'left';
|
|
23171
|
+
} else if (textAlign === 'justify-left') {
|
|
23172
|
+
// If text is RTL but fabric says justify-left, override to justify-right for better UX
|
|
23173
|
+
if (autoDetectedDirection === 'rtl') {
|
|
23174
|
+
this.textarea.style.textAlignLast = 'right';
|
|
23175
|
+
console.log(' → Overrode justify-left to justify-right for RTL text');
|
|
23176
|
+
} else {
|
|
23177
|
+
this.textarea.style.textAlignLast = 'left';
|
|
23178
|
+
}
|
|
23179
|
+
} else if (textAlign === 'justify-right') {
|
|
23180
|
+
// If text is LTR but fabric says justify-right, override to justify-left for better UX
|
|
23181
|
+
if (autoDetectedDirection === 'ltr') {
|
|
23182
|
+
this.textarea.style.textAlignLast = 'left';
|
|
23183
|
+
console.log(' → Overrode justify-right to justify-left for LTR text');
|
|
23184
|
+
} else {
|
|
23185
|
+
this.textarea.style.textAlignLast = 'right';
|
|
23186
|
+
}
|
|
23187
|
+
} else if (textAlign === 'justify-center') {
|
|
23188
|
+
this.textarea.style.textAlignLast = 'center';
|
|
23189
|
+
}
|
|
23190
|
+
|
|
23191
|
+
// Enhanced justify settings for better fabric.js matching
|
|
23192
|
+
this.textarea.style.textJustify = 'inter-word';
|
|
23193
|
+
this.textarea.style.wordSpacing = 'normal';
|
|
23194
|
+
|
|
23195
|
+
// Additional CSS properties for better justify matching
|
|
23196
|
+
this.textarea.style.textAlign = 'justify';
|
|
23197
|
+
this.textarea.style.textAlignLast = this.textarea.style.textAlignLast;
|
|
23198
|
+
|
|
23199
|
+
// Try to force better justify behavior
|
|
23200
|
+
this.textarea.style.textJustifyTrim = 'none';
|
|
23201
|
+
this.textarea.style.textAutospace = 'none';
|
|
23202
|
+
console.log(' → Applied justify alignment:', textAlign, 'with last-line:', this.textarea.style.textAlignLast);
|
|
23203
|
+
} catch (error) {
|
|
23204
|
+
console.warn(' → Justify setup failed, falling back to standard alignment:', error);
|
|
23205
|
+
cssTextAlign = textAlign.replace('justify-', '').replace('justify', 'left');
|
|
23206
|
+
}
|
|
23207
|
+
} else {
|
|
23208
|
+
this.textarea.style.textAlignLast = 'auto';
|
|
23209
|
+
this.textarea.style.textJustify = 'auto';
|
|
23210
|
+
this.textarea.style.wordSpacing = 'normal';
|
|
23211
|
+
console.log(' → Applied standard alignment:', cssTextAlign);
|
|
23212
|
+
}
|
|
23213
|
+
this.textarea.style.textAlign = cssTextAlign;
|
|
22173
23214
|
this.textarea.style.color = ((_target$fill = target.fill) === null || _target$fill === void 0 ? void 0 : _target$fill.toString()) || '#000';
|
|
22174
23215
|
this.textarea.style.letterSpacing = `${letterSpacingPx}px`;
|
|
22175
|
-
|
|
23216
|
+
|
|
23217
|
+
// Use the already detected direction from above
|
|
23218
|
+
const fabricDirection = target.direction;
|
|
23219
|
+
|
|
23220
|
+
// Use auto-detected direction for better BiDi support, but respect fabric direction if it makes sense
|
|
23221
|
+
this.textarea.style.direction = autoDetectedDirection || fabricDirection || 'ltr';
|
|
22176
23222
|
this.textarea.style.fontVariant = 'normal';
|
|
22177
23223
|
this.textarea.style.fontStretch = 'normal';
|
|
22178
|
-
this.textarea.style.textRendering = 'optimizeLegibility'
|
|
23224
|
+
this.textarea.style.textRendering = 'auto'; // Changed from 'optimizeLegibility' to match canvas
|
|
22179
23225
|
this.textarea.style.fontKerning = 'normal';
|
|
22180
23226
|
this.textarea.style.fontFeatureSettings = 'normal';
|
|
22181
23227
|
this.textarea.style.fontVariationSettings = 'normal';
|
|
@@ -22186,14 +23232,58 @@ class OverlayEditor {
|
|
|
22186
23232
|
this.textarea.style.overflowWrap = 'break-word';
|
|
22187
23233
|
this.textarea.style.whiteSpace = 'pre-wrap';
|
|
22188
23234
|
this.textarea.style.hyphens = 'none';
|
|
22189
|
-
this.textarea.style.webkitFontSmoothing = 'antialiased';
|
|
22190
|
-
this.textarea.style.mozOsxFontSmoothing = 'grayscale';
|
|
22191
23235
|
|
|
22192
|
-
//
|
|
22193
|
-
|
|
23236
|
+
// DEBUG: Log final CSS properties
|
|
23237
|
+
console.log('🎨 FINAL TEXTAREA CSS:');
|
|
23238
|
+
console.log(' textAlign:', this.textarea.style.textAlign);
|
|
23239
|
+
console.log(' textAlignLast:', this.textarea.style.textAlignLast);
|
|
23240
|
+
console.log(' direction:', this.textarea.style.direction);
|
|
23241
|
+
console.log(' unicodeBidi:', this.textarea.style.unicodeBidi);
|
|
23242
|
+
console.log(' width:', this.textarea.style.width);
|
|
23243
|
+
console.log(' textJustify:', this.textarea.style.textJustify);
|
|
23244
|
+
console.log(' wordSpacing:', this.textarea.style.wordSpacing);
|
|
23245
|
+
console.log(' whiteSpace:', this.textarea.style.whiteSpace);
|
|
23246
|
+
|
|
23247
|
+
// If justify, log Fabric object dimensions for comparison
|
|
23248
|
+
if (textAlign.includes('justify')) {
|
|
23249
|
+
var _calcTextWidth, _ref;
|
|
23250
|
+
console.log('🔧 FABRIC OBJECT JUSTIFY INFO:');
|
|
23251
|
+
console.log(' Fabric width:', target.width);
|
|
23252
|
+
console.log(' Fabric calcTextWidth:', (_calcTextWidth = (_ref = target).calcTextWidth) === null || _calcTextWidth === void 0 ? void 0 : _calcTextWidth.call(_ref));
|
|
23253
|
+
console.log(' Fabric textAlign:', target.textAlign);
|
|
23254
|
+
console.log(' Text lines:', target.textLines);
|
|
23255
|
+
}
|
|
23256
|
+
|
|
23257
|
+
// Debug font properties matching
|
|
23258
|
+
console.log('🔤 FONT PROPERTIES COMPARISON:');
|
|
23259
|
+
console.log(' Fabric fontFamily:', target.fontFamily);
|
|
23260
|
+
console.log(' Fabric fontWeight:', target.fontWeight);
|
|
23261
|
+
console.log(' Fabric fontStyle:', target.fontStyle);
|
|
23262
|
+
console.log(' Fabric fontSize:', target.fontSize);
|
|
23263
|
+
console.log(' → Textarea fontFamily:', this.textarea.style.fontFamily);
|
|
23264
|
+
console.log(' → Textarea fontWeight:', this.textarea.style.fontWeight);
|
|
23265
|
+
console.log(' → Textarea fontStyle:', this.textarea.style.fontStyle);
|
|
23266
|
+
console.log(' → Textarea fontSize:', this.textarea.style.fontSize);
|
|
23267
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
22194
23268
|
|
|
22195
|
-
//
|
|
22196
|
-
|
|
23269
|
+
// Enhanced font rendering to better match fabric.js canvas rendering
|
|
23270
|
+
// Default to auto for more natural rendering
|
|
23271
|
+
this.textarea.style.webkitFontSmoothing = 'auto';
|
|
23272
|
+
this.textarea.style.mozOsxFontSmoothing = 'auto';
|
|
23273
|
+
this.textarea.style.fontSmooth = 'auto';
|
|
23274
|
+
this.textarea.style.textSizeAdjust = 'none';
|
|
23275
|
+
|
|
23276
|
+
// For bold fonts, use subpixel rendering to match canvas thickness better
|
|
23277
|
+
const fontWeight = String(target.fontWeight || 'normal');
|
|
23278
|
+
const isBold = fontWeight === 'bold' || fontWeight === '700' || parseInt(fontWeight) >= 600;
|
|
23279
|
+
if (isBold) {
|
|
23280
|
+
this.textarea.style.webkitFontSmoothing = 'subpixel-antialiased';
|
|
23281
|
+
this.textarea.style.mozOsxFontSmoothing = 'unset';
|
|
23282
|
+
console.log('🔤 Applied enhanced bold rendering for better thickness matching');
|
|
23283
|
+
}
|
|
23284
|
+
console.log('🎨 FONT SMOOTHING APPLIED:');
|
|
23285
|
+
console.log(' webkitFontSmoothing:', this.textarea.style.webkitFontSmoothing);
|
|
23286
|
+
console.log(' mozOsxFontSmoothing:', this.textarea.style.mozOsxFontSmoothing);
|
|
22197
23287
|
|
|
22198
23288
|
// Initial bounds are set correctly by Fabric.js - don't force update here
|
|
22199
23289
|
}
|
|
@@ -22383,6 +23473,11 @@ class OverlayEditor {
|
|
|
22383
23473
|
this.canvas.requestRenderAll();
|
|
22384
23474
|
this.target.setCoords();
|
|
22385
23475
|
this.applyOverlayStyle();
|
|
23476
|
+
|
|
23477
|
+
// Fix character mapping issues after JSON loading for browser-wrapped fonts
|
|
23478
|
+
if (this.target._fixCharacterMappingAfterJsonLoad) {
|
|
23479
|
+
this.target._fixCharacterMappingAfterJsonLoad();
|
|
23480
|
+
}
|
|
22386
23481
|
this.textarea.focus();
|
|
22387
23482
|
this.textarea.setSelectionRange(this.textarea.value.length, this.textarea.value.length);
|
|
22388
23483
|
|
|
@@ -22428,6 +23523,23 @@ class OverlayEditor {
|
|
|
22428
23523
|
// Handle commit/cancel after restoring visibility
|
|
22429
23524
|
if (commit && !this.isComposing) {
|
|
22430
23525
|
const finalText = this.textarea.value;
|
|
23526
|
+
|
|
23527
|
+
// Auto-detect text direction and update fabric object if needed
|
|
23528
|
+
const detectedDirection = this.firstStrongDir(finalText);
|
|
23529
|
+
const currentDirection = this.target.direction || 'ltr';
|
|
23530
|
+
if (detectedDirection && detectedDirection !== currentDirection) {
|
|
23531
|
+
console.log(`🔄 Overlay Exit: Auto-detected direction change from "${currentDirection}" to "${detectedDirection}"`);
|
|
23532
|
+
console.log(` Text content: "${finalText.substring(0, 50)}..."`);
|
|
23533
|
+
|
|
23534
|
+
// Update the fabric object's direction
|
|
23535
|
+
this.target.set('direction', detectedDirection);
|
|
23536
|
+
|
|
23537
|
+
// Force a re-render to apply the direction change
|
|
23538
|
+
this.canvas.requestRenderAll();
|
|
23539
|
+
console.log(`✅ Fabric object direction updated to: ${detectedDirection}`);
|
|
23540
|
+
} else {
|
|
23541
|
+
console.log(`📝 Overlay Exit: Direction unchanged (${currentDirection}), text: "${finalText.substring(0, 30)}..."`);
|
|
23542
|
+
}
|
|
22431
23543
|
if (this.onCommit) {
|
|
22432
23544
|
this.onCommit(finalText);
|
|
22433
23545
|
}
|
|
@@ -25520,8 +26632,27 @@ class Textbox extends IText {
|
|
|
25520
26632
|
*/
|
|
25521
26633
|
initDimensions() {
|
|
25522
26634
|
if (!this.initialized) {
|
|
26635
|
+
this.initialized = true;
|
|
26636
|
+
}
|
|
26637
|
+
|
|
26638
|
+
// Prevent rapid recalculations during moves
|
|
26639
|
+
if (this._usingBrowserWrapping) {
|
|
26640
|
+
const now = Date.now();
|
|
26641
|
+
const lastCall = this._lastInitDimensionsTime || 0;
|
|
26642
|
+
const isRapidCall = now - lastCall < 100;
|
|
26643
|
+
const isDuringLoading = this._jsonLoading || !this._browserWrapInitialized;
|
|
26644
|
+
if (isRapidCall && !isDuringLoading) {
|
|
26645
|
+
return;
|
|
26646
|
+
}
|
|
26647
|
+
this._lastInitDimensionsTime = now;
|
|
26648
|
+
}
|
|
26649
|
+
|
|
26650
|
+
// Skip if nothing changed
|
|
26651
|
+
const currentState = `${this.text}|${this.width}|${this.fontSize}|${this.fontFamily}|${this.textAlign}`;
|
|
26652
|
+
if (this._lastDimensionState === currentState && this._textLines && this._textLines.length > 0) {
|
|
25523
26653
|
return;
|
|
25524
26654
|
}
|
|
26655
|
+
this._lastDimensionState = currentState;
|
|
25525
26656
|
|
|
25526
26657
|
// Use advanced layout if enabled
|
|
25527
26658
|
if (this.enableAdvancedLayout) {
|
|
@@ -25532,17 +26663,142 @@ class Textbox extends IText {
|
|
|
25532
26663
|
// clear dynamicMinWidth as it will be different after we re-wrap line
|
|
25533
26664
|
this.dynamicMinWidth = 0;
|
|
25534
26665
|
// wrap lines
|
|
25535
|
-
|
|
25536
|
-
|
|
25537
|
-
|
|
26666
|
+
const splitTextResult = this._splitText();
|
|
26667
|
+
this._styleMap = this._generateStyleMap(splitTextResult);
|
|
26668
|
+
|
|
26669
|
+
// For browser wrapping, ensure _textLines is set from browser results
|
|
26670
|
+
if (this._usingBrowserWrapping && splitTextResult && splitTextResult.lines) {
|
|
26671
|
+
this._textLines = splitTextResult.lines.map(line => line.split(''));
|
|
26672
|
+
|
|
26673
|
+
// Store justify measurements and browser height
|
|
26674
|
+
const justifyMeasurements = splitTextResult.justifySpaceMeasurements;
|
|
26675
|
+
if (justifyMeasurements) {
|
|
26676
|
+
this._styleMap.justifySpaceMeasurements = justifyMeasurements;
|
|
26677
|
+
}
|
|
26678
|
+
const actualHeight = splitTextResult.actualBrowserHeight;
|
|
26679
|
+
if (actualHeight) {
|
|
26680
|
+
this._actualBrowserHeight = actualHeight;
|
|
26681
|
+
}
|
|
26682
|
+
}
|
|
26683
|
+
// Don't auto-resize width when using browser wrapping to prevent width increases during moves
|
|
26684
|
+
if (!this._usingBrowserWrapping && this.dynamicMinWidth > this.width) {
|
|
25538
26685
|
this._set('width', this.dynamicMinWidth);
|
|
25539
26686
|
}
|
|
26687
|
+
|
|
26688
|
+
// For browser wrapping fonts (like STV), ensure minimum width for new textboxes
|
|
26689
|
+
// since these fonts can't measure English characters properly
|
|
26690
|
+
if (this._usingBrowserWrapping && this.width < 50) {
|
|
26691
|
+
console.log(`🔤 BROWSER WRAP: Font ${this.fontFamily} has width ${this.width}px, setting to 300px for usability`);
|
|
26692
|
+
this.width = 300;
|
|
26693
|
+
}
|
|
26694
|
+
|
|
26695
|
+
// Mark browser wrapping as initialized when complete
|
|
26696
|
+
if (this._usingBrowserWrapping) {
|
|
26697
|
+
this._browserWrapInitialized = true;
|
|
26698
|
+
}
|
|
25540
26699
|
if (this.textAlign.includes(JUSTIFY)) {
|
|
26700
|
+
// For browser wrapping fonts, apply browser-calculated justify spaces
|
|
26701
|
+
if (this._usingBrowserWrapping) {
|
|
26702
|
+
console.log('🔤 BROWSER WRAP: Applying browser-calculated justify spaces');
|
|
26703
|
+
this._applyBrowserJustifySpaces();
|
|
26704
|
+
return;
|
|
26705
|
+
}
|
|
26706
|
+
|
|
26707
|
+
// Don't apply justify alignment during drag operations to prevent snapping
|
|
26708
|
+
const now = Date.now();
|
|
26709
|
+
const lastDragTime = this._lastInitDimensionsTime || 0;
|
|
26710
|
+
const isDuringDrag = now - lastDragTime < 200; // 200ms window for drag detection
|
|
26711
|
+
|
|
26712
|
+
if (isDuringDrag) {
|
|
26713
|
+
console.log('🔤 Skipping justify during drag operation to prevent snapping');
|
|
26714
|
+
return;
|
|
26715
|
+
}
|
|
26716
|
+
|
|
26717
|
+
// For non-browser-wrapping fonts, use Fabric's justify system
|
|
25541
26718
|
// once text is measured we need to make space fatter to make justified text.
|
|
25542
|
-
|
|
26719
|
+
// Ensure __charBounds exists and fonts are ready before applying justify
|
|
26720
|
+
if (this.__charBounds && this.__charBounds.length > 0) {
|
|
26721
|
+
// Check if font is ready for accurate justify calculations
|
|
26722
|
+
const fontReady = this._isFontReady ? this._isFontReady() : true;
|
|
26723
|
+
if (fontReady) {
|
|
26724
|
+
this.enlargeSpaces();
|
|
26725
|
+
} else {
|
|
26726
|
+
console.warn('⚠️ Textbox: Font not ready for justify, deferring enlargeSpaces');
|
|
26727
|
+
// Defer justify calculation until font is ready
|
|
26728
|
+
this._scheduleJustifyAfterFontLoad();
|
|
26729
|
+
}
|
|
26730
|
+
} else {
|
|
26731
|
+
console.warn('⚠️ Textbox: __charBounds not ready for justify alignment, deferring enlargeSpaces');
|
|
26732
|
+
// Defer the justify calculation until the next frame
|
|
26733
|
+
setTimeout(() => {
|
|
26734
|
+
if (this.__charBounds && this.__charBounds.length > 0 && this.enlargeSpaces) {
|
|
26735
|
+
var _this$canvas;
|
|
26736
|
+
console.log('🔧 Applying deferred Textbox justify alignment');
|
|
26737
|
+
this.enlargeSpaces();
|
|
26738
|
+
(_this$canvas = this.canvas) === null || _this$canvas === void 0 || _this$canvas.requestRenderAll();
|
|
26739
|
+
}
|
|
26740
|
+
}, 0);
|
|
26741
|
+
}
|
|
26742
|
+
}
|
|
26743
|
+
// Calculate height - use Fabric's calculation for proper text rendering space
|
|
26744
|
+
if (this._usingBrowserWrapping && this._textLines && this._textLines.length > 0) {
|
|
26745
|
+
const actualBrowserHeight = this._actualBrowserHeight;
|
|
26746
|
+
const oldHeight = this.height;
|
|
26747
|
+
// Use Fabric's height calculation since it knows how much space text rendering needs
|
|
26748
|
+
this.height = this.calcTextHeight();
|
|
26749
|
+
|
|
26750
|
+
// Force canvas refresh and control update if height changed significantly
|
|
26751
|
+
if (Math.abs(this.height - oldHeight) > 1) {
|
|
26752
|
+
var _this$canvas2, _this$_textLines;
|
|
26753
|
+
this.setCoords();
|
|
26754
|
+
(_this$canvas2 = this.canvas) === null || _this$canvas2 === void 0 || _this$canvas2.requestRenderAll();
|
|
26755
|
+
|
|
26756
|
+
// DEBUG: Log exact positioning details
|
|
26757
|
+
console.log(`🎯 POSITIONING DEBUG:`);
|
|
26758
|
+
console.log(` Textbox height: ${this.height}px`);
|
|
26759
|
+
console.log(` Textbox top: ${this.top}px`);
|
|
26760
|
+
console.log(` Textbox left: ${this.left}px`);
|
|
26761
|
+
console.log(` Text lines: ${((_this$_textLines = this._textLines) === null || _this$_textLines === void 0 ? void 0 : _this$_textLines.length) || 0}`);
|
|
26762
|
+
console.log(` Font size: ${this.fontSize}px`);
|
|
26763
|
+
console.log(` Line height: ${this.lineHeight || 1.16}`);
|
|
26764
|
+
console.log(` Calculated line height: ${this.fontSize * (this.lineHeight || 1.16)}px`);
|
|
26765
|
+
console.log(` _getTopOffset(): ${this._getTopOffset()}px`);
|
|
26766
|
+
console.log(` calcTextHeight(): ${this.calcTextHeight()}px`);
|
|
26767
|
+
console.log(` Browser height: ${actualBrowserHeight}px`);
|
|
26768
|
+
console.log(` Height difference: ${this.height - this.calcTextHeight()}px`);
|
|
26769
|
+
}
|
|
26770
|
+
} else {
|
|
26771
|
+
this.height = this.calcTextHeight();
|
|
26772
|
+
}
|
|
26773
|
+
}
|
|
26774
|
+
|
|
26775
|
+
/**
|
|
26776
|
+
* Schedule justify calculation after font loads (Textbox-specific)
|
|
26777
|
+
* @private
|
|
26778
|
+
*/
|
|
26779
|
+
_scheduleJustifyAfterFontLoad() {
|
|
26780
|
+
if (typeof document === 'undefined' || !('fonts' in document)) {
|
|
26781
|
+
return;
|
|
26782
|
+
}
|
|
26783
|
+
|
|
26784
|
+
// Only schedule if not already waiting
|
|
26785
|
+
if (this._fontJustifyScheduled) {
|
|
26786
|
+
return;
|
|
25543
26787
|
}
|
|
25544
|
-
|
|
25545
|
-
|
|
26788
|
+
this._fontJustifyScheduled = true;
|
|
26789
|
+
const fontSpec = `${this.fontSize}px ${this.fontFamily}`;
|
|
26790
|
+
document.fonts.load(fontSpec).then(() => {
|
|
26791
|
+
var _this$canvas3;
|
|
26792
|
+
this._fontJustifyScheduled = false;
|
|
26793
|
+
console.log('🔧 Textbox: Font loaded, applying justify alignment');
|
|
26794
|
+
|
|
26795
|
+
// Re-run initDimensions to ensure proper justify calculation
|
|
26796
|
+
this.initDimensions();
|
|
26797
|
+
(_this$canvas3 = this.canvas) === null || _this$canvas3 === void 0 || _this$canvas3.requestRenderAll();
|
|
26798
|
+
}).catch(() => {
|
|
26799
|
+
this._fontJustifyScheduled = false;
|
|
26800
|
+
console.warn('⚠️ Textbox: Font loading failed, justify may be incorrect');
|
|
26801
|
+
});
|
|
25546
26802
|
}
|
|
25547
26803
|
|
|
25548
26804
|
/**
|
|
@@ -25909,19 +27165,33 @@ class Textbox extends IText {
|
|
|
25909
27165
|
width: wordWidth
|
|
25910
27166
|
} = data[i];
|
|
25911
27167
|
offset += word.length;
|
|
25912
|
-
|
|
25913
|
-
if
|
|
27168
|
+
|
|
27169
|
+
// Predictive wrapping: check if adding this word would exceed the width
|
|
27170
|
+
const potentialLineWidth = lineWidth + infixWidth + wordWidth - additionalSpace;
|
|
27171
|
+
// Use exact width to match overlay editor behavior
|
|
27172
|
+
const conservativeMaxWidth = maxWidth; // No artificial buffer
|
|
27173
|
+
|
|
27174
|
+
// Debug logging for wrapping decisions
|
|
27175
|
+
const currentLineText = line.join('');
|
|
27176
|
+
console.log(`🔧 FABRIC WRAP CHECK: "${data[i].word}" -> potential: ${potentialLineWidth.toFixed(1)}px vs limit: ${conservativeMaxWidth.toFixed(1)}px`);
|
|
27177
|
+
if (potentialLineWidth > conservativeMaxWidth && !lineJustStarted) {
|
|
27178
|
+
// This word would exceed the width, wrap before adding it
|
|
27179
|
+
console.log(`🔧 FABRIC WRAP! Line: "${currentLineText}" (${lineWidth.toFixed(1)}px)`);
|
|
25914
27180
|
graphemeLines.push(line);
|
|
25915
27181
|
line = [];
|
|
25916
|
-
lineWidth = wordWidth;
|
|
27182
|
+
lineWidth = wordWidth; // Start new line with just this word
|
|
25917
27183
|
lineJustStarted = true;
|
|
25918
27184
|
} else {
|
|
25919
|
-
|
|
27185
|
+
// Word fits, add it to current line
|
|
27186
|
+
lineWidth = potentialLineWidth + additionalSpace;
|
|
25920
27187
|
}
|
|
25921
27188
|
if (!lineJustStarted && !splitByGrapheme) {
|
|
25922
27189
|
line.push(infix);
|
|
25923
27190
|
}
|
|
25924
27191
|
line = line.concat(word);
|
|
27192
|
+
|
|
27193
|
+
// Debug: show current line after adding word
|
|
27194
|
+
console.log(`🔧 FABRIC AFTER ADD: Line now: "${line.join('')}" (${line.length} chars)`);
|
|
25925
27195
|
infixWidth = splitByGrapheme ? 0 : this._measureWord([infix], lineIndex, offset);
|
|
25926
27196
|
offset++;
|
|
25927
27197
|
lineJustStarted = false;
|
|
@@ -25931,9 +27201,19 @@ class Textbox extends IText {
|
|
|
25931
27201
|
// TODO: this code is probably not necessary anymore.
|
|
25932
27202
|
// it can be moved out of this function since largestWordWidth is now
|
|
25933
27203
|
// known in advance
|
|
25934
|
-
|
|
27204
|
+
// Don't modify dynamicMinWidth when using browser wrapping to prevent width increases
|
|
27205
|
+
if (!this._usingBrowserWrapping && largestWordWidth + reservedSpace > this.dynamicMinWidth) {
|
|
27206
|
+
console.log(`🔧 FABRIC updating dynamicMinWidth: ${this.dynamicMinWidth} -> ${largestWordWidth - additionalSpace + reservedSpace}`);
|
|
25935
27207
|
this.dynamicMinWidth = largestWordWidth - additionalSpace + reservedSpace;
|
|
27208
|
+
} else if (this._usingBrowserWrapping) {
|
|
27209
|
+
console.log(`🔤 BROWSER WRAP: Skipping dynamicMinWidth update to prevent width increase`);
|
|
25936
27210
|
}
|
|
27211
|
+
|
|
27212
|
+
// Debug: show final wrapped lines
|
|
27213
|
+
console.log(`🔧 FABRIC FINAL LINES: ${graphemeLines.length} lines`);
|
|
27214
|
+
graphemeLines.forEach((line, i) => {
|
|
27215
|
+
console.log(` Line ${i + 1}: "${line.join('')}" (${line.length} chars)`);
|
|
27216
|
+
});
|
|
25937
27217
|
return graphemeLines;
|
|
25938
27218
|
}
|
|
25939
27219
|
|
|
@@ -25977,6 +27257,260 @@ class Textbox extends IText {
|
|
|
25977
27257
|
* @override
|
|
25978
27258
|
*/
|
|
25979
27259
|
_splitTextIntoLines(text) {
|
|
27260
|
+
// Check if we need browser wrapping using smart font detection
|
|
27261
|
+
const needsBrowserWrapping = this.fontFamily && fontLacksEnglishGlyphsCached(this.fontFamily);
|
|
27262
|
+
if (needsBrowserWrapping) {
|
|
27263
|
+
// Cache key based on text content, width, font properties, AND text alignment
|
|
27264
|
+
const textHash = text.length + text.slice(0, 50); // Include text content in cache key
|
|
27265
|
+
const cacheKey = `${textHash}|${this.width}|${this.fontSize}|${this.fontFamily}|${this.textAlign}`;
|
|
27266
|
+
|
|
27267
|
+
// Check if we have a cached result and nothing has changed
|
|
27268
|
+
if (this._browserWrapCache && this._browserWrapCache.key === cacheKey) {
|
|
27269
|
+
const cachedResult = this._browserWrapCache.result;
|
|
27270
|
+
|
|
27271
|
+
// For justify alignment, ensure we have the measurements
|
|
27272
|
+
if (this.textAlign.includes('justify') && !cachedResult.justifySpaceMeasurements) ; else {
|
|
27273
|
+
return cachedResult;
|
|
27274
|
+
}
|
|
27275
|
+
}
|
|
27276
|
+
const result = this._splitTextIntoLinesWithBrowser(text);
|
|
27277
|
+
|
|
27278
|
+
// Cache the result
|
|
27279
|
+
this._browserWrapCache = {
|
|
27280
|
+
key: cacheKey,
|
|
27281
|
+
result
|
|
27282
|
+
};
|
|
27283
|
+
|
|
27284
|
+
// Mark that we used browser wrapping to prevent dynamicMinWidth modifications
|
|
27285
|
+
this._usingBrowserWrapping = true;
|
|
27286
|
+
return result;
|
|
27287
|
+
}
|
|
27288
|
+
|
|
27289
|
+
// Clear the browser wrapping flag when using regular wrapping
|
|
27290
|
+
this._usingBrowserWrapping = false;
|
|
27291
|
+
|
|
27292
|
+
// Default Fabric wrapping for other fonts
|
|
27293
|
+
const newText = super._splitTextIntoLines(text),
|
|
27294
|
+
graphemeLines = this._wrapText(newText.lines, this.width),
|
|
27295
|
+
lines = new Array(graphemeLines.length);
|
|
27296
|
+
for (let i = 0; i < graphemeLines.length; i++) {
|
|
27297
|
+
lines[i] = graphemeLines[i].join('');
|
|
27298
|
+
}
|
|
27299
|
+
newText.lines = lines;
|
|
27300
|
+
newText.graphemeLines = graphemeLines;
|
|
27301
|
+
return newText;
|
|
27302
|
+
}
|
|
27303
|
+
|
|
27304
|
+
/**
|
|
27305
|
+
* Use browser's native text wrapping for accurate handling of fonts without English glyphs
|
|
27306
|
+
* @private
|
|
27307
|
+
*/
|
|
27308
|
+
_splitTextIntoLinesWithBrowser(text) {
|
|
27309
|
+
if (typeof document === 'undefined') {
|
|
27310
|
+
// Fallback to regular wrapping in Node.js
|
|
27311
|
+
return this._splitTextIntoLinesDefault(text);
|
|
27312
|
+
}
|
|
27313
|
+
|
|
27314
|
+
// Create a hidden element that mimics the overlay editor
|
|
27315
|
+
const testElement = document.createElement('div');
|
|
27316
|
+
testElement.style.position = 'absolute';
|
|
27317
|
+
testElement.style.left = '-9999px';
|
|
27318
|
+
testElement.style.visibility = 'hidden';
|
|
27319
|
+
testElement.style.fontSize = `${this.fontSize}px`;
|
|
27320
|
+
testElement.style.fontFamily = `"${this.fontFamily}"`;
|
|
27321
|
+
testElement.style.fontWeight = String(this.fontWeight || 'normal');
|
|
27322
|
+
testElement.style.fontStyle = String(this.fontStyle || 'normal');
|
|
27323
|
+
testElement.style.lineHeight = String(this.lineHeight || 1.16);
|
|
27324
|
+
testElement.style.width = `${this.width}px`;
|
|
27325
|
+
testElement.style.direction = this.direction || 'ltr';
|
|
27326
|
+
testElement.style.whiteSpace = 'pre-wrap';
|
|
27327
|
+
testElement.style.wordBreak = 'normal';
|
|
27328
|
+
testElement.style.overflowWrap = 'break-word';
|
|
27329
|
+
|
|
27330
|
+
// Set browser-native text alignment (including justify)
|
|
27331
|
+
if (this.textAlign.includes('justify')) {
|
|
27332
|
+
testElement.style.textAlign = 'justify';
|
|
27333
|
+
testElement.style.textAlignLast = 'auto'; // Let browser decide last line alignment
|
|
27334
|
+
} else {
|
|
27335
|
+
testElement.style.textAlign = this.textAlign;
|
|
27336
|
+
}
|
|
27337
|
+
testElement.textContent = text;
|
|
27338
|
+
document.body.appendChild(testElement);
|
|
27339
|
+
|
|
27340
|
+
// Get the browser's natural line breaks
|
|
27341
|
+
const range = document.createRange();
|
|
27342
|
+
const lines = [];
|
|
27343
|
+
const graphemeLines = [];
|
|
27344
|
+
try {
|
|
27345
|
+
// Simple approach: split by measuring character positions
|
|
27346
|
+
const textNode = testElement.firstChild;
|
|
27347
|
+
if (textNode && textNode.nodeType === Node.TEXT_NODE) {
|
|
27348
|
+
let currentLineStart = 0;
|
|
27349
|
+
const textLength = text.length;
|
|
27350
|
+
let previousBottom = 0;
|
|
27351
|
+
for (let i = 0; i <= textLength; i++) {
|
|
27352
|
+
range.setStart(textNode, currentLineStart);
|
|
27353
|
+
range.setEnd(textNode, i);
|
|
27354
|
+
const rect = range.getBoundingClientRect();
|
|
27355
|
+
if (i > currentLineStart && (rect.bottom > previousBottom + 5 || i === textLength)) {
|
|
27356
|
+
// New line detected or end of text
|
|
27357
|
+
const lineEnd = i === textLength ? i : i - 1;
|
|
27358
|
+
const lineText = text.substring(currentLineStart, lineEnd).trim();
|
|
27359
|
+
if (lineText) {
|
|
27360
|
+
lines.push(lineText);
|
|
27361
|
+
// Convert to graphemes for compatibility
|
|
27362
|
+
const graphemeLine = lineText.split('');
|
|
27363
|
+
graphemeLines.push(graphemeLine);
|
|
27364
|
+
}
|
|
27365
|
+
currentLineStart = lineEnd;
|
|
27366
|
+
previousBottom = rect.bottom;
|
|
27367
|
+
}
|
|
27368
|
+
}
|
|
27369
|
+
}
|
|
27370
|
+
} catch (error) {
|
|
27371
|
+
console.warn('Browser wrapping failed, using fallback:', error);
|
|
27372
|
+
document.body.removeChild(testElement);
|
|
27373
|
+
return this._splitTextIntoLinesDefault(text);
|
|
27374
|
+
}
|
|
27375
|
+
|
|
27376
|
+
// Extract actual browser height BEFORE removing element
|
|
27377
|
+
const actualBrowserHeight = testElement.scrollHeight;
|
|
27378
|
+
const offsetHeight = testElement.offsetHeight;
|
|
27379
|
+
const clientHeight = testElement.clientHeight;
|
|
27380
|
+
const boundingRect = testElement.getBoundingClientRect();
|
|
27381
|
+
console.log(`🔤 Browser element measurements:`);
|
|
27382
|
+
console.log(` scrollHeight: ${actualBrowserHeight}px (content + padding + hidden overflow)`);
|
|
27383
|
+
console.log(` offsetHeight: ${offsetHeight}px (content + padding + border)`);
|
|
27384
|
+
console.log(` clientHeight: ${clientHeight}px (content + padding, no border/scrollbar)`);
|
|
27385
|
+
console.log(` boundingRect.height: ${boundingRect.height}px (actual rendered height)`);
|
|
27386
|
+
console.log(` Font size: ${this.fontSize}px, Line height: ${this.lineHeight || 1.16}, Lines: ${lines.length}`);
|
|
27387
|
+
|
|
27388
|
+
// For justify alignment, extract space measurements from browser BEFORE removing element
|
|
27389
|
+
let justifySpaceMeasurements = null;
|
|
27390
|
+
if (this.textAlign.includes('justify')) {
|
|
27391
|
+
justifySpaceMeasurements = this._extractJustifySpaceMeasurements(testElement, lines);
|
|
27392
|
+
}
|
|
27393
|
+
document.body.removeChild(testElement);
|
|
27394
|
+
console.log(`🔤 Browser wrapping result: ${lines.length} lines`);
|
|
27395
|
+
|
|
27396
|
+
// Try different height measurements to find the most accurate
|
|
27397
|
+
let bestHeight = actualBrowserHeight;
|
|
27398
|
+
|
|
27399
|
+
// If scrollHeight and offsetHeight differ significantly, investigate
|
|
27400
|
+
if (Math.abs(actualBrowserHeight - offsetHeight) > 2) {
|
|
27401
|
+
console.log(`🔤 Height discrepancy detected: scrollHeight=${actualBrowserHeight}px vs offsetHeight=${offsetHeight}px`);
|
|
27402
|
+
}
|
|
27403
|
+
|
|
27404
|
+
// Consider using boundingRect height if it's larger (sometimes more accurate for visible content)
|
|
27405
|
+
if (boundingRect.height > bestHeight) {
|
|
27406
|
+
console.log(`🔤 Using boundingRect height (${boundingRect.height}px) instead of scrollHeight (${bestHeight}px)`);
|
|
27407
|
+
bestHeight = boundingRect.height;
|
|
27408
|
+
}
|
|
27409
|
+
|
|
27410
|
+
// Font-specific height adjustments for accurate bounding box
|
|
27411
|
+
let adjustedHeight = bestHeight;
|
|
27412
|
+
|
|
27413
|
+
// Fonts without English glyphs need additional height buffer due to different font metrics
|
|
27414
|
+
const lacksEnglishGlyphs = fontLacksEnglishGlyphsCached(this.fontFamily);
|
|
27415
|
+
if (lacksEnglishGlyphs) {
|
|
27416
|
+
const glyphBuffer = this.fontSize * 0.25; // 25% of font size for non-English fonts
|
|
27417
|
+
adjustedHeight = bestHeight + glyphBuffer;
|
|
27418
|
+
console.log(`🔤 Non-English font detected (${this.fontFamily}): Adding ${glyphBuffer}px buffer (${bestHeight}px + ${glyphBuffer}px = ${adjustedHeight}px)`);
|
|
27419
|
+
} else {
|
|
27420
|
+
console.log(`🔤 Standard font (${this.fontFamily}): Using browser height directly (${bestHeight}px)`);
|
|
27421
|
+
}
|
|
27422
|
+
return {
|
|
27423
|
+
_unwrappedLines: [text.split('')],
|
|
27424
|
+
lines: lines,
|
|
27425
|
+
graphemeText: text.split(''),
|
|
27426
|
+
graphemeLines: graphemeLines,
|
|
27427
|
+
justifySpaceMeasurements: justifySpaceMeasurements,
|
|
27428
|
+
actualBrowserHeight: adjustedHeight
|
|
27429
|
+
};
|
|
27430
|
+
}
|
|
27431
|
+
|
|
27432
|
+
/**
|
|
27433
|
+
* Extract justify space measurements from browser
|
|
27434
|
+
* @private
|
|
27435
|
+
*/
|
|
27436
|
+
_extractJustifySpaceMeasurements(element, lines) {
|
|
27437
|
+
console.log(`🔤 Extracting browser justify space measurements for ${lines.length} lines`);
|
|
27438
|
+
|
|
27439
|
+
// For now, we'll use a simplified approach:
|
|
27440
|
+
// Apply uniform space expansion to match the line width
|
|
27441
|
+
const spaceWidths = [];
|
|
27442
|
+
lines.forEach((line, lineIndex) => {
|
|
27443
|
+
const lineSpaces = [];
|
|
27444
|
+
const spaceCount = (line.match(/\s/g) || []).length;
|
|
27445
|
+
if (spaceCount > 0 && lineIndex < lines.length - 1) {
|
|
27446
|
+
// Don't justify last line
|
|
27447
|
+
// Calculate how much space expansion is needed
|
|
27448
|
+
const normalSpaceWidth = 6.4; // Default space width for STV font
|
|
27449
|
+
const lineWidth = this.width;
|
|
27450
|
+
|
|
27451
|
+
// Estimate natural line width
|
|
27452
|
+
const charCount = line.length - spaceCount;
|
|
27453
|
+
const avgCharWidth = 12; // Approximate for STV font
|
|
27454
|
+
|
|
27455
|
+
// Calculate expanded space width
|
|
27456
|
+
const remainingSpace = lineWidth - charCount * avgCharWidth;
|
|
27457
|
+
const expandedSpaceWidth = remainingSpace / spaceCount;
|
|
27458
|
+
console.log(`🔤 Line ${lineIndex}: ${spaceCount} spaces, natural: ${normalSpaceWidth}px -> justified: ${expandedSpaceWidth.toFixed(1)}px`);
|
|
27459
|
+
|
|
27460
|
+
// Fill array with expanded space widths for this line
|
|
27461
|
+
for (let i = 0; i < spaceCount; i++) {
|
|
27462
|
+
lineSpaces.push(expandedSpaceWidth);
|
|
27463
|
+
}
|
|
27464
|
+
}
|
|
27465
|
+
spaceWidths.push(lineSpaces);
|
|
27466
|
+
});
|
|
27467
|
+
return spaceWidths;
|
|
27468
|
+
}
|
|
27469
|
+
|
|
27470
|
+
/**
|
|
27471
|
+
* Apply browser-calculated justify space measurements
|
|
27472
|
+
* @private
|
|
27473
|
+
*/
|
|
27474
|
+
_applyBrowserJustifySpaces() {
|
|
27475
|
+
if (!this._textLines || !this.__charBounds) {
|
|
27476
|
+
console.warn('🔤 BROWSER JUSTIFY: _textLines or __charBounds not ready');
|
|
27477
|
+
return;
|
|
27478
|
+
}
|
|
27479
|
+
|
|
27480
|
+
// Get space measurements from browser wrapping result
|
|
27481
|
+
const styleMap = this._styleMap;
|
|
27482
|
+
if (!styleMap || !styleMap.justifySpaceMeasurements) {
|
|
27483
|
+
console.warn('🔤 BROWSER JUSTIFY: No justify space measurements available');
|
|
27484
|
+
return;
|
|
27485
|
+
}
|
|
27486
|
+
const spaceWidths = styleMap.justifySpaceMeasurements;
|
|
27487
|
+
console.log('🔤 BROWSER JUSTIFY: Applying space measurements to __charBounds');
|
|
27488
|
+
|
|
27489
|
+
// Apply space widths to character bounds
|
|
27490
|
+
this._textLines.forEach((line, lineIndex) => {
|
|
27491
|
+
if (!this.__charBounds || !this.__charBounds[lineIndex] || !spaceWidths[lineIndex]) return;
|
|
27492
|
+
const lineBounds = this.__charBounds[lineIndex];
|
|
27493
|
+
const lineSpaceWidths = spaceWidths[lineIndex];
|
|
27494
|
+
let spaceIndex = 0;
|
|
27495
|
+
for (let charIndex = 0; charIndex < line.length; charIndex++) {
|
|
27496
|
+
if (/\s/.test(line[charIndex]) && spaceIndex < lineSpaceWidths.length) {
|
|
27497
|
+
const expandedWidth = lineSpaceWidths[spaceIndex];
|
|
27498
|
+
if (lineBounds[charIndex]) {
|
|
27499
|
+
const oldWidth = lineBounds[charIndex].width;
|
|
27500
|
+
lineBounds[charIndex].width = expandedWidth;
|
|
27501
|
+
console.log(`🔤 Line ${lineIndex} space ${spaceIndex}: ${oldWidth.toFixed(1)}px -> ${expandedWidth.toFixed(1)}px`);
|
|
27502
|
+
}
|
|
27503
|
+
spaceIndex++;
|
|
27504
|
+
}
|
|
27505
|
+
}
|
|
27506
|
+
});
|
|
27507
|
+
}
|
|
27508
|
+
|
|
27509
|
+
/**
|
|
27510
|
+
* Fallback to default Fabric wrapping
|
|
27511
|
+
* @private
|
|
27512
|
+
*/
|
|
27513
|
+
_splitTextIntoLinesDefault(text) {
|
|
25980
27514
|
const newText = super._splitTextIntoLines(text),
|
|
25981
27515
|
graphemeLines = this._wrapText(newText.lines, this.width),
|
|
25982
27516
|
lines = new Array(graphemeLines.length);
|
|
@@ -26011,37 +27545,24 @@ class Textbox extends IText {
|
|
|
26011
27545
|
* @private
|
|
26012
27546
|
*/
|
|
26013
27547
|
initializeEventListeners() {
|
|
26014
|
-
var _this$
|
|
27548
|
+
var _this$canvas4;
|
|
26015
27549
|
// Track which side is being used for resize to handle position compensation
|
|
26016
27550
|
let resizeOrigin = null;
|
|
26017
27551
|
|
|
26018
27552
|
// Detect resize origin during resizing
|
|
26019
27553
|
this.on('resizing', e => {
|
|
26020
27554
|
// Check transform origin to determine which side is being resized
|
|
26021
|
-
console.log('🔍 Resize event data:', e);
|
|
26022
27555
|
if (e.transform) {
|
|
26023
27556
|
const {
|
|
26024
|
-
originX
|
|
26025
|
-
originY
|
|
27557
|
+
originX
|
|
26026
27558
|
} = e.transform;
|
|
26027
|
-
console.log('🔍 Transform origins:', {
|
|
26028
|
-
originX,
|
|
26029
|
-
originY
|
|
26030
|
-
});
|
|
26031
27559
|
// originX tells us which side is the anchor - opposite side is being dragged
|
|
26032
27560
|
resizeOrigin = originX === 'right' ? 'left' : originX === 'left' ? 'right' : null;
|
|
26033
|
-
console.log('🎯 Setting resizeOrigin to:', resizeOrigin);
|
|
26034
27561
|
} else if (e.originX) {
|
|
26035
27562
|
const {
|
|
26036
|
-
originX
|
|
26037
|
-
originY
|
|
27563
|
+
originX
|
|
26038
27564
|
} = e;
|
|
26039
|
-
console.log('🔍 Event origins:', {
|
|
26040
|
-
originX,
|
|
26041
|
-
originY
|
|
26042
|
-
});
|
|
26043
27565
|
resizeOrigin = originX === 'right' ? 'left' : originX === 'left' ? 'right' : null;
|
|
26044
|
-
console.log('🎯 Setting resizeOrigin to:', resizeOrigin);
|
|
26045
27566
|
}
|
|
26046
27567
|
});
|
|
26047
27568
|
|
|
@@ -26049,19 +27570,15 @@ class Textbox extends IText {
|
|
|
26049
27570
|
// Use 'modified' event which fires after user releases the mouse
|
|
26050
27571
|
this.on('modified', () => {
|
|
26051
27572
|
const currentResizeOrigin = resizeOrigin; // Capture the value before reset
|
|
26052
|
-
console.log('✅ Modified event fired - resize complete, triggering safety snap', {
|
|
26053
|
-
resizeOrigin: currentResizeOrigin
|
|
26054
|
-
});
|
|
26055
27573
|
// Small delay to ensure text layout is updated
|
|
26056
27574
|
setTimeout(() => this.safetySnapWidth(currentResizeOrigin), 10);
|
|
26057
27575
|
resizeOrigin = null; // Reset after capturing
|
|
26058
27576
|
});
|
|
26059
27577
|
|
|
26060
27578
|
// Also listen to canvas-level modified event as backup
|
|
26061
|
-
(_this$
|
|
27579
|
+
(_this$canvas4 = this.canvas) === null || _this$canvas4 === void 0 || _this$canvas4.on('object:modified', e => {
|
|
26062
27580
|
if (e.target === this) {
|
|
26063
27581
|
const currentResizeOrigin = resizeOrigin; // Capture the value before reset
|
|
26064
|
-
console.log('✅ Canvas object:modified fired for this textbox');
|
|
26065
27582
|
setTimeout(() => this.safetySnapWidth(currentResizeOrigin), 10);
|
|
26066
27583
|
resizeOrigin = null; // Reset after capturing
|
|
26067
27584
|
}
|
|
@@ -26076,38 +27593,17 @@ class Textbox extends IText {
|
|
|
26076
27593
|
* @param resizeOrigin - Which side was used for resizing ('left' or 'right')
|
|
26077
27594
|
*/
|
|
26078
27595
|
safetySnapWidth(resizeOrigin) {
|
|
26079
|
-
var _this$_textLines;
|
|
26080
|
-
console.log('🔍 safetySnapWidth called', {
|
|
26081
|
-
isWrapping: this.isWrapping,
|
|
26082
|
-
hasTextLines: !!this._textLines,
|
|
26083
|
-
lineCount: ((_this$_textLines = this._textLines) === null || _this$_textLines === void 0 ? void 0 : _this$_textLines.length) || 0,
|
|
26084
|
-
currentWidth: this.width,
|
|
26085
|
-
type: this.type,
|
|
26086
|
-
text: this.text
|
|
26087
|
-
});
|
|
26088
|
-
|
|
26089
27596
|
// For Textbox objects, we always want to check for clipping regardless of isWrapping flag
|
|
26090
27597
|
if (!this._textLines || this.type.toLowerCase() !== 'textbox' || this._textLines.length === 0) {
|
|
26091
|
-
var _this$_textLines2;
|
|
26092
|
-
console.log('❌ Early return - missing requirements', {
|
|
26093
|
-
hasTextLines: !!this._textLines,
|
|
26094
|
-
typeMatch: this.type.toLowerCase() === 'textbox',
|
|
26095
|
-
actualType: this.type,
|
|
26096
|
-
hasLines: ((_this$_textLines2 = this._textLines) === null || _this$_textLines2 === void 0 ? void 0 : _this$_textLines2.length) > 0
|
|
26097
|
-
});
|
|
26098
27598
|
return;
|
|
26099
27599
|
}
|
|
26100
27600
|
const lineCount = this._textLines.length;
|
|
26101
27601
|
if (lineCount === 0) return;
|
|
26102
|
-
|
|
26103
|
-
// Check all lines, not just the last one
|
|
26104
|
-
let maxActualLineWidth = 0; // Actual measured width without buffers
|
|
26105
27602
|
let maxRequiredWidth = 0; // Width including RTL buffer
|
|
26106
27603
|
|
|
26107
27604
|
for (let i = 0; i < lineCount; i++) {
|
|
26108
27605
|
const lineText = this._textLines[i].join(''); // Convert grapheme array to string
|
|
26109
27606
|
const lineWidth = this.getLineWidth(i);
|
|
26110
|
-
maxActualLineWidth = Math.max(maxActualLineWidth, lineWidth);
|
|
26111
27607
|
|
|
26112
27608
|
// RTL detection - regex for Arabic, Hebrew, and other RTL characters
|
|
26113
27609
|
const rtlRegex = /[\u0590-\u05FF\u0600-\u06FF\u0750-\u077F\uFB50-\uFDFF\uFE70-\uFEFF]/;
|
|
@@ -26124,14 +27620,9 @@ class Textbox extends IText {
|
|
|
26124
27620
|
const safetyThreshold = 2; // px - very subtle trigger
|
|
26125
27621
|
|
|
26126
27622
|
if (maxRequiredWidth > this.width - safetyThreshold) {
|
|
26127
|
-
var _this$
|
|
27623
|
+
var _this$canvas5;
|
|
26128
27624
|
// Set width to exactly what's needed + minimal safety margin
|
|
26129
27625
|
const newWidth = maxRequiredWidth + 1; // Add just 1px safety margin
|
|
26130
|
-
console.log(`Safety snap: ${this.width.toFixed(0)}px -> ${newWidth.toFixed(0)}px`, {
|
|
26131
|
-
maxActualLineWidth: maxActualLineWidth.toFixed(1),
|
|
26132
|
-
maxRequiredWidth: maxRequiredWidth.toFixed(1),
|
|
26133
|
-
difference: (newWidth - this.width).toFixed(1)
|
|
26134
|
-
});
|
|
26135
27626
|
|
|
26136
27627
|
// Store original position before width change
|
|
26137
27628
|
const originalLeft = this.left;
|
|
@@ -26147,19 +27638,12 @@ class Textbox extends IText {
|
|
|
26147
27638
|
// Only compensate position when resizing from left handle
|
|
26148
27639
|
// Right handle resize doesn't shift the text position
|
|
26149
27640
|
if (resizeOrigin === 'left') {
|
|
26150
|
-
console.log('🔧 Compensating for left-side resize', {
|
|
26151
|
-
originalLeft,
|
|
26152
|
-
widthIncrease,
|
|
26153
|
-
newLeft: originalLeft - widthIncrease
|
|
26154
|
-
});
|
|
26155
27641
|
// When resizing from left, the expansion pushes text right
|
|
26156
27642
|
// Compensate by moving the textbox left by the width increase
|
|
26157
27643
|
this.set({
|
|
26158
27644
|
'left': originalLeft - widthIncrease,
|
|
26159
27645
|
'top': originalTop
|
|
26160
27646
|
});
|
|
26161
|
-
} else {
|
|
26162
|
-
console.log('✅ Right-side resize, no compensation needed');
|
|
26163
27647
|
}
|
|
26164
27648
|
this.setCoords();
|
|
26165
27649
|
|
|
@@ -26169,7 +27653,88 @@ class Textbox extends IText {
|
|
|
26169
27653
|
this.__overlayEditor.refresh();
|
|
26170
27654
|
}, 0);
|
|
26171
27655
|
}
|
|
26172
|
-
(_this$
|
|
27656
|
+
(_this$canvas5 = this.canvas) === null || _this$canvas5 === void 0 || _this$canvas5.requestRenderAll();
|
|
27657
|
+
}
|
|
27658
|
+
}
|
|
27659
|
+
|
|
27660
|
+
/**
|
|
27661
|
+
* Fix character selection mismatch after JSON loading for browser-wrapped fonts
|
|
27662
|
+
* @private
|
|
27663
|
+
*/
|
|
27664
|
+
_fixCharacterMappingAfterJsonLoad() {
|
|
27665
|
+
if (this._usingBrowserWrapping) {
|
|
27666
|
+
// Clear all cached states to force fresh text layout calculation
|
|
27667
|
+
this._browserWrapCache = null;
|
|
27668
|
+
this._lastDimensionState = null;
|
|
27669
|
+
|
|
27670
|
+
// Force complete re-initialization
|
|
27671
|
+
this.initDimensions();
|
|
27672
|
+
this._forceClearCache = true;
|
|
27673
|
+
|
|
27674
|
+
// Ensure canvas refresh
|
|
27675
|
+
this.setCoords();
|
|
27676
|
+
if (this.canvas) {
|
|
27677
|
+
this.canvas.requestRenderAll();
|
|
27678
|
+
}
|
|
27679
|
+
}
|
|
27680
|
+
}
|
|
27681
|
+
|
|
27682
|
+
/**
|
|
27683
|
+
* Force complete textbox re-initialization (useful after JSON loading)
|
|
27684
|
+
* Overrides Text version with Textbox-specific logic
|
|
27685
|
+
*/
|
|
27686
|
+
forceTextReinitialization() {
|
|
27687
|
+
console.log('🔄 Force reinitializing Textbox object');
|
|
27688
|
+
|
|
27689
|
+
// CRITICAL: Ensure textbox is marked as initialized
|
|
27690
|
+
this.initialized = true;
|
|
27691
|
+
|
|
27692
|
+
// Clear all caches and force dirty state
|
|
27693
|
+
this._clearCache();
|
|
27694
|
+
this.dirty = true;
|
|
27695
|
+
this.dynamicMinWidth = 0;
|
|
27696
|
+
|
|
27697
|
+
// Force isEditing false to ensure clean state
|
|
27698
|
+
this.isEditing = false;
|
|
27699
|
+
console.log(' → Set initialized=true, dirty=true, cleared caches');
|
|
27700
|
+
|
|
27701
|
+
// Re-initialize dimensions (this will handle justify properly)
|
|
27702
|
+
this.initDimensions();
|
|
27703
|
+
|
|
27704
|
+
// Double-check that justify was applied by checking space widths
|
|
27705
|
+
if (this.textAlign.includes('justify') && this.__charBounds) {
|
|
27706
|
+
setTimeout(() => {
|
|
27707
|
+
var _this$canvas6;
|
|
27708
|
+
// Verify justify was applied by checking if space widths vary
|
|
27709
|
+
let hasVariableSpaces = false;
|
|
27710
|
+
this.__charBounds.forEach((lineBounds, i) => {
|
|
27711
|
+
if (lineBounds && this._textLines && this._textLines[i]) {
|
|
27712
|
+
const spaces = lineBounds.filter((bound, j) => /\s/.test(this._textLines[i][j]));
|
|
27713
|
+
if (spaces.length > 1) {
|
|
27714
|
+
const firstSpaceWidth = spaces[0].width;
|
|
27715
|
+
hasVariableSpaces = spaces.some(space => Math.abs(space.width - firstSpaceWidth) > 0.1);
|
|
27716
|
+
}
|
|
27717
|
+
}
|
|
27718
|
+
});
|
|
27719
|
+
if (!hasVariableSpaces && this.__charBounds.length > 0) {
|
|
27720
|
+
console.warn(' ⚠️ Justify spaces still uniform - forcing enlargeSpaces again');
|
|
27721
|
+
if (this.enlargeSpaces) {
|
|
27722
|
+
this.enlargeSpaces();
|
|
27723
|
+
}
|
|
27724
|
+
} else {
|
|
27725
|
+
console.log(' ✅ Justify spaces properly expanded');
|
|
27726
|
+
}
|
|
27727
|
+
|
|
27728
|
+
// Ensure height is recalculated - use browser height if available
|
|
27729
|
+
if (this._usingBrowserWrapping && this._actualBrowserHeight) {
|
|
27730
|
+
this.height = this._actualBrowserHeight;
|
|
27731
|
+
console.log(`🔤 JUSTIFY: Preserved browser height: ${this.height}px`);
|
|
27732
|
+
} else {
|
|
27733
|
+
this.height = this.calcTextHeight();
|
|
27734
|
+
console.log(`🔧 JUSTIFY: Used calcTextHeight: ${this.height}px`);
|
|
27735
|
+
}
|
|
27736
|
+
(_this$canvas6 = this.canvas) === null || _this$canvas6 === void 0 || _this$canvas6.requestRenderAll();
|
|
27737
|
+
}, 10);
|
|
26173
27738
|
}
|
|
26174
27739
|
}
|
|
26175
27740
|
|