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