@nasser-sw/fabric 7.0.1-beta1 → 7.0.1-beta3
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/dist/index.js +504 -102
- 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 +504 -102
- package/dist/index.mjs.map +1 -1
- package/dist/index.node.cjs +504 -102
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.mjs +504 -102
- 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/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.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 +64 -12
- package/dist/src/shapes/Text/Text.mjs.map +1 -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 +2 -52
- 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/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 +143 -9
- package/dist/src/text/overlayEditor.mjs.map +1 -1
- 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/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.map +1 -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/overlayEditor.d.ts.map +1 -1
- 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 +1048 -0
- package/package.json +164 -164
- package/src/shapes/Polyline.ts +70 -29
- package/src/shapes/Text/Text.ts +79 -14
- package/src/shapes/Textbox.ts +1 -1
- package/src/shapes/Triangle.spec.ts +76 -0
- package/src/shapes/Triangle.ts +85 -15
- package/src/text/overlayEditor.ts +152 -12
- package/src/util/misc/cornerRadius.spec.ts +141 -0
- package/src/util/misc/cornerRadius.ts +269 -0
package/dist/index.node.cjs
CHANGED
|
@@ -412,7 +412,7 @@ class Cache {
|
|
|
412
412
|
}
|
|
413
413
|
const cache = new Cache();
|
|
414
414
|
|
|
415
|
-
var version = "7.0.
|
|
415
|
+
var version = "7.0.1-beta2";
|
|
416
416
|
|
|
417
417
|
// use this syntax so babel plugin see this import here
|
|
418
418
|
const VERSION = version;
|
|
@@ -17892,10 +17892,189 @@ _defineProperty(Line, "ATTRIBUTE_NAMES", SHARED_ATTRIBUTES.concat(coordProps));
|
|
|
17892
17892
|
classRegistry.setClass(Line);
|
|
17893
17893
|
classRegistry.setSVGClass(Line);
|
|
17894
17894
|
|
|
17895
|
+
/**
|
|
17896
|
+
* Calculate the distance between two points
|
|
17897
|
+
*/
|
|
17898
|
+
function pointDistance(p1, p2) {
|
|
17899
|
+
return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
|
|
17900
|
+
}
|
|
17901
|
+
|
|
17902
|
+
/**
|
|
17903
|
+
* Normalize a vector
|
|
17904
|
+
*/
|
|
17905
|
+
function normalizeVector(vector) {
|
|
17906
|
+
const length = Math.sqrt(vector.x * vector.x + vector.y * vector.y);
|
|
17907
|
+
if (length === 0) return {
|
|
17908
|
+
x: 0,
|
|
17909
|
+
y: 0
|
|
17910
|
+
};
|
|
17911
|
+
return {
|
|
17912
|
+
x: vector.x / length,
|
|
17913
|
+
y: vector.y / length
|
|
17914
|
+
};
|
|
17915
|
+
}
|
|
17916
|
+
|
|
17917
|
+
/**
|
|
17918
|
+
* Get the maximum allowed radius for a corner based on adjacent edge lengths
|
|
17919
|
+
*/
|
|
17920
|
+
function getMaxRadius(prevPoint, currentPoint, nextPoint) {
|
|
17921
|
+
const dist1 = pointDistance(prevPoint, currentPoint);
|
|
17922
|
+
const dist2 = pointDistance(currentPoint, nextPoint);
|
|
17923
|
+
return Math.min(dist1, dist2) / 2;
|
|
17924
|
+
}
|
|
17925
|
+
|
|
17926
|
+
/**
|
|
17927
|
+
* Calculate rounded corner data for a single corner
|
|
17928
|
+
*/
|
|
17929
|
+
function calculateRoundedCorner(prevPoint, currentPoint, nextPoint, radius) {
|
|
17930
|
+
// Calculate edge vectors
|
|
17931
|
+
const edge1 = {
|
|
17932
|
+
x: currentPoint.x - prevPoint.x,
|
|
17933
|
+
y: currentPoint.y - prevPoint.y
|
|
17934
|
+
};
|
|
17935
|
+
const edge2 = {
|
|
17936
|
+
x: nextPoint.x - currentPoint.x,
|
|
17937
|
+
y: nextPoint.y - currentPoint.y
|
|
17938
|
+
};
|
|
17939
|
+
|
|
17940
|
+
// Normalize edge vectors
|
|
17941
|
+
const norm1 = normalizeVector(edge1);
|
|
17942
|
+
const norm2 = normalizeVector(edge2);
|
|
17943
|
+
|
|
17944
|
+
// Calculate the maximum allowed radius
|
|
17945
|
+
const maxRadius = getMaxRadius(prevPoint, currentPoint, nextPoint);
|
|
17946
|
+
const actualRadius = Math.min(radius, maxRadius);
|
|
17947
|
+
|
|
17948
|
+
// Calculate start and end points of the rounded corner
|
|
17949
|
+
const startPoint = {
|
|
17950
|
+
x: currentPoint.x - norm1.x * actualRadius,
|
|
17951
|
+
y: currentPoint.y - norm1.y * actualRadius
|
|
17952
|
+
};
|
|
17953
|
+
const endPoint = {
|
|
17954
|
+
x: currentPoint.x + norm2.x * actualRadius,
|
|
17955
|
+
y: currentPoint.y + norm2.y * actualRadius
|
|
17956
|
+
};
|
|
17957
|
+
|
|
17958
|
+
// Calculate control points for bezier curve
|
|
17959
|
+
// Using the magic number kRect for optimal circular approximation
|
|
17960
|
+
const controlOffset = actualRadius * kRect;
|
|
17961
|
+
const cp1 = {
|
|
17962
|
+
x: startPoint.x + norm1.x * controlOffset,
|
|
17963
|
+
y: startPoint.y + norm1.y * controlOffset
|
|
17964
|
+
};
|
|
17965
|
+
const cp2 = {
|
|
17966
|
+
x: endPoint.x - norm2.x * controlOffset,
|
|
17967
|
+
y: endPoint.y - norm2.y * controlOffset
|
|
17968
|
+
};
|
|
17969
|
+
return {
|
|
17970
|
+
corner: currentPoint,
|
|
17971
|
+
start: startPoint,
|
|
17972
|
+
end: endPoint,
|
|
17973
|
+
cp1,
|
|
17974
|
+
cp2,
|
|
17975
|
+
actualRadius
|
|
17976
|
+
};
|
|
17977
|
+
}
|
|
17978
|
+
|
|
17979
|
+
/**
|
|
17980
|
+
* Apply corner radius to a polygon defined by points
|
|
17981
|
+
*/
|
|
17982
|
+
function applyCornerRadiusToPolygon(points, radius) {
|
|
17983
|
+
let radiusAsPercentage = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
17984
|
+
if (points.length < 3) {
|
|
17985
|
+
throw new Error('Polygon must have at least 3 points');
|
|
17986
|
+
}
|
|
17987
|
+
|
|
17988
|
+
// Calculate bounding box if radius is percentage-based
|
|
17989
|
+
let actualRadius = radius;
|
|
17990
|
+
if (radiusAsPercentage) {
|
|
17991
|
+
const minX = Math.min(...points.map(p => p.x));
|
|
17992
|
+
const maxX = Math.max(...points.map(p => p.x));
|
|
17993
|
+
const minY = Math.min(...points.map(p => p.y));
|
|
17994
|
+
const maxY = Math.max(...points.map(p => p.y));
|
|
17995
|
+
const width = maxX - minX;
|
|
17996
|
+
const height = maxY - minY;
|
|
17997
|
+
const minDimension = Math.min(width, height);
|
|
17998
|
+
actualRadius = radius / 100 * minDimension;
|
|
17999
|
+
}
|
|
18000
|
+
const roundedCorners = [];
|
|
18001
|
+
for (let i = 0; i < points.length; i++) {
|
|
18002
|
+
const prevIndex = (i - 1 + points.length) % points.length;
|
|
18003
|
+
const nextIndex = (i + 1) % points.length;
|
|
18004
|
+
const prevPoint = points[prevIndex];
|
|
18005
|
+
const currentPoint = points[i];
|
|
18006
|
+
const nextPoint = points[nextIndex];
|
|
18007
|
+
const roundedCorner = calculateRoundedCorner(prevPoint, currentPoint, nextPoint, actualRadius);
|
|
18008
|
+
roundedCorners.push(roundedCorner);
|
|
18009
|
+
}
|
|
18010
|
+
return roundedCorners;
|
|
18011
|
+
}
|
|
18012
|
+
|
|
18013
|
+
/**
|
|
18014
|
+
* Render a rounded polygon to a canvas context
|
|
18015
|
+
*/
|
|
18016
|
+
function renderRoundedPolygon(ctx, roundedCorners) {
|
|
18017
|
+
let closed = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
|
|
18018
|
+
if (roundedCorners.length === 0) return;
|
|
18019
|
+
ctx.beginPath();
|
|
18020
|
+
|
|
18021
|
+
// Start at the first corner's start point
|
|
18022
|
+
const firstCorner = roundedCorners[0];
|
|
18023
|
+
ctx.moveTo(firstCorner.start.x, firstCorner.start.y);
|
|
18024
|
+
for (let i = 0; i < roundedCorners.length; i++) {
|
|
18025
|
+
const corner = roundedCorners[i];
|
|
18026
|
+
const nextIndex = (i + 1) % roundedCorners.length;
|
|
18027
|
+
const nextCorner = roundedCorners[nextIndex];
|
|
18028
|
+
|
|
18029
|
+
// Draw the rounded corner using bezier curve
|
|
18030
|
+
ctx.bezierCurveTo(corner.cp1.x, corner.cp1.y, corner.cp2.x, corner.cp2.y, corner.end.x, corner.end.y);
|
|
18031
|
+
|
|
18032
|
+
// Draw line to next corner's start point (if not the last segment in open path)
|
|
18033
|
+
if (i < roundedCorners.length - 1 || closed) {
|
|
18034
|
+
ctx.lineTo(nextCorner.start.x, nextCorner.start.y);
|
|
18035
|
+
}
|
|
18036
|
+
}
|
|
18037
|
+
if (closed) {
|
|
18038
|
+
ctx.closePath();
|
|
18039
|
+
}
|
|
18040
|
+
}
|
|
18041
|
+
|
|
18042
|
+
/**
|
|
18043
|
+
* Generate SVG path data for a rounded polygon
|
|
18044
|
+
*/
|
|
18045
|
+
function generateRoundedPolygonPath(roundedCorners) {
|
|
18046
|
+
let closed = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
|
|
18047
|
+
if (roundedCorners.length === 0) return '';
|
|
18048
|
+
const pathData = [];
|
|
18049
|
+
const firstCorner = roundedCorners[0];
|
|
18050
|
+
|
|
18051
|
+
// Move to first corner's start point
|
|
18052
|
+
pathData.push(`M ${firstCorner.start.x} ${firstCorner.start.y}`);
|
|
18053
|
+
for (let i = 0; i < roundedCorners.length; i++) {
|
|
18054
|
+
const corner = roundedCorners[i];
|
|
18055
|
+
const nextIndex = (i + 1) % roundedCorners.length;
|
|
18056
|
+
const nextCorner = roundedCorners[nextIndex];
|
|
18057
|
+
|
|
18058
|
+
// Add bezier curve for the rounded corner
|
|
18059
|
+
pathData.push(`C ${corner.cp1.x} ${corner.cp1.y} ${corner.cp2.x} ${corner.cp2.y} ${corner.end.x} ${corner.end.y}`);
|
|
18060
|
+
|
|
18061
|
+
// Add line to next corner's start point (if not the last segment in open path)
|
|
18062
|
+
if (i < roundedCorners.length - 1 || closed) {
|
|
18063
|
+
pathData.push(`L ${nextCorner.start.x} ${nextCorner.start.y}`);
|
|
18064
|
+
}
|
|
18065
|
+
}
|
|
18066
|
+
if (closed) {
|
|
18067
|
+
pathData.push('Z');
|
|
18068
|
+
}
|
|
18069
|
+
return pathData.join(' ');
|
|
18070
|
+
}
|
|
18071
|
+
|
|
17895
18072
|
const triangleDefaultValues = {
|
|
17896
18073
|
width: 100,
|
|
17897
|
-
height: 100
|
|
18074
|
+
height: 100,
|
|
18075
|
+
cornerRadius: 0
|
|
17898
18076
|
};
|
|
18077
|
+
const TRIANGLE_PROPS = ['cornerRadius'];
|
|
17899
18078
|
class Triangle extends FabricObject {
|
|
17900
18079
|
static getDefaults() {
|
|
17901
18080
|
return {
|
|
@@ -17914,34 +18093,90 @@ class Triangle extends FabricObject {
|
|
|
17914
18093
|
this.setOptions(options);
|
|
17915
18094
|
}
|
|
17916
18095
|
|
|
18096
|
+
/**
|
|
18097
|
+
* Get triangle points as an array of XY coordinates
|
|
18098
|
+
* @private
|
|
18099
|
+
*/
|
|
18100
|
+
_getTrianglePoints() {
|
|
18101
|
+
const widthBy2 = this.width / 2;
|
|
18102
|
+
const heightBy2 = this.height / 2;
|
|
18103
|
+
return [{
|
|
18104
|
+
x: -widthBy2,
|
|
18105
|
+
y: heightBy2
|
|
18106
|
+
},
|
|
18107
|
+
// bottom left
|
|
18108
|
+
{
|
|
18109
|
+
x: 0,
|
|
18110
|
+
y: -heightBy2
|
|
18111
|
+
},
|
|
18112
|
+
// top center
|
|
18113
|
+
{
|
|
18114
|
+
x: widthBy2,
|
|
18115
|
+
y: heightBy2
|
|
18116
|
+
} // bottom right
|
|
18117
|
+
];
|
|
18118
|
+
}
|
|
18119
|
+
|
|
17917
18120
|
/**
|
|
17918
18121
|
* @private
|
|
17919
18122
|
* @param {CanvasRenderingContext2D} ctx Context to render on
|
|
17920
18123
|
*/
|
|
17921
18124
|
_render(ctx) {
|
|
17922
|
-
|
|
17923
|
-
|
|
17924
|
-
|
|
17925
|
-
|
|
17926
|
-
|
|
17927
|
-
|
|
17928
|
-
|
|
18125
|
+
if (this.cornerRadius > 0) {
|
|
18126
|
+
// Render rounded triangle
|
|
18127
|
+
const points = this._getTrianglePoints();
|
|
18128
|
+
const roundedCorners = applyCornerRadiusToPolygon(points, this.cornerRadius);
|
|
18129
|
+
renderRoundedPolygon(ctx, roundedCorners, true);
|
|
18130
|
+
} else {
|
|
18131
|
+
// Render sharp triangle (original implementation)
|
|
18132
|
+
const widthBy2 = this.width / 2;
|
|
18133
|
+
const heightBy2 = this.height / 2;
|
|
18134
|
+
ctx.beginPath();
|
|
18135
|
+
ctx.moveTo(-widthBy2, heightBy2);
|
|
18136
|
+
ctx.lineTo(0, -heightBy2);
|
|
18137
|
+
ctx.lineTo(widthBy2, heightBy2);
|
|
18138
|
+
ctx.closePath();
|
|
18139
|
+
}
|
|
17929
18140
|
this._renderPaintInOrder(ctx);
|
|
17930
18141
|
}
|
|
17931
18142
|
|
|
18143
|
+
/**
|
|
18144
|
+
* Returns object representation of an instance
|
|
18145
|
+
* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
|
|
18146
|
+
* @return {Object} object representation of an instance
|
|
18147
|
+
*/
|
|
18148
|
+
toObject() {
|
|
18149
|
+
let propertiesToInclude = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
|
|
18150
|
+
return super.toObject([...TRIANGLE_PROPS, ...propertiesToInclude]);
|
|
18151
|
+
}
|
|
18152
|
+
|
|
17932
18153
|
/**
|
|
17933
18154
|
* Returns svg representation of an instance
|
|
17934
18155
|
* @return {Array} an array of strings with the specific svg representation
|
|
17935
18156
|
* of the instance
|
|
17936
18157
|
*/
|
|
17937
18158
|
_toSVG() {
|
|
17938
|
-
|
|
17939
|
-
|
|
17940
|
-
points =
|
|
17941
|
-
|
|
18159
|
+
if (this.cornerRadius > 0) {
|
|
18160
|
+
// Generate rounded triangle as path
|
|
18161
|
+
const points = this._getTrianglePoints();
|
|
18162
|
+
const roundedCorners = applyCornerRadiusToPolygon(points, this.cornerRadius);
|
|
18163
|
+
const pathData = generateRoundedPolygonPath(roundedCorners, true);
|
|
18164
|
+
return ['<path ', 'COMMON_PARTS', `d="${pathData}" />`];
|
|
18165
|
+
} else {
|
|
18166
|
+
// Original sharp triangle implementation
|
|
18167
|
+
const widthBy2 = this.width / 2;
|
|
18168
|
+
const heightBy2 = this.height / 2;
|
|
18169
|
+
const points = `${-widthBy2} ${heightBy2},0 ${-heightBy2},${widthBy2} ${heightBy2}`;
|
|
18170
|
+
return ['<polygon ', 'COMMON_PARTS', 'points="', points, '" />'];
|
|
18171
|
+
}
|
|
17942
18172
|
}
|
|
17943
18173
|
}
|
|
18174
|
+
/**
|
|
18175
|
+
* Corner radius for rounded triangle corners
|
|
18176
|
+
* @type Number
|
|
18177
|
+
*/
|
|
17944
18178
|
_defineProperty(Triangle, "type", 'Triangle');
|
|
18179
|
+
_defineProperty(Triangle, "cacheProperties", [...cacheProperties, ...TRIANGLE_PROPS]);
|
|
17945
18180
|
_defineProperty(Triangle, "ownDefaults", triangleDefaultValues);
|
|
17946
18181
|
classRegistry.setClass(Triangle);
|
|
17947
18182
|
classRegistry.setSVGClass(Triangle);
|
|
@@ -18106,7 +18341,8 @@ const polylineDefaultValues = {
|
|
|
18106
18341
|
/**
|
|
18107
18342
|
* @deprecated transient option soon to be removed in favor of a different design
|
|
18108
18343
|
*/
|
|
18109
|
-
exactBoundingBox: false
|
|
18344
|
+
exactBoundingBox: false,
|
|
18345
|
+
cornerRadius: 0
|
|
18110
18346
|
};
|
|
18111
18347
|
class Polyline extends FabricObject {
|
|
18112
18348
|
static getDefaults() {
|
|
@@ -18320,7 +18556,7 @@ class Polyline extends FabricObject {
|
|
|
18320
18556
|
toObject() {
|
|
18321
18557
|
let propertiesToInclude = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
|
|
18322
18558
|
return {
|
|
18323
|
-
...super.toObject(propertiesToInclude),
|
|
18559
|
+
...super.toObject(['cornerRadius', ...propertiesToInclude]),
|
|
18324
18560
|
points: this.points.map(_ref => {
|
|
18325
18561
|
let {
|
|
18326
18562
|
x,
|
|
@@ -18340,14 +18576,28 @@ class Polyline extends FabricObject {
|
|
|
18340
18576
|
* of the instance
|
|
18341
18577
|
*/
|
|
18342
18578
|
_toSVG() {
|
|
18343
|
-
|
|
18344
|
-
|
|
18345
|
-
|
|
18346
|
-
|
|
18347
|
-
|
|
18348
|
-
|
|
18579
|
+
if (this.cornerRadius > 0 && this.points.length >= 3) {
|
|
18580
|
+
// Generate rounded polygon/polyline as path
|
|
18581
|
+
const diffX = this.pathOffset.x;
|
|
18582
|
+
const diffY = this.pathOffset.y;
|
|
18583
|
+
const adjustedPoints = this.points.map(point => ({
|
|
18584
|
+
x: point.x - diffX,
|
|
18585
|
+
y: point.y - diffY
|
|
18586
|
+
}));
|
|
18587
|
+
const roundedCorners = applyCornerRadiusToPolygon(adjustedPoints, this.cornerRadius);
|
|
18588
|
+
const pathData = generateRoundedPolygonPath(roundedCorners, !this.isOpen());
|
|
18589
|
+
return ['<path ', 'COMMON_PARTS', `d="${pathData}" />\n`];
|
|
18590
|
+
} else {
|
|
18591
|
+
// Original sharp corners implementation
|
|
18592
|
+
const points = [];
|
|
18593
|
+
const diffX = this.pathOffset.x;
|
|
18594
|
+
const diffY = this.pathOffset.y;
|
|
18595
|
+
const NUM_FRACTION_DIGITS = config.NUM_FRACTION_DIGITS;
|
|
18596
|
+
for (let i = 0, len = this.points.length; i < len; i++) {
|
|
18597
|
+
points.push(toFixed(this.points[i].x - diffX, NUM_FRACTION_DIGITS), ',', toFixed(this.points[i].y - diffY, NUM_FRACTION_DIGITS), ' ');
|
|
18598
|
+
}
|
|
18599
|
+
return [`<${this.constructor.type.toLowerCase()} `, 'COMMON_PARTS', `points="${points.join('')}" />\n`];
|
|
18349
18600
|
}
|
|
18350
|
-
return [`<${this.constructor.type.toLowerCase()} `, 'COMMON_PARTS', `points="${points.join('')}" />\n`];
|
|
18351
18601
|
}
|
|
18352
18602
|
|
|
18353
18603
|
/**
|
|
@@ -18363,13 +18613,24 @@ class Polyline extends FabricObject {
|
|
|
18363
18613
|
// NaN comes from parseFloat of a empty string in parser
|
|
18364
18614
|
return;
|
|
18365
18615
|
}
|
|
18366
|
-
|
|
18367
|
-
|
|
18368
|
-
|
|
18369
|
-
|
|
18370
|
-
|
|
18616
|
+
if (this.cornerRadius > 0 && len >= 3) {
|
|
18617
|
+
// Render with rounded corners
|
|
18618
|
+
const adjustedPoints = this.points.map(point => ({
|
|
18619
|
+
x: point.x - x,
|
|
18620
|
+
y: point.y - y
|
|
18621
|
+
}));
|
|
18622
|
+
const roundedCorners = applyCornerRadiusToPolygon(adjustedPoints, this.cornerRadius);
|
|
18623
|
+
renderRoundedPolygon(ctx, roundedCorners, !this.isOpen());
|
|
18624
|
+
} else {
|
|
18625
|
+
// Original sharp corners implementation
|
|
18626
|
+
ctx.beginPath();
|
|
18627
|
+
ctx.moveTo(this.points[0].x - x, this.points[0].y - y);
|
|
18628
|
+
for (let i = 0; i < len; i++) {
|
|
18629
|
+
const point = this.points[i];
|
|
18630
|
+
ctx.lineTo(point.x - x, point.y - y);
|
|
18631
|
+
}
|
|
18632
|
+
!this.isOpen() && ctx.closePath();
|
|
18371
18633
|
}
|
|
18372
|
-
!this.isOpen() && ctx.closePath();
|
|
18373
18634
|
this._renderPaintInOrder(ctx);
|
|
18374
18635
|
}
|
|
18375
18636
|
|
|
@@ -18434,10 +18695,15 @@ class Polyline extends FabricObject {
|
|
|
18434
18695
|
* @type Boolean
|
|
18435
18696
|
* @default false
|
|
18436
18697
|
*/
|
|
18698
|
+
/**
|
|
18699
|
+
* Corner radius for rounded corners
|
|
18700
|
+
* @type Number
|
|
18701
|
+
* @default 0
|
|
18702
|
+
*/
|
|
18437
18703
|
_defineProperty(Polyline, "ownDefaults", polylineDefaultValues);
|
|
18438
18704
|
_defineProperty(Polyline, "type", 'Polyline');
|
|
18439
18705
|
_defineProperty(Polyline, "layoutProperties", [SKEW_X, SKEW_Y, 'strokeLineCap', 'strokeLineJoin', 'strokeMiterLimit', 'strokeWidth', 'strokeUniform', 'points']);
|
|
18440
|
-
_defineProperty(Polyline, "cacheProperties", [...cacheProperties, 'points']);
|
|
18706
|
+
_defineProperty(Polyline, "cacheProperties", [...cacheProperties, 'points', 'cornerRadius']);
|
|
18441
18707
|
_defineProperty(Polyline, "ATTRIBUTE_NAMES", [...SHARED_ATTRIBUTES]);
|
|
18442
18708
|
classRegistry.setClass(Polyline);
|
|
18443
18709
|
classRegistry.setSVGClass(Polyline);
|
|
@@ -20247,6 +20513,7 @@ class FabricText extends StyledText {
|
|
|
20247
20513
|
*/
|
|
20248
20514
|
enlargeSpaces() {
|
|
20249
20515
|
let diffSpace, currentLineWidth, numberOfSpaces, accumulatedSpace, line, charBound, spaces;
|
|
20516
|
+
const isRtl = this.direction === 'rtl';
|
|
20250
20517
|
for (let i = 0, len = this._textLines.length; i < len; i++) {
|
|
20251
20518
|
if (this.textAlign !== JUSTIFY && (i === len - 1 || this.isEndOfWrapping(i))) {
|
|
20252
20519
|
continue;
|
|
@@ -20257,15 +20524,44 @@ class FabricText extends StyledText {
|
|
|
20257
20524
|
if (currentLineWidth < this.width && (spaces = this.textLines[i].match(this._reSpacesAndTabs))) {
|
|
20258
20525
|
numberOfSpaces = spaces.length;
|
|
20259
20526
|
diffSpace = (this.width - currentLineWidth) / numberOfSpaces;
|
|
20260
|
-
|
|
20261
|
-
|
|
20262
|
-
|
|
20263
|
-
|
|
20264
|
-
|
|
20265
|
-
|
|
20266
|
-
|
|
20267
|
-
|
|
20268
|
-
|
|
20527
|
+
if (isRtl) {
|
|
20528
|
+
for (let j = 0; j < line.length; j++) {
|
|
20529
|
+
if (this._reSpaceAndTab.test(line[j])) ;
|
|
20530
|
+
}
|
|
20531
|
+
|
|
20532
|
+
// For RTL, we need to work backwards through the visual positions
|
|
20533
|
+
// but still update logical positions correctly
|
|
20534
|
+
let spaceCount = 0;
|
|
20535
|
+
for (let j = 0; j <= line.length; j++) {
|
|
20536
|
+
charBound = this.__charBounds[i][j];
|
|
20537
|
+
if (charBound) {
|
|
20538
|
+
if (this._reSpaceAndTab.test(line[j])) {
|
|
20539
|
+
charBound.width += diffSpace;
|
|
20540
|
+
charBound.kernedWidth += diffSpace;
|
|
20541
|
+
spaceCount++;
|
|
20542
|
+
}
|
|
20543
|
+
|
|
20544
|
+
// For RTL, shift all characters to the right by the total expansion
|
|
20545
|
+
// minus the expansion that comes after this character
|
|
20546
|
+
const remainingSpaces = numberOfSpaces - spaceCount;
|
|
20547
|
+
const shiftAmount = remainingSpaces * diffSpace;
|
|
20548
|
+
charBound.left += shiftAmount;
|
|
20549
|
+
}
|
|
20550
|
+
}
|
|
20551
|
+
} else {
|
|
20552
|
+
// LTR processing (original logic)
|
|
20553
|
+
for (let j = 0; j <= line.length; j++) {
|
|
20554
|
+
charBound = this.__charBounds[i][j];
|
|
20555
|
+
if (charBound) {
|
|
20556
|
+
if (this._reSpaceAndTab.test(line[j])) {
|
|
20557
|
+
charBound.width += diffSpace;
|
|
20558
|
+
charBound.kernedWidth += diffSpace;
|
|
20559
|
+
charBound.left += accumulatedSpace;
|
|
20560
|
+
accumulatedSpace += diffSpace;
|
|
20561
|
+
} else {
|
|
20562
|
+
charBound.left += accumulatedSpace;
|
|
20563
|
+
}
|
|
20564
|
+
}
|
|
20269
20565
|
}
|
|
20270
20566
|
}
|
|
20271
20567
|
}
|
|
@@ -20923,7 +21219,15 @@ class FabricText extends StyledText {
|
|
|
20923
21219
|
if (currentDirection !== this.direction) {
|
|
20924
21220
|
ctx.canvas.setAttribute('dir', isLtr ? 'ltr' : 'rtl');
|
|
20925
21221
|
ctx.direction = isLtr ? 'ltr' : 'rtl';
|
|
20926
|
-
|
|
21222
|
+
|
|
21223
|
+
// For justify alignments, we need to set the correct canvas text alignment
|
|
21224
|
+
// This is crucial for RTL text to render in the correct order
|
|
21225
|
+
if (isJustify) {
|
|
21226
|
+
// Justify uses LEFT alignment as a base, letting the character positioning handle justification
|
|
21227
|
+
ctx.textAlign = LEFT;
|
|
21228
|
+
} else {
|
|
21229
|
+
ctx.textAlign = isLtr ? LEFT : RIGHT;
|
|
21230
|
+
}
|
|
20927
21231
|
}
|
|
20928
21232
|
top -= lineHeight * this._fontSizeFraction / this.lineHeight;
|
|
20929
21233
|
if (shortCut) {
|
|
@@ -21159,9 +21463,21 @@ class FabricText extends StyledText {
|
|
|
21159
21463
|
direction = this.direction,
|
|
21160
21464
|
isEndOfWrapping = this.isEndOfWrapping(lineIndex);
|
|
21161
21465
|
let leftOffset = 0;
|
|
21162
|
-
|
|
21163
|
-
|
|
21466
|
+
|
|
21467
|
+
// Handle justify alignments (excluding last lines and wrapped line ends)
|
|
21468
|
+
const isJustifyLine = textAlign === JUSTIFY || textAlign === JUSTIFY_CENTER && !isEndOfWrapping || textAlign === JUSTIFY_RIGHT && !isEndOfWrapping || textAlign === JUSTIFY_LEFT && !isEndOfWrapping;
|
|
21469
|
+
if (isJustifyLine) {
|
|
21470
|
+
// Justify lines should start at the left edge for LTR and right edge for RTL
|
|
21471
|
+
// The space distribution is handled by enlargeSpaces()
|
|
21472
|
+
if (direction === 'rtl') {
|
|
21473
|
+
// For RTL justify, we need to account for the line being right-aligned
|
|
21474
|
+
return 0;
|
|
21475
|
+
} else {
|
|
21476
|
+
return 0;
|
|
21477
|
+
}
|
|
21164
21478
|
}
|
|
21479
|
+
|
|
21480
|
+
// Handle non-justify alignments
|
|
21165
21481
|
if (textAlign === CENTER) {
|
|
21166
21482
|
leftOffset = lineDiff / 2;
|
|
21167
21483
|
}
|
|
@@ -21174,6 +21490,8 @@ class FabricText extends StyledText {
|
|
|
21174
21490
|
if (textAlign === JUSTIFY_RIGHT) {
|
|
21175
21491
|
leftOffset = lineDiff;
|
|
21176
21492
|
}
|
|
21493
|
+
|
|
21494
|
+
// Apply RTL adjustments for non-justify alignments
|
|
21177
21495
|
if (direction === 'rtl') {
|
|
21178
21496
|
if (textAlign === RIGHT || textAlign === JUSTIFY || textAlign === JUSTIFY_RIGHT) {
|
|
21179
21497
|
leftOffset = 0;
|
|
@@ -22169,13 +22487,86 @@ class OverlayEditor {
|
|
|
22169
22487
|
this.textarea.style.fontFamily = target.fontFamily || 'Arial';
|
|
22170
22488
|
this.textarea.style.fontWeight = String(target.fontWeight || 'normal');
|
|
22171
22489
|
this.textarea.style.fontStyle = target.fontStyle || 'normal';
|
|
22172
|
-
|
|
22490
|
+
// Handle text alignment and justification
|
|
22491
|
+
const textAlign = target.textAlign || 'left';
|
|
22492
|
+
let cssTextAlign = textAlign;
|
|
22493
|
+
|
|
22494
|
+
// Detect text direction from content for proper justify handling
|
|
22495
|
+
const autoDetectedDirection = this.firstStrongDir(this.textarea.value || '');
|
|
22496
|
+
|
|
22497
|
+
// DEBUG: Log alignment details
|
|
22498
|
+
console.log('🔍 ALIGNMENT DEBUG:');
|
|
22499
|
+
console.log(' Fabric textAlign:', textAlign);
|
|
22500
|
+
console.log(' Fabric direction:', target.direction);
|
|
22501
|
+
console.log(' Text content:', JSON.stringify(target.text));
|
|
22502
|
+
console.log(' Detected direction:', autoDetectedDirection);
|
|
22503
|
+
|
|
22504
|
+
// Map fabric.js justify to CSS
|
|
22505
|
+
if (textAlign.includes('justify')) {
|
|
22506
|
+
// Try to match fabric.js justify behavior more precisely
|
|
22507
|
+
try {
|
|
22508
|
+
// For justify, we need to replicate fabric.js space expansion
|
|
22509
|
+
// Use CSS justify but with specific settings to match fabric.js better
|
|
22510
|
+
cssTextAlign = 'justify';
|
|
22511
|
+
|
|
22512
|
+
// Set text-align-last based on justify type and detected direction
|
|
22513
|
+
// Smart justify: respect detected direction even when fabric alignment doesn't match
|
|
22514
|
+
if (textAlign === 'justify') {
|
|
22515
|
+
this.textarea.style.textAlignLast = autoDetectedDirection === 'rtl' ? 'right' : 'left';
|
|
22516
|
+
} else if (textAlign === 'justify-left') {
|
|
22517
|
+
// If text is RTL but fabric says justify-left, override to justify-right for better UX
|
|
22518
|
+
if (autoDetectedDirection === 'rtl') {
|
|
22519
|
+
this.textarea.style.textAlignLast = 'right';
|
|
22520
|
+
console.log(' → Overrode justify-left to justify-right for RTL text');
|
|
22521
|
+
} else {
|
|
22522
|
+
this.textarea.style.textAlignLast = 'left';
|
|
22523
|
+
}
|
|
22524
|
+
} else if (textAlign === 'justify-right') {
|
|
22525
|
+
// If text is LTR but fabric says justify-right, override to justify-left for better UX
|
|
22526
|
+
if (autoDetectedDirection === 'ltr') {
|
|
22527
|
+
this.textarea.style.textAlignLast = 'left';
|
|
22528
|
+
console.log(' → Overrode justify-right to justify-left for LTR text');
|
|
22529
|
+
} else {
|
|
22530
|
+
this.textarea.style.textAlignLast = 'right';
|
|
22531
|
+
}
|
|
22532
|
+
} else if (textAlign === 'justify-center') {
|
|
22533
|
+
this.textarea.style.textAlignLast = 'center';
|
|
22534
|
+
}
|
|
22535
|
+
|
|
22536
|
+
// Enhanced justify settings for better fabric.js matching
|
|
22537
|
+
this.textarea.style.textJustify = 'inter-word';
|
|
22538
|
+
this.textarea.style.wordSpacing = 'normal';
|
|
22539
|
+
|
|
22540
|
+
// Additional CSS properties for better justify matching
|
|
22541
|
+
this.textarea.style.textAlign = 'justify';
|
|
22542
|
+
this.textarea.style.textAlignLast = this.textarea.style.textAlignLast;
|
|
22543
|
+
|
|
22544
|
+
// Try to force better justify behavior
|
|
22545
|
+
this.textarea.style.textJustifyTrim = 'none';
|
|
22546
|
+
this.textarea.style.textAutospace = 'none';
|
|
22547
|
+
console.log(' → Applied justify alignment:', textAlign, 'with last-line:', this.textarea.style.textAlignLast);
|
|
22548
|
+
} catch (error) {
|
|
22549
|
+
console.warn(' → Justify setup failed, falling back to standard alignment:', error);
|
|
22550
|
+
cssTextAlign = textAlign.replace('justify-', '').replace('justify', 'left');
|
|
22551
|
+
}
|
|
22552
|
+
} else {
|
|
22553
|
+
this.textarea.style.textAlignLast = 'auto';
|
|
22554
|
+
this.textarea.style.textJustify = 'auto';
|
|
22555
|
+
this.textarea.style.wordSpacing = 'normal';
|
|
22556
|
+
console.log(' → Applied standard alignment:', cssTextAlign);
|
|
22557
|
+
}
|
|
22558
|
+
this.textarea.style.textAlign = cssTextAlign;
|
|
22173
22559
|
this.textarea.style.color = ((_target$fill = target.fill) === null || _target$fill === void 0 ? void 0 : _target$fill.toString()) || '#000';
|
|
22174
22560
|
this.textarea.style.letterSpacing = `${letterSpacingPx}px`;
|
|
22175
|
-
|
|
22561
|
+
|
|
22562
|
+
// Use the already detected direction from above
|
|
22563
|
+
const fabricDirection = target.direction;
|
|
22564
|
+
|
|
22565
|
+
// Use auto-detected direction for better BiDi support, but respect fabric direction if it makes sense
|
|
22566
|
+
this.textarea.style.direction = autoDetectedDirection || fabricDirection || 'ltr';
|
|
22176
22567
|
this.textarea.style.fontVariant = 'normal';
|
|
22177
22568
|
this.textarea.style.fontStretch = 'normal';
|
|
22178
|
-
this.textarea.style.textRendering = 'optimizeLegibility'
|
|
22569
|
+
this.textarea.style.textRendering = 'auto'; // Changed from 'optimizeLegibility' to match canvas
|
|
22179
22570
|
this.textarea.style.fontKerning = 'normal';
|
|
22180
22571
|
this.textarea.style.fontFeatureSettings = 'normal';
|
|
22181
22572
|
this.textarea.style.fontVariationSettings = 'normal';
|
|
@@ -22186,14 +22577,58 @@ class OverlayEditor {
|
|
|
22186
22577
|
this.textarea.style.overflowWrap = 'break-word';
|
|
22187
22578
|
this.textarea.style.whiteSpace = 'pre-wrap';
|
|
22188
22579
|
this.textarea.style.hyphens = 'none';
|
|
22189
|
-
this.textarea.style.webkitFontSmoothing = 'antialiased';
|
|
22190
|
-
this.textarea.style.mozOsxFontSmoothing = 'grayscale';
|
|
22191
22580
|
|
|
22192
|
-
//
|
|
22193
|
-
|
|
22581
|
+
// DEBUG: Log final CSS properties
|
|
22582
|
+
console.log('🎨 FINAL TEXTAREA CSS:');
|
|
22583
|
+
console.log(' textAlign:', this.textarea.style.textAlign);
|
|
22584
|
+
console.log(' textAlignLast:', this.textarea.style.textAlignLast);
|
|
22585
|
+
console.log(' direction:', this.textarea.style.direction);
|
|
22586
|
+
console.log(' unicodeBidi:', this.textarea.style.unicodeBidi);
|
|
22587
|
+
console.log(' width:', this.textarea.style.width);
|
|
22588
|
+
console.log(' textJustify:', this.textarea.style.textJustify);
|
|
22589
|
+
console.log(' wordSpacing:', this.textarea.style.wordSpacing);
|
|
22590
|
+
console.log(' whiteSpace:', this.textarea.style.whiteSpace);
|
|
22591
|
+
|
|
22592
|
+
// If justify, log Fabric object dimensions for comparison
|
|
22593
|
+
if (textAlign.includes('justify')) {
|
|
22594
|
+
var _calcTextWidth, _ref;
|
|
22595
|
+
console.log('🔧 FABRIC OBJECT JUSTIFY INFO:');
|
|
22596
|
+
console.log(' Fabric width:', target.width);
|
|
22597
|
+
console.log(' Fabric calcTextWidth:', (_calcTextWidth = (_ref = target).calcTextWidth) === null || _calcTextWidth === void 0 ? void 0 : _calcTextWidth.call(_ref));
|
|
22598
|
+
console.log(' Fabric textAlign:', target.textAlign);
|
|
22599
|
+
console.log(' Text lines:', target.textLines);
|
|
22600
|
+
}
|
|
22601
|
+
|
|
22602
|
+
// Debug font properties matching
|
|
22603
|
+
console.log('🔤 FONT PROPERTIES COMPARISON:');
|
|
22604
|
+
console.log(' Fabric fontFamily:', target.fontFamily);
|
|
22605
|
+
console.log(' Fabric fontWeight:', target.fontWeight);
|
|
22606
|
+
console.log(' Fabric fontStyle:', target.fontStyle);
|
|
22607
|
+
console.log(' Fabric fontSize:', target.fontSize);
|
|
22608
|
+
console.log(' → Textarea fontFamily:', this.textarea.style.fontFamily);
|
|
22609
|
+
console.log(' → Textarea fontWeight:', this.textarea.style.fontWeight);
|
|
22610
|
+
console.log(' → Textarea fontStyle:', this.textarea.style.fontStyle);
|
|
22611
|
+
console.log(' → Textarea fontSize:', this.textarea.style.fontSize);
|
|
22612
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
22194
22613
|
|
|
22195
|
-
//
|
|
22196
|
-
|
|
22614
|
+
// Enhanced font rendering to better match fabric.js canvas rendering
|
|
22615
|
+
// Default to auto for more natural rendering
|
|
22616
|
+
this.textarea.style.webkitFontSmoothing = 'auto';
|
|
22617
|
+
this.textarea.style.mozOsxFontSmoothing = 'auto';
|
|
22618
|
+
this.textarea.style.fontSmooth = 'auto';
|
|
22619
|
+
this.textarea.style.textSizeAdjust = 'none';
|
|
22620
|
+
|
|
22621
|
+
// For bold fonts, use subpixel rendering to match canvas thickness better
|
|
22622
|
+
const fontWeight = String(target.fontWeight || 'normal');
|
|
22623
|
+
const isBold = fontWeight === 'bold' || fontWeight === '700' || parseInt(fontWeight) >= 600;
|
|
22624
|
+
if (isBold) {
|
|
22625
|
+
this.textarea.style.webkitFontSmoothing = 'subpixel-antialiased';
|
|
22626
|
+
this.textarea.style.mozOsxFontSmoothing = 'unset';
|
|
22627
|
+
console.log('🔤 Applied enhanced bold rendering for better thickness matching');
|
|
22628
|
+
}
|
|
22629
|
+
console.log('🎨 FONT SMOOTHING APPLIED:');
|
|
22630
|
+
console.log(' webkitFontSmoothing:', this.textarea.style.webkitFontSmoothing);
|
|
22631
|
+
console.log(' mozOsxFontSmoothing:', this.textarea.style.mozOsxFontSmoothing);
|
|
22197
22632
|
|
|
22198
22633
|
// Initial bounds are set correctly by Fabric.js - don't force update here
|
|
22199
22634
|
}
|
|
@@ -22428,6 +22863,23 @@ class OverlayEditor {
|
|
|
22428
22863
|
// Handle commit/cancel after restoring visibility
|
|
22429
22864
|
if (commit && !this.isComposing) {
|
|
22430
22865
|
const finalText = this.textarea.value;
|
|
22866
|
+
|
|
22867
|
+
// Auto-detect text direction and update fabric object if needed
|
|
22868
|
+
const detectedDirection = this.firstStrongDir(finalText);
|
|
22869
|
+
const currentDirection = this.target.direction || 'ltr';
|
|
22870
|
+
if (detectedDirection && detectedDirection !== currentDirection) {
|
|
22871
|
+
console.log(`🔄 Overlay Exit: Auto-detected direction change from "${currentDirection}" to "${detectedDirection}"`);
|
|
22872
|
+
console.log(` Text content: "${finalText.substring(0, 50)}..."`);
|
|
22873
|
+
|
|
22874
|
+
// Update the fabric object's direction
|
|
22875
|
+
this.target.set('direction', detectedDirection);
|
|
22876
|
+
|
|
22877
|
+
// Force a re-render to apply the direction change
|
|
22878
|
+
this.canvas.requestRenderAll();
|
|
22879
|
+
console.log(`✅ Fabric object direction updated to: ${detectedDirection}`);
|
|
22880
|
+
} else {
|
|
22881
|
+
console.log(`📝 Overlay Exit: Direction unchanged (${currentDirection}), text: "${finalText.substring(0, 30)}..."`);
|
|
22882
|
+
}
|
|
22431
22883
|
if (this.onCommit) {
|
|
22432
22884
|
this.onCommit(finalText);
|
|
22433
22885
|
}
|
|
@@ -26018,30 +26470,17 @@ class Textbox extends IText {
|
|
|
26018
26470
|
// Detect resize origin during resizing
|
|
26019
26471
|
this.on('resizing', e => {
|
|
26020
26472
|
// Check transform origin to determine which side is being resized
|
|
26021
|
-
console.log('🔍 Resize event data:', e);
|
|
26022
26473
|
if (e.transform) {
|
|
26023
26474
|
const {
|
|
26024
|
-
originX
|
|
26025
|
-
originY
|
|
26475
|
+
originX
|
|
26026
26476
|
} = e.transform;
|
|
26027
|
-
console.log('🔍 Transform origins:', {
|
|
26028
|
-
originX,
|
|
26029
|
-
originY
|
|
26030
|
-
});
|
|
26031
26477
|
// originX tells us which side is the anchor - opposite side is being dragged
|
|
26032
26478
|
resizeOrigin = originX === 'right' ? 'left' : originX === 'left' ? 'right' : null;
|
|
26033
|
-
console.log('🎯 Setting resizeOrigin to:', resizeOrigin);
|
|
26034
26479
|
} else if (e.originX) {
|
|
26035
26480
|
const {
|
|
26036
|
-
originX
|
|
26037
|
-
originY
|
|
26481
|
+
originX
|
|
26038
26482
|
} = e;
|
|
26039
|
-
console.log('🔍 Event origins:', {
|
|
26040
|
-
originX,
|
|
26041
|
-
originY
|
|
26042
|
-
});
|
|
26043
26483
|
resizeOrigin = originX === 'right' ? 'left' : originX === 'left' ? 'right' : null;
|
|
26044
|
-
console.log('🎯 Setting resizeOrigin to:', resizeOrigin);
|
|
26045
26484
|
}
|
|
26046
26485
|
});
|
|
26047
26486
|
|
|
@@ -26049,9 +26488,6 @@ class Textbox extends IText {
|
|
|
26049
26488
|
// Use 'modified' event which fires after user releases the mouse
|
|
26050
26489
|
this.on('modified', () => {
|
|
26051
26490
|
const currentResizeOrigin = resizeOrigin; // Capture the value before reset
|
|
26052
|
-
console.log('✅ Modified event fired - resize complete, triggering safety snap', {
|
|
26053
|
-
resizeOrigin: currentResizeOrigin
|
|
26054
|
-
});
|
|
26055
26491
|
// Small delay to ensure text layout is updated
|
|
26056
26492
|
setTimeout(() => this.safetySnapWidth(currentResizeOrigin), 10);
|
|
26057
26493
|
resizeOrigin = null; // Reset after capturing
|
|
@@ -26061,7 +26497,6 @@ class Textbox extends IText {
|
|
|
26061
26497
|
(_this$canvas = this.canvas) === null || _this$canvas === void 0 || _this$canvas.on('object:modified', e => {
|
|
26062
26498
|
if (e.target === this) {
|
|
26063
26499
|
const currentResizeOrigin = resizeOrigin; // Capture the value before reset
|
|
26064
|
-
console.log('✅ Canvas object:modified fired for this textbox');
|
|
26065
26500
|
setTimeout(() => this.safetySnapWidth(currentResizeOrigin), 10);
|
|
26066
26501
|
resizeOrigin = null; // Reset after capturing
|
|
26067
26502
|
}
|
|
@@ -26076,38 +26511,17 @@ class Textbox extends IText {
|
|
|
26076
26511
|
* @param resizeOrigin - Which side was used for resizing ('left' or 'right')
|
|
26077
26512
|
*/
|
|
26078
26513
|
safetySnapWidth(resizeOrigin) {
|
|
26079
|
-
var _this$_textLines;
|
|
26080
|
-
console.log('🔍 safetySnapWidth called', {
|
|
26081
|
-
isWrapping: this.isWrapping,
|
|
26082
|
-
hasTextLines: !!this._textLines,
|
|
26083
|
-
lineCount: ((_this$_textLines = this._textLines) === null || _this$_textLines === void 0 ? void 0 : _this$_textLines.length) || 0,
|
|
26084
|
-
currentWidth: this.width,
|
|
26085
|
-
type: this.type,
|
|
26086
|
-
text: this.text
|
|
26087
|
-
});
|
|
26088
|
-
|
|
26089
26514
|
// For Textbox objects, we always want to check for clipping regardless of isWrapping flag
|
|
26090
26515
|
if (!this._textLines || this.type.toLowerCase() !== 'textbox' || this._textLines.length === 0) {
|
|
26091
|
-
var _this$_textLines2;
|
|
26092
|
-
console.log('❌ Early return - missing requirements', {
|
|
26093
|
-
hasTextLines: !!this._textLines,
|
|
26094
|
-
typeMatch: this.type.toLowerCase() === 'textbox',
|
|
26095
|
-
actualType: this.type,
|
|
26096
|
-
hasLines: ((_this$_textLines2 = this._textLines) === null || _this$_textLines2 === void 0 ? void 0 : _this$_textLines2.length) > 0
|
|
26097
|
-
});
|
|
26098
26516
|
return;
|
|
26099
26517
|
}
|
|
26100
26518
|
const lineCount = this._textLines.length;
|
|
26101
26519
|
if (lineCount === 0) return;
|
|
26102
|
-
|
|
26103
|
-
// Check all lines, not just the last one
|
|
26104
|
-
let maxActualLineWidth = 0; // Actual measured width without buffers
|
|
26105
26520
|
let maxRequiredWidth = 0; // Width including RTL buffer
|
|
26106
26521
|
|
|
26107
26522
|
for (let i = 0; i < lineCount; i++) {
|
|
26108
26523
|
const lineText = this._textLines[i].join(''); // Convert grapheme array to string
|
|
26109
26524
|
const lineWidth = this.getLineWidth(i);
|
|
26110
|
-
maxActualLineWidth = Math.max(maxActualLineWidth, lineWidth);
|
|
26111
26525
|
|
|
26112
26526
|
// RTL detection - regex for Arabic, Hebrew, and other RTL characters
|
|
26113
26527
|
const rtlRegex = /[\u0590-\u05FF\u0600-\u06FF\u0750-\u077F\uFB50-\uFDFF\uFE70-\uFEFF]/;
|
|
@@ -26127,11 +26541,6 @@ class Textbox extends IText {
|
|
|
26127
26541
|
var _this$canvas2;
|
|
26128
26542
|
// Set width to exactly what's needed + minimal safety margin
|
|
26129
26543
|
const newWidth = maxRequiredWidth + 1; // Add just 1px safety margin
|
|
26130
|
-
console.log(`Safety snap: ${this.width.toFixed(0)}px -> ${newWidth.toFixed(0)}px`, {
|
|
26131
|
-
maxActualLineWidth: maxActualLineWidth.toFixed(1),
|
|
26132
|
-
maxRequiredWidth: maxRequiredWidth.toFixed(1),
|
|
26133
|
-
difference: (newWidth - this.width).toFixed(1)
|
|
26134
|
-
});
|
|
26135
26544
|
|
|
26136
26545
|
// Store original position before width change
|
|
26137
26546
|
const originalLeft = this.left;
|
|
@@ -26147,19 +26556,12 @@ class Textbox extends IText {
|
|
|
26147
26556
|
// Only compensate position when resizing from left handle
|
|
26148
26557
|
// Right handle resize doesn't shift the text position
|
|
26149
26558
|
if (resizeOrigin === 'left') {
|
|
26150
|
-
console.log('🔧 Compensating for left-side resize', {
|
|
26151
|
-
originalLeft,
|
|
26152
|
-
widthIncrease,
|
|
26153
|
-
newLeft: originalLeft - widthIncrease
|
|
26154
|
-
});
|
|
26155
26559
|
// When resizing from left, the expansion pushes text right
|
|
26156
26560
|
// Compensate by moving the textbox left by the width increase
|
|
26157
26561
|
this.set({
|
|
26158
26562
|
'left': originalLeft - widthIncrease,
|
|
26159
26563
|
'top': originalTop
|
|
26160
26564
|
});
|
|
26161
|
-
} else {
|
|
26162
|
-
console.log('✅ Right-side resize, no compensation needed');
|
|
26163
26565
|
}
|
|
26164
26566
|
this.setCoords();
|
|
26165
26567
|
|