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