@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.js
CHANGED
|
@@ -360,7 +360,7 @@
|
|
|
360
360
|
}
|
|
361
361
|
const cache = new Cache();
|
|
362
362
|
|
|
363
|
-
var version = "7.0.
|
|
363
|
+
var version = "7.0.1-beta2";
|
|
364
364
|
|
|
365
365
|
// use this syntax so babel plugin see this import here
|
|
366
366
|
const VERSION = version;
|
|
@@ -17840,10 +17840,189 @@
|
|
|
17840
17840
|
classRegistry.setClass(Line);
|
|
17841
17841
|
classRegistry.setSVGClass(Line);
|
|
17842
17842
|
|
|
17843
|
+
/**
|
|
17844
|
+
* Calculate the distance between two points
|
|
17845
|
+
*/
|
|
17846
|
+
function pointDistance(p1, p2) {
|
|
17847
|
+
return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
|
|
17848
|
+
}
|
|
17849
|
+
|
|
17850
|
+
/**
|
|
17851
|
+
* Normalize a vector
|
|
17852
|
+
*/
|
|
17853
|
+
function normalizeVector(vector) {
|
|
17854
|
+
const length = Math.sqrt(vector.x * vector.x + vector.y * vector.y);
|
|
17855
|
+
if (length === 0) return {
|
|
17856
|
+
x: 0,
|
|
17857
|
+
y: 0
|
|
17858
|
+
};
|
|
17859
|
+
return {
|
|
17860
|
+
x: vector.x / length,
|
|
17861
|
+
y: vector.y / length
|
|
17862
|
+
};
|
|
17863
|
+
}
|
|
17864
|
+
|
|
17865
|
+
/**
|
|
17866
|
+
* Get the maximum allowed radius for a corner based on adjacent edge lengths
|
|
17867
|
+
*/
|
|
17868
|
+
function getMaxRadius(prevPoint, currentPoint, nextPoint) {
|
|
17869
|
+
const dist1 = pointDistance(prevPoint, currentPoint);
|
|
17870
|
+
const dist2 = pointDistance(currentPoint, nextPoint);
|
|
17871
|
+
return Math.min(dist1, dist2) / 2;
|
|
17872
|
+
}
|
|
17873
|
+
|
|
17874
|
+
/**
|
|
17875
|
+
* Calculate rounded corner data for a single corner
|
|
17876
|
+
*/
|
|
17877
|
+
function calculateRoundedCorner(prevPoint, currentPoint, nextPoint, radius) {
|
|
17878
|
+
// Calculate edge vectors
|
|
17879
|
+
const edge1 = {
|
|
17880
|
+
x: currentPoint.x - prevPoint.x,
|
|
17881
|
+
y: currentPoint.y - prevPoint.y
|
|
17882
|
+
};
|
|
17883
|
+
const edge2 = {
|
|
17884
|
+
x: nextPoint.x - currentPoint.x,
|
|
17885
|
+
y: nextPoint.y - currentPoint.y
|
|
17886
|
+
};
|
|
17887
|
+
|
|
17888
|
+
// Normalize edge vectors
|
|
17889
|
+
const norm1 = normalizeVector(edge1);
|
|
17890
|
+
const norm2 = normalizeVector(edge2);
|
|
17891
|
+
|
|
17892
|
+
// Calculate the maximum allowed radius
|
|
17893
|
+
const maxRadius = getMaxRadius(prevPoint, currentPoint, nextPoint);
|
|
17894
|
+
const actualRadius = Math.min(radius, maxRadius);
|
|
17895
|
+
|
|
17896
|
+
// Calculate start and end points of the rounded corner
|
|
17897
|
+
const startPoint = {
|
|
17898
|
+
x: currentPoint.x - norm1.x * actualRadius,
|
|
17899
|
+
y: currentPoint.y - norm1.y * actualRadius
|
|
17900
|
+
};
|
|
17901
|
+
const endPoint = {
|
|
17902
|
+
x: currentPoint.x + norm2.x * actualRadius,
|
|
17903
|
+
y: currentPoint.y + norm2.y * actualRadius
|
|
17904
|
+
};
|
|
17905
|
+
|
|
17906
|
+
// Calculate control points for bezier curve
|
|
17907
|
+
// Using the magic number kRect for optimal circular approximation
|
|
17908
|
+
const controlOffset = actualRadius * kRect;
|
|
17909
|
+
const cp1 = {
|
|
17910
|
+
x: startPoint.x + norm1.x * controlOffset,
|
|
17911
|
+
y: startPoint.y + norm1.y * controlOffset
|
|
17912
|
+
};
|
|
17913
|
+
const cp2 = {
|
|
17914
|
+
x: endPoint.x - norm2.x * controlOffset,
|
|
17915
|
+
y: endPoint.y - norm2.y * controlOffset
|
|
17916
|
+
};
|
|
17917
|
+
return {
|
|
17918
|
+
corner: currentPoint,
|
|
17919
|
+
start: startPoint,
|
|
17920
|
+
end: endPoint,
|
|
17921
|
+
cp1,
|
|
17922
|
+
cp2,
|
|
17923
|
+
actualRadius
|
|
17924
|
+
};
|
|
17925
|
+
}
|
|
17926
|
+
|
|
17927
|
+
/**
|
|
17928
|
+
* Apply corner radius to a polygon defined by points
|
|
17929
|
+
*/
|
|
17930
|
+
function applyCornerRadiusToPolygon(points, radius) {
|
|
17931
|
+
let radiusAsPercentage = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
17932
|
+
if (points.length < 3) {
|
|
17933
|
+
throw new Error('Polygon must have at least 3 points');
|
|
17934
|
+
}
|
|
17935
|
+
|
|
17936
|
+
// Calculate bounding box if radius is percentage-based
|
|
17937
|
+
let actualRadius = radius;
|
|
17938
|
+
if (radiusAsPercentage) {
|
|
17939
|
+
const minX = Math.min(...points.map(p => p.x));
|
|
17940
|
+
const maxX = Math.max(...points.map(p => p.x));
|
|
17941
|
+
const minY = Math.min(...points.map(p => p.y));
|
|
17942
|
+
const maxY = Math.max(...points.map(p => p.y));
|
|
17943
|
+
const width = maxX - minX;
|
|
17944
|
+
const height = maxY - minY;
|
|
17945
|
+
const minDimension = Math.min(width, height);
|
|
17946
|
+
actualRadius = radius / 100 * minDimension;
|
|
17947
|
+
}
|
|
17948
|
+
const roundedCorners = [];
|
|
17949
|
+
for (let i = 0; i < points.length; i++) {
|
|
17950
|
+
const prevIndex = (i - 1 + points.length) % points.length;
|
|
17951
|
+
const nextIndex = (i + 1) % points.length;
|
|
17952
|
+
const prevPoint = points[prevIndex];
|
|
17953
|
+
const currentPoint = points[i];
|
|
17954
|
+
const nextPoint = points[nextIndex];
|
|
17955
|
+
const roundedCorner = calculateRoundedCorner(prevPoint, currentPoint, nextPoint, actualRadius);
|
|
17956
|
+
roundedCorners.push(roundedCorner);
|
|
17957
|
+
}
|
|
17958
|
+
return roundedCorners;
|
|
17959
|
+
}
|
|
17960
|
+
|
|
17961
|
+
/**
|
|
17962
|
+
* Render a rounded polygon to a canvas context
|
|
17963
|
+
*/
|
|
17964
|
+
function renderRoundedPolygon(ctx, roundedCorners) {
|
|
17965
|
+
let closed = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
|
|
17966
|
+
if (roundedCorners.length === 0) return;
|
|
17967
|
+
ctx.beginPath();
|
|
17968
|
+
|
|
17969
|
+
// Start at the first corner's start point
|
|
17970
|
+
const firstCorner = roundedCorners[0];
|
|
17971
|
+
ctx.moveTo(firstCorner.start.x, firstCorner.start.y);
|
|
17972
|
+
for (let i = 0; i < roundedCorners.length; i++) {
|
|
17973
|
+
const corner = roundedCorners[i];
|
|
17974
|
+
const nextIndex = (i + 1) % roundedCorners.length;
|
|
17975
|
+
const nextCorner = roundedCorners[nextIndex];
|
|
17976
|
+
|
|
17977
|
+
// Draw the rounded corner using bezier curve
|
|
17978
|
+
ctx.bezierCurveTo(corner.cp1.x, corner.cp1.y, corner.cp2.x, corner.cp2.y, corner.end.x, corner.end.y);
|
|
17979
|
+
|
|
17980
|
+
// Draw line to next corner's start point (if not the last segment in open path)
|
|
17981
|
+
if (i < roundedCorners.length - 1 || closed) {
|
|
17982
|
+
ctx.lineTo(nextCorner.start.x, nextCorner.start.y);
|
|
17983
|
+
}
|
|
17984
|
+
}
|
|
17985
|
+
if (closed) {
|
|
17986
|
+
ctx.closePath();
|
|
17987
|
+
}
|
|
17988
|
+
}
|
|
17989
|
+
|
|
17990
|
+
/**
|
|
17991
|
+
* Generate SVG path data for a rounded polygon
|
|
17992
|
+
*/
|
|
17993
|
+
function generateRoundedPolygonPath(roundedCorners) {
|
|
17994
|
+
let closed = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
|
|
17995
|
+
if (roundedCorners.length === 0) return '';
|
|
17996
|
+
const pathData = [];
|
|
17997
|
+
const firstCorner = roundedCorners[0];
|
|
17998
|
+
|
|
17999
|
+
// Move to first corner's start point
|
|
18000
|
+
pathData.push(`M ${firstCorner.start.x} ${firstCorner.start.y}`);
|
|
18001
|
+
for (let i = 0; i < roundedCorners.length; i++) {
|
|
18002
|
+
const corner = roundedCorners[i];
|
|
18003
|
+
const nextIndex = (i + 1) % roundedCorners.length;
|
|
18004
|
+
const nextCorner = roundedCorners[nextIndex];
|
|
18005
|
+
|
|
18006
|
+
// Add bezier curve for the rounded corner
|
|
18007
|
+
pathData.push(`C ${corner.cp1.x} ${corner.cp1.y} ${corner.cp2.x} ${corner.cp2.y} ${corner.end.x} ${corner.end.y}`);
|
|
18008
|
+
|
|
18009
|
+
// Add line to next corner's start point (if not the last segment in open path)
|
|
18010
|
+
if (i < roundedCorners.length - 1 || closed) {
|
|
18011
|
+
pathData.push(`L ${nextCorner.start.x} ${nextCorner.start.y}`);
|
|
18012
|
+
}
|
|
18013
|
+
}
|
|
18014
|
+
if (closed) {
|
|
18015
|
+
pathData.push('Z');
|
|
18016
|
+
}
|
|
18017
|
+
return pathData.join(' ');
|
|
18018
|
+
}
|
|
18019
|
+
|
|
17843
18020
|
const triangleDefaultValues = {
|
|
17844
18021
|
width: 100,
|
|
17845
|
-
height: 100
|
|
18022
|
+
height: 100,
|
|
18023
|
+
cornerRadius: 0
|
|
17846
18024
|
};
|
|
18025
|
+
const TRIANGLE_PROPS = ['cornerRadius'];
|
|
17847
18026
|
class Triangle extends FabricObject {
|
|
17848
18027
|
static getDefaults() {
|
|
17849
18028
|
return {
|
|
@@ -17862,34 +18041,90 @@
|
|
|
17862
18041
|
this.setOptions(options);
|
|
17863
18042
|
}
|
|
17864
18043
|
|
|
18044
|
+
/**
|
|
18045
|
+
* Get triangle points as an array of XY coordinates
|
|
18046
|
+
* @private
|
|
18047
|
+
*/
|
|
18048
|
+
_getTrianglePoints() {
|
|
18049
|
+
const widthBy2 = this.width / 2;
|
|
18050
|
+
const heightBy2 = this.height / 2;
|
|
18051
|
+
return [{
|
|
18052
|
+
x: -widthBy2,
|
|
18053
|
+
y: heightBy2
|
|
18054
|
+
},
|
|
18055
|
+
// bottom left
|
|
18056
|
+
{
|
|
18057
|
+
x: 0,
|
|
18058
|
+
y: -heightBy2
|
|
18059
|
+
},
|
|
18060
|
+
// top center
|
|
18061
|
+
{
|
|
18062
|
+
x: widthBy2,
|
|
18063
|
+
y: heightBy2
|
|
18064
|
+
} // bottom right
|
|
18065
|
+
];
|
|
18066
|
+
}
|
|
18067
|
+
|
|
17865
18068
|
/**
|
|
17866
18069
|
* @private
|
|
17867
18070
|
* @param {CanvasRenderingContext2D} ctx Context to render on
|
|
17868
18071
|
*/
|
|
17869
18072
|
_render(ctx) {
|
|
17870
|
-
|
|
17871
|
-
|
|
17872
|
-
|
|
17873
|
-
|
|
17874
|
-
|
|
17875
|
-
|
|
17876
|
-
|
|
18073
|
+
if (this.cornerRadius > 0) {
|
|
18074
|
+
// Render rounded triangle
|
|
18075
|
+
const points = this._getTrianglePoints();
|
|
18076
|
+
const roundedCorners = applyCornerRadiusToPolygon(points, this.cornerRadius);
|
|
18077
|
+
renderRoundedPolygon(ctx, roundedCorners, true);
|
|
18078
|
+
} else {
|
|
18079
|
+
// Render sharp triangle (original implementation)
|
|
18080
|
+
const widthBy2 = this.width / 2;
|
|
18081
|
+
const heightBy2 = this.height / 2;
|
|
18082
|
+
ctx.beginPath();
|
|
18083
|
+
ctx.moveTo(-widthBy2, heightBy2);
|
|
18084
|
+
ctx.lineTo(0, -heightBy2);
|
|
18085
|
+
ctx.lineTo(widthBy2, heightBy2);
|
|
18086
|
+
ctx.closePath();
|
|
18087
|
+
}
|
|
17877
18088
|
this._renderPaintInOrder(ctx);
|
|
17878
18089
|
}
|
|
17879
18090
|
|
|
18091
|
+
/**
|
|
18092
|
+
* Returns object representation of an instance
|
|
18093
|
+
* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
|
|
18094
|
+
* @return {Object} object representation of an instance
|
|
18095
|
+
*/
|
|
18096
|
+
toObject() {
|
|
18097
|
+
let propertiesToInclude = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
|
|
18098
|
+
return super.toObject([...TRIANGLE_PROPS, ...propertiesToInclude]);
|
|
18099
|
+
}
|
|
18100
|
+
|
|
17880
18101
|
/**
|
|
17881
18102
|
* Returns svg representation of an instance
|
|
17882
18103
|
* @return {Array} an array of strings with the specific svg representation
|
|
17883
18104
|
* of the instance
|
|
17884
18105
|
*/
|
|
17885
18106
|
_toSVG() {
|
|
17886
|
-
|
|
17887
|
-
|
|
17888
|
-
points =
|
|
17889
|
-
|
|
18107
|
+
if (this.cornerRadius > 0) {
|
|
18108
|
+
// Generate rounded triangle as path
|
|
18109
|
+
const points = this._getTrianglePoints();
|
|
18110
|
+
const roundedCorners = applyCornerRadiusToPolygon(points, this.cornerRadius);
|
|
18111
|
+
const pathData = generateRoundedPolygonPath(roundedCorners, true);
|
|
18112
|
+
return ['<path ', 'COMMON_PARTS', `d="${pathData}" />`];
|
|
18113
|
+
} else {
|
|
18114
|
+
// Original sharp triangle implementation
|
|
18115
|
+
const widthBy2 = this.width / 2;
|
|
18116
|
+
const heightBy2 = this.height / 2;
|
|
18117
|
+
const points = `${-widthBy2} ${heightBy2},0 ${-heightBy2},${widthBy2} ${heightBy2}`;
|
|
18118
|
+
return ['<polygon ', 'COMMON_PARTS', 'points="', points, '" />'];
|
|
18119
|
+
}
|
|
17890
18120
|
}
|
|
17891
18121
|
}
|
|
18122
|
+
/**
|
|
18123
|
+
* Corner radius for rounded triangle corners
|
|
18124
|
+
* @type Number
|
|
18125
|
+
*/
|
|
17892
18126
|
_defineProperty(Triangle, "type", 'Triangle');
|
|
18127
|
+
_defineProperty(Triangle, "cacheProperties", [...cacheProperties, ...TRIANGLE_PROPS]);
|
|
17893
18128
|
_defineProperty(Triangle, "ownDefaults", triangleDefaultValues);
|
|
17894
18129
|
classRegistry.setClass(Triangle);
|
|
17895
18130
|
classRegistry.setSVGClass(Triangle);
|
|
@@ -18054,7 +18289,8 @@
|
|
|
18054
18289
|
/**
|
|
18055
18290
|
* @deprecated transient option soon to be removed in favor of a different design
|
|
18056
18291
|
*/
|
|
18057
|
-
exactBoundingBox: false
|
|
18292
|
+
exactBoundingBox: false,
|
|
18293
|
+
cornerRadius: 0
|
|
18058
18294
|
};
|
|
18059
18295
|
class Polyline extends FabricObject {
|
|
18060
18296
|
static getDefaults() {
|
|
@@ -18268,7 +18504,7 @@
|
|
|
18268
18504
|
toObject() {
|
|
18269
18505
|
let propertiesToInclude = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
|
|
18270
18506
|
return {
|
|
18271
|
-
...super.toObject(propertiesToInclude),
|
|
18507
|
+
...super.toObject(['cornerRadius', ...propertiesToInclude]),
|
|
18272
18508
|
points: this.points.map(_ref => {
|
|
18273
18509
|
let {
|
|
18274
18510
|
x,
|
|
@@ -18288,14 +18524,28 @@
|
|
|
18288
18524
|
* of the instance
|
|
18289
18525
|
*/
|
|
18290
18526
|
_toSVG() {
|
|
18291
|
-
|
|
18292
|
-
|
|
18293
|
-
|
|
18294
|
-
|
|
18295
|
-
|
|
18296
|
-
|
|
18527
|
+
if (this.cornerRadius > 0 && this.points.length >= 3) {
|
|
18528
|
+
// Generate rounded polygon/polyline as path
|
|
18529
|
+
const diffX = this.pathOffset.x;
|
|
18530
|
+
const diffY = this.pathOffset.y;
|
|
18531
|
+
const adjustedPoints = this.points.map(point => ({
|
|
18532
|
+
x: point.x - diffX,
|
|
18533
|
+
y: point.y - diffY
|
|
18534
|
+
}));
|
|
18535
|
+
const roundedCorners = applyCornerRadiusToPolygon(adjustedPoints, this.cornerRadius);
|
|
18536
|
+
const pathData = generateRoundedPolygonPath(roundedCorners, !this.isOpen());
|
|
18537
|
+
return ['<path ', 'COMMON_PARTS', `d="${pathData}" />\n`];
|
|
18538
|
+
} else {
|
|
18539
|
+
// Original sharp corners implementation
|
|
18540
|
+
const points = [];
|
|
18541
|
+
const diffX = this.pathOffset.x;
|
|
18542
|
+
const diffY = this.pathOffset.y;
|
|
18543
|
+
const NUM_FRACTION_DIGITS = config.NUM_FRACTION_DIGITS;
|
|
18544
|
+
for (let i = 0, len = this.points.length; i < len; i++) {
|
|
18545
|
+
points.push(toFixed(this.points[i].x - diffX, NUM_FRACTION_DIGITS), ',', toFixed(this.points[i].y - diffY, NUM_FRACTION_DIGITS), ' ');
|
|
18546
|
+
}
|
|
18547
|
+
return [`<${this.constructor.type.toLowerCase()} `, 'COMMON_PARTS', `points="${points.join('')}" />\n`];
|
|
18297
18548
|
}
|
|
18298
|
-
return [`<${this.constructor.type.toLowerCase()} `, 'COMMON_PARTS', `points="${points.join('')}" />\n`];
|
|
18299
18549
|
}
|
|
18300
18550
|
|
|
18301
18551
|
/**
|
|
@@ -18311,13 +18561,24 @@
|
|
|
18311
18561
|
// NaN comes from parseFloat of a empty string in parser
|
|
18312
18562
|
return;
|
|
18313
18563
|
}
|
|
18314
|
-
|
|
18315
|
-
|
|
18316
|
-
|
|
18317
|
-
|
|
18318
|
-
|
|
18564
|
+
if (this.cornerRadius > 0 && len >= 3) {
|
|
18565
|
+
// Render with rounded corners
|
|
18566
|
+
const adjustedPoints = this.points.map(point => ({
|
|
18567
|
+
x: point.x - x,
|
|
18568
|
+
y: point.y - y
|
|
18569
|
+
}));
|
|
18570
|
+
const roundedCorners = applyCornerRadiusToPolygon(adjustedPoints, this.cornerRadius);
|
|
18571
|
+
renderRoundedPolygon(ctx, roundedCorners, !this.isOpen());
|
|
18572
|
+
} else {
|
|
18573
|
+
// Original sharp corners implementation
|
|
18574
|
+
ctx.beginPath();
|
|
18575
|
+
ctx.moveTo(this.points[0].x - x, this.points[0].y - y);
|
|
18576
|
+
for (let i = 0; i < len; i++) {
|
|
18577
|
+
const point = this.points[i];
|
|
18578
|
+
ctx.lineTo(point.x - x, point.y - y);
|
|
18579
|
+
}
|
|
18580
|
+
!this.isOpen() && ctx.closePath();
|
|
18319
18581
|
}
|
|
18320
|
-
!this.isOpen() && ctx.closePath();
|
|
18321
18582
|
this._renderPaintInOrder(ctx);
|
|
18322
18583
|
}
|
|
18323
18584
|
|
|
@@ -18382,10 +18643,15 @@
|
|
|
18382
18643
|
* @type Boolean
|
|
18383
18644
|
* @default false
|
|
18384
18645
|
*/
|
|
18646
|
+
/**
|
|
18647
|
+
* Corner radius for rounded corners
|
|
18648
|
+
* @type Number
|
|
18649
|
+
* @default 0
|
|
18650
|
+
*/
|
|
18385
18651
|
_defineProperty(Polyline, "ownDefaults", polylineDefaultValues);
|
|
18386
18652
|
_defineProperty(Polyline, "type", 'Polyline');
|
|
18387
18653
|
_defineProperty(Polyline, "layoutProperties", [SKEW_X, SKEW_Y, 'strokeLineCap', 'strokeLineJoin', 'strokeMiterLimit', 'strokeWidth', 'strokeUniform', 'points']);
|
|
18388
|
-
_defineProperty(Polyline, "cacheProperties", [...cacheProperties, 'points']);
|
|
18654
|
+
_defineProperty(Polyline, "cacheProperties", [...cacheProperties, 'points', 'cornerRadius']);
|
|
18389
18655
|
_defineProperty(Polyline, "ATTRIBUTE_NAMES", [...SHARED_ATTRIBUTES]);
|
|
18390
18656
|
classRegistry.setClass(Polyline);
|
|
18391
18657
|
classRegistry.setSVGClass(Polyline);
|
|
@@ -20195,6 +20461,7 @@
|
|
|
20195
20461
|
*/
|
|
20196
20462
|
enlargeSpaces() {
|
|
20197
20463
|
let diffSpace, currentLineWidth, numberOfSpaces, accumulatedSpace, line, charBound, spaces;
|
|
20464
|
+
const isRtl = this.direction === 'rtl';
|
|
20198
20465
|
for (let i = 0, len = this._textLines.length; i < len; i++) {
|
|
20199
20466
|
if (this.textAlign !== JUSTIFY && (i === len - 1 || this.isEndOfWrapping(i))) {
|
|
20200
20467
|
continue;
|
|
@@ -20205,15 +20472,44 @@
|
|
|
20205
20472
|
if (currentLineWidth < this.width && (spaces = this.textLines[i].match(this._reSpacesAndTabs))) {
|
|
20206
20473
|
numberOfSpaces = spaces.length;
|
|
20207
20474
|
diffSpace = (this.width - currentLineWidth) / numberOfSpaces;
|
|
20208
|
-
|
|
20209
|
-
|
|
20210
|
-
|
|
20211
|
-
|
|
20212
|
-
|
|
20213
|
-
|
|
20214
|
-
|
|
20215
|
-
|
|
20216
|
-
|
|
20475
|
+
if (isRtl) {
|
|
20476
|
+
for (let j = 0; j < line.length; j++) {
|
|
20477
|
+
if (this._reSpaceAndTab.test(line[j])) ;
|
|
20478
|
+
}
|
|
20479
|
+
|
|
20480
|
+
// For RTL, we need to work backwards through the visual positions
|
|
20481
|
+
// but still update logical positions correctly
|
|
20482
|
+
let spaceCount = 0;
|
|
20483
|
+
for (let j = 0; j <= line.length; j++) {
|
|
20484
|
+
charBound = this.__charBounds[i][j];
|
|
20485
|
+
if (charBound) {
|
|
20486
|
+
if (this._reSpaceAndTab.test(line[j])) {
|
|
20487
|
+
charBound.width += diffSpace;
|
|
20488
|
+
charBound.kernedWidth += diffSpace;
|
|
20489
|
+
spaceCount++;
|
|
20490
|
+
}
|
|
20491
|
+
|
|
20492
|
+
// For RTL, shift all characters to the right by the total expansion
|
|
20493
|
+
// minus the expansion that comes after this character
|
|
20494
|
+
const remainingSpaces = numberOfSpaces - spaceCount;
|
|
20495
|
+
const shiftAmount = remainingSpaces * diffSpace;
|
|
20496
|
+
charBound.left += shiftAmount;
|
|
20497
|
+
}
|
|
20498
|
+
}
|
|
20499
|
+
} else {
|
|
20500
|
+
// LTR processing (original logic)
|
|
20501
|
+
for (let j = 0; j <= line.length; j++) {
|
|
20502
|
+
charBound = this.__charBounds[i][j];
|
|
20503
|
+
if (charBound) {
|
|
20504
|
+
if (this._reSpaceAndTab.test(line[j])) {
|
|
20505
|
+
charBound.width += diffSpace;
|
|
20506
|
+
charBound.kernedWidth += diffSpace;
|
|
20507
|
+
charBound.left += accumulatedSpace;
|
|
20508
|
+
accumulatedSpace += diffSpace;
|
|
20509
|
+
} else {
|
|
20510
|
+
charBound.left += accumulatedSpace;
|
|
20511
|
+
}
|
|
20512
|
+
}
|
|
20217
20513
|
}
|
|
20218
20514
|
}
|
|
20219
20515
|
}
|
|
@@ -20871,7 +21167,15 @@
|
|
|
20871
21167
|
if (currentDirection !== this.direction) {
|
|
20872
21168
|
ctx.canvas.setAttribute('dir', isLtr ? 'ltr' : 'rtl');
|
|
20873
21169
|
ctx.direction = isLtr ? 'ltr' : 'rtl';
|
|
20874
|
-
|
|
21170
|
+
|
|
21171
|
+
// For justify alignments, we need to set the correct canvas text alignment
|
|
21172
|
+
// This is crucial for RTL text to render in the correct order
|
|
21173
|
+
if (isJustify) {
|
|
21174
|
+
// Justify uses LEFT alignment as a base, letting the character positioning handle justification
|
|
21175
|
+
ctx.textAlign = LEFT;
|
|
21176
|
+
} else {
|
|
21177
|
+
ctx.textAlign = isLtr ? LEFT : RIGHT;
|
|
21178
|
+
}
|
|
20875
21179
|
}
|
|
20876
21180
|
top -= lineHeight * this._fontSizeFraction / this.lineHeight;
|
|
20877
21181
|
if (shortCut) {
|
|
@@ -21107,9 +21411,21 @@
|
|
|
21107
21411
|
direction = this.direction,
|
|
21108
21412
|
isEndOfWrapping = this.isEndOfWrapping(lineIndex);
|
|
21109
21413
|
let leftOffset = 0;
|
|
21110
|
-
|
|
21111
|
-
|
|
21414
|
+
|
|
21415
|
+
// Handle justify alignments (excluding last lines and wrapped line ends)
|
|
21416
|
+
const isJustifyLine = textAlign === JUSTIFY || textAlign === JUSTIFY_CENTER && !isEndOfWrapping || textAlign === JUSTIFY_RIGHT && !isEndOfWrapping || textAlign === JUSTIFY_LEFT && !isEndOfWrapping;
|
|
21417
|
+
if (isJustifyLine) {
|
|
21418
|
+
// Justify lines should start at the left edge for LTR and right edge for RTL
|
|
21419
|
+
// The space distribution is handled by enlargeSpaces()
|
|
21420
|
+
if (direction === 'rtl') {
|
|
21421
|
+
// For RTL justify, we need to account for the line being right-aligned
|
|
21422
|
+
return 0;
|
|
21423
|
+
} else {
|
|
21424
|
+
return 0;
|
|
21425
|
+
}
|
|
21112
21426
|
}
|
|
21427
|
+
|
|
21428
|
+
// Handle non-justify alignments
|
|
21113
21429
|
if (textAlign === CENTER) {
|
|
21114
21430
|
leftOffset = lineDiff / 2;
|
|
21115
21431
|
}
|
|
@@ -21122,6 +21438,8 @@
|
|
|
21122
21438
|
if (textAlign === JUSTIFY_RIGHT) {
|
|
21123
21439
|
leftOffset = lineDiff;
|
|
21124
21440
|
}
|
|
21441
|
+
|
|
21442
|
+
// Apply RTL adjustments for non-justify alignments
|
|
21125
21443
|
if (direction === 'rtl') {
|
|
21126
21444
|
if (textAlign === RIGHT || textAlign === JUSTIFY || textAlign === JUSTIFY_RIGHT) {
|
|
21127
21445
|
leftOffset = 0;
|
|
@@ -22117,13 +22435,86 @@
|
|
|
22117
22435
|
this.textarea.style.fontFamily = target.fontFamily || 'Arial';
|
|
22118
22436
|
this.textarea.style.fontWeight = String(target.fontWeight || 'normal');
|
|
22119
22437
|
this.textarea.style.fontStyle = target.fontStyle || 'normal';
|
|
22120
|
-
|
|
22438
|
+
// Handle text alignment and justification
|
|
22439
|
+
const textAlign = target.textAlign || 'left';
|
|
22440
|
+
let cssTextAlign = textAlign;
|
|
22441
|
+
|
|
22442
|
+
// Detect text direction from content for proper justify handling
|
|
22443
|
+
const autoDetectedDirection = this.firstStrongDir(this.textarea.value || '');
|
|
22444
|
+
|
|
22445
|
+
// DEBUG: Log alignment details
|
|
22446
|
+
console.log('🔍 ALIGNMENT DEBUG:');
|
|
22447
|
+
console.log(' Fabric textAlign:', textAlign);
|
|
22448
|
+
console.log(' Fabric direction:', target.direction);
|
|
22449
|
+
console.log(' Text content:', JSON.stringify(target.text));
|
|
22450
|
+
console.log(' Detected direction:', autoDetectedDirection);
|
|
22451
|
+
|
|
22452
|
+
// Map fabric.js justify to CSS
|
|
22453
|
+
if (textAlign.includes('justify')) {
|
|
22454
|
+
// Try to match fabric.js justify behavior more precisely
|
|
22455
|
+
try {
|
|
22456
|
+
// For justify, we need to replicate fabric.js space expansion
|
|
22457
|
+
// Use CSS justify but with specific settings to match fabric.js better
|
|
22458
|
+
cssTextAlign = 'justify';
|
|
22459
|
+
|
|
22460
|
+
// Set text-align-last based on justify type and detected direction
|
|
22461
|
+
// Smart justify: respect detected direction even when fabric alignment doesn't match
|
|
22462
|
+
if (textAlign === 'justify') {
|
|
22463
|
+
this.textarea.style.textAlignLast = autoDetectedDirection === 'rtl' ? 'right' : 'left';
|
|
22464
|
+
} else if (textAlign === 'justify-left') {
|
|
22465
|
+
// If text is RTL but fabric says justify-left, override to justify-right for better UX
|
|
22466
|
+
if (autoDetectedDirection === 'rtl') {
|
|
22467
|
+
this.textarea.style.textAlignLast = 'right';
|
|
22468
|
+
console.log(' → Overrode justify-left to justify-right for RTL text');
|
|
22469
|
+
} else {
|
|
22470
|
+
this.textarea.style.textAlignLast = 'left';
|
|
22471
|
+
}
|
|
22472
|
+
} else if (textAlign === 'justify-right') {
|
|
22473
|
+
// If text is LTR but fabric says justify-right, override to justify-left for better UX
|
|
22474
|
+
if (autoDetectedDirection === 'ltr') {
|
|
22475
|
+
this.textarea.style.textAlignLast = 'left';
|
|
22476
|
+
console.log(' → Overrode justify-right to justify-left for LTR text');
|
|
22477
|
+
} else {
|
|
22478
|
+
this.textarea.style.textAlignLast = 'right';
|
|
22479
|
+
}
|
|
22480
|
+
} else if (textAlign === 'justify-center') {
|
|
22481
|
+
this.textarea.style.textAlignLast = 'center';
|
|
22482
|
+
}
|
|
22483
|
+
|
|
22484
|
+
// Enhanced justify settings for better fabric.js matching
|
|
22485
|
+
this.textarea.style.textJustify = 'inter-word';
|
|
22486
|
+
this.textarea.style.wordSpacing = 'normal';
|
|
22487
|
+
|
|
22488
|
+
// Additional CSS properties for better justify matching
|
|
22489
|
+
this.textarea.style.textAlign = 'justify';
|
|
22490
|
+
this.textarea.style.textAlignLast = this.textarea.style.textAlignLast;
|
|
22491
|
+
|
|
22492
|
+
// Try to force better justify behavior
|
|
22493
|
+
this.textarea.style.textJustifyTrim = 'none';
|
|
22494
|
+
this.textarea.style.textAutospace = 'none';
|
|
22495
|
+
console.log(' → Applied justify alignment:', textAlign, 'with last-line:', this.textarea.style.textAlignLast);
|
|
22496
|
+
} catch (error) {
|
|
22497
|
+
console.warn(' → Justify setup failed, falling back to standard alignment:', error);
|
|
22498
|
+
cssTextAlign = textAlign.replace('justify-', '').replace('justify', 'left');
|
|
22499
|
+
}
|
|
22500
|
+
} else {
|
|
22501
|
+
this.textarea.style.textAlignLast = 'auto';
|
|
22502
|
+
this.textarea.style.textJustify = 'auto';
|
|
22503
|
+
this.textarea.style.wordSpacing = 'normal';
|
|
22504
|
+
console.log(' → Applied standard alignment:', cssTextAlign);
|
|
22505
|
+
}
|
|
22506
|
+
this.textarea.style.textAlign = cssTextAlign;
|
|
22121
22507
|
this.textarea.style.color = ((_target$fill = target.fill) === null || _target$fill === void 0 ? void 0 : _target$fill.toString()) || '#000';
|
|
22122
22508
|
this.textarea.style.letterSpacing = `${letterSpacingPx}px`;
|
|
22123
|
-
|
|
22509
|
+
|
|
22510
|
+
// Use the already detected direction from above
|
|
22511
|
+
const fabricDirection = target.direction;
|
|
22512
|
+
|
|
22513
|
+
// Use auto-detected direction for better BiDi support, but respect fabric direction if it makes sense
|
|
22514
|
+
this.textarea.style.direction = autoDetectedDirection || fabricDirection || 'ltr';
|
|
22124
22515
|
this.textarea.style.fontVariant = 'normal';
|
|
22125
22516
|
this.textarea.style.fontStretch = 'normal';
|
|
22126
|
-
this.textarea.style.textRendering = 'optimizeLegibility'
|
|
22517
|
+
this.textarea.style.textRendering = 'auto'; // Changed from 'optimizeLegibility' to match canvas
|
|
22127
22518
|
this.textarea.style.fontKerning = 'normal';
|
|
22128
22519
|
this.textarea.style.fontFeatureSettings = 'normal';
|
|
22129
22520
|
this.textarea.style.fontVariationSettings = 'normal';
|
|
@@ -22134,14 +22525,58 @@
|
|
|
22134
22525
|
this.textarea.style.overflowWrap = 'break-word';
|
|
22135
22526
|
this.textarea.style.whiteSpace = 'pre-wrap';
|
|
22136
22527
|
this.textarea.style.hyphens = 'none';
|
|
22137
|
-
this.textarea.style.webkitFontSmoothing = 'antialiased';
|
|
22138
|
-
this.textarea.style.mozOsxFontSmoothing = 'grayscale';
|
|
22139
22528
|
|
|
22140
|
-
//
|
|
22141
|
-
|
|
22529
|
+
// DEBUG: Log final CSS properties
|
|
22530
|
+
console.log('🎨 FINAL TEXTAREA CSS:');
|
|
22531
|
+
console.log(' textAlign:', this.textarea.style.textAlign);
|
|
22532
|
+
console.log(' textAlignLast:', this.textarea.style.textAlignLast);
|
|
22533
|
+
console.log(' direction:', this.textarea.style.direction);
|
|
22534
|
+
console.log(' unicodeBidi:', this.textarea.style.unicodeBidi);
|
|
22535
|
+
console.log(' width:', this.textarea.style.width);
|
|
22536
|
+
console.log(' textJustify:', this.textarea.style.textJustify);
|
|
22537
|
+
console.log(' wordSpacing:', this.textarea.style.wordSpacing);
|
|
22538
|
+
console.log(' whiteSpace:', this.textarea.style.whiteSpace);
|
|
22539
|
+
|
|
22540
|
+
// If justify, log Fabric object dimensions for comparison
|
|
22541
|
+
if (textAlign.includes('justify')) {
|
|
22542
|
+
var _calcTextWidth, _ref;
|
|
22543
|
+
console.log('🔧 FABRIC OBJECT JUSTIFY INFO:');
|
|
22544
|
+
console.log(' Fabric width:', target.width);
|
|
22545
|
+
console.log(' Fabric calcTextWidth:', (_calcTextWidth = (_ref = target).calcTextWidth) === null || _calcTextWidth === void 0 ? void 0 : _calcTextWidth.call(_ref));
|
|
22546
|
+
console.log(' Fabric textAlign:', target.textAlign);
|
|
22547
|
+
console.log(' Text lines:', target.textLines);
|
|
22548
|
+
}
|
|
22549
|
+
|
|
22550
|
+
// Debug font properties matching
|
|
22551
|
+
console.log('🔤 FONT PROPERTIES COMPARISON:');
|
|
22552
|
+
console.log(' Fabric fontFamily:', target.fontFamily);
|
|
22553
|
+
console.log(' Fabric fontWeight:', target.fontWeight);
|
|
22554
|
+
console.log(' Fabric fontStyle:', target.fontStyle);
|
|
22555
|
+
console.log(' Fabric fontSize:', target.fontSize);
|
|
22556
|
+
console.log(' → Textarea fontFamily:', this.textarea.style.fontFamily);
|
|
22557
|
+
console.log(' → Textarea fontWeight:', this.textarea.style.fontWeight);
|
|
22558
|
+
console.log(' → Textarea fontStyle:', this.textarea.style.fontStyle);
|
|
22559
|
+
console.log(' → Textarea fontSize:', this.textarea.style.fontSize);
|
|
22560
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
22142
22561
|
|
|
22143
|
-
//
|
|
22144
|
-
|
|
22562
|
+
// Enhanced font rendering to better match fabric.js canvas rendering
|
|
22563
|
+
// Default to auto for more natural rendering
|
|
22564
|
+
this.textarea.style.webkitFontSmoothing = 'auto';
|
|
22565
|
+
this.textarea.style.mozOsxFontSmoothing = 'auto';
|
|
22566
|
+
this.textarea.style.fontSmooth = 'auto';
|
|
22567
|
+
this.textarea.style.textSizeAdjust = 'none';
|
|
22568
|
+
|
|
22569
|
+
// For bold fonts, use subpixel rendering to match canvas thickness better
|
|
22570
|
+
const fontWeight = String(target.fontWeight || 'normal');
|
|
22571
|
+
const isBold = fontWeight === 'bold' || fontWeight === '700' || parseInt(fontWeight) >= 600;
|
|
22572
|
+
if (isBold) {
|
|
22573
|
+
this.textarea.style.webkitFontSmoothing = 'subpixel-antialiased';
|
|
22574
|
+
this.textarea.style.mozOsxFontSmoothing = 'unset';
|
|
22575
|
+
console.log('🔤 Applied enhanced bold rendering for better thickness matching');
|
|
22576
|
+
}
|
|
22577
|
+
console.log('🎨 FONT SMOOTHING APPLIED:');
|
|
22578
|
+
console.log(' webkitFontSmoothing:', this.textarea.style.webkitFontSmoothing);
|
|
22579
|
+
console.log(' mozOsxFontSmoothing:', this.textarea.style.mozOsxFontSmoothing);
|
|
22145
22580
|
|
|
22146
22581
|
// Initial bounds are set correctly by Fabric.js - don't force update here
|
|
22147
22582
|
}
|
|
@@ -22376,6 +22811,23 @@
|
|
|
22376
22811
|
// Handle commit/cancel after restoring visibility
|
|
22377
22812
|
if (commit && !this.isComposing) {
|
|
22378
22813
|
const finalText = this.textarea.value;
|
|
22814
|
+
|
|
22815
|
+
// Auto-detect text direction and update fabric object if needed
|
|
22816
|
+
const detectedDirection = this.firstStrongDir(finalText);
|
|
22817
|
+
const currentDirection = this.target.direction || 'ltr';
|
|
22818
|
+
if (detectedDirection && detectedDirection !== currentDirection) {
|
|
22819
|
+
console.log(`🔄 Overlay Exit: Auto-detected direction change from "${currentDirection}" to "${detectedDirection}"`);
|
|
22820
|
+
console.log(` Text content: "${finalText.substring(0, 50)}..."`);
|
|
22821
|
+
|
|
22822
|
+
// Update the fabric object's direction
|
|
22823
|
+
this.target.set('direction', detectedDirection);
|
|
22824
|
+
|
|
22825
|
+
// Force a re-render to apply the direction change
|
|
22826
|
+
this.canvas.requestRenderAll();
|
|
22827
|
+
console.log(`✅ Fabric object direction updated to: ${detectedDirection}`);
|
|
22828
|
+
} else {
|
|
22829
|
+
console.log(`📝 Overlay Exit: Direction unchanged (${currentDirection}), text: "${finalText.substring(0, 30)}..."`);
|
|
22830
|
+
}
|
|
22379
22831
|
if (this.onCommit) {
|
|
22380
22832
|
this.onCommit(finalText);
|
|
22381
22833
|
}
|
|
@@ -25966,30 +26418,17 @@
|
|
|
25966
26418
|
// Detect resize origin during resizing
|
|
25967
26419
|
this.on('resizing', e => {
|
|
25968
26420
|
// Check transform origin to determine which side is being resized
|
|
25969
|
-
console.log('🔍 Resize event data:', e);
|
|
25970
26421
|
if (e.transform) {
|
|
25971
26422
|
const {
|
|
25972
|
-
originX
|
|
25973
|
-
originY
|
|
26423
|
+
originX
|
|
25974
26424
|
} = e.transform;
|
|
25975
|
-
console.log('🔍 Transform origins:', {
|
|
25976
|
-
originX,
|
|
25977
|
-
originY
|
|
25978
|
-
});
|
|
25979
26425
|
// originX tells us which side is the anchor - opposite side is being dragged
|
|
25980
26426
|
resizeOrigin = originX === 'right' ? 'left' : originX === 'left' ? 'right' : null;
|
|
25981
|
-
console.log('🎯 Setting resizeOrigin to:', resizeOrigin);
|
|
25982
26427
|
} else if (e.originX) {
|
|
25983
26428
|
const {
|
|
25984
|
-
originX
|
|
25985
|
-
originY
|
|
26429
|
+
originX
|
|
25986
26430
|
} = e;
|
|
25987
|
-
console.log('🔍 Event origins:', {
|
|
25988
|
-
originX,
|
|
25989
|
-
originY
|
|
25990
|
-
});
|
|
25991
26431
|
resizeOrigin = originX === 'right' ? 'left' : originX === 'left' ? 'right' : null;
|
|
25992
|
-
console.log('🎯 Setting resizeOrigin to:', resizeOrigin);
|
|
25993
26432
|
}
|
|
25994
26433
|
});
|
|
25995
26434
|
|
|
@@ -25997,9 +26436,6 @@
|
|
|
25997
26436
|
// Use 'modified' event which fires after user releases the mouse
|
|
25998
26437
|
this.on('modified', () => {
|
|
25999
26438
|
const currentResizeOrigin = resizeOrigin; // Capture the value before reset
|
|
26000
|
-
console.log('✅ Modified event fired - resize complete, triggering safety snap', {
|
|
26001
|
-
resizeOrigin: currentResizeOrigin
|
|
26002
|
-
});
|
|
26003
26439
|
// Small delay to ensure text layout is updated
|
|
26004
26440
|
setTimeout(() => this.safetySnapWidth(currentResizeOrigin), 10);
|
|
26005
26441
|
resizeOrigin = null; // Reset after capturing
|
|
@@ -26009,7 +26445,6 @@
|
|
|
26009
26445
|
(_this$canvas = this.canvas) === null || _this$canvas === void 0 || _this$canvas.on('object:modified', e => {
|
|
26010
26446
|
if (e.target === this) {
|
|
26011
26447
|
const currentResizeOrigin = resizeOrigin; // Capture the value before reset
|
|
26012
|
-
console.log('✅ Canvas object:modified fired for this textbox');
|
|
26013
26448
|
setTimeout(() => this.safetySnapWidth(currentResizeOrigin), 10);
|
|
26014
26449
|
resizeOrigin = null; // Reset after capturing
|
|
26015
26450
|
}
|
|
@@ -26024,38 +26459,17 @@
|
|
|
26024
26459
|
* @param resizeOrigin - Which side was used for resizing ('left' or 'right')
|
|
26025
26460
|
*/
|
|
26026
26461
|
safetySnapWidth(resizeOrigin) {
|
|
26027
|
-
var _this$_textLines;
|
|
26028
|
-
console.log('🔍 safetySnapWidth called', {
|
|
26029
|
-
isWrapping: this.isWrapping,
|
|
26030
|
-
hasTextLines: !!this._textLines,
|
|
26031
|
-
lineCount: ((_this$_textLines = this._textLines) === null || _this$_textLines === void 0 ? void 0 : _this$_textLines.length) || 0,
|
|
26032
|
-
currentWidth: this.width,
|
|
26033
|
-
type: this.type,
|
|
26034
|
-
text: this.text
|
|
26035
|
-
});
|
|
26036
|
-
|
|
26037
26462
|
// For Textbox objects, we always want to check for clipping regardless of isWrapping flag
|
|
26038
26463
|
if (!this._textLines || this.type.toLowerCase() !== 'textbox' || this._textLines.length === 0) {
|
|
26039
|
-
var _this$_textLines2;
|
|
26040
|
-
console.log('❌ Early return - missing requirements', {
|
|
26041
|
-
hasTextLines: !!this._textLines,
|
|
26042
|
-
typeMatch: this.type.toLowerCase() === 'textbox',
|
|
26043
|
-
actualType: this.type,
|
|
26044
|
-
hasLines: ((_this$_textLines2 = this._textLines) === null || _this$_textLines2 === void 0 ? void 0 : _this$_textLines2.length) > 0
|
|
26045
|
-
});
|
|
26046
26464
|
return;
|
|
26047
26465
|
}
|
|
26048
26466
|
const lineCount = this._textLines.length;
|
|
26049
26467
|
if (lineCount === 0) return;
|
|
26050
|
-
|
|
26051
|
-
// Check all lines, not just the last one
|
|
26052
|
-
let maxActualLineWidth = 0; // Actual measured width without buffers
|
|
26053
26468
|
let maxRequiredWidth = 0; // Width including RTL buffer
|
|
26054
26469
|
|
|
26055
26470
|
for (let i = 0; i < lineCount; i++) {
|
|
26056
26471
|
const lineText = this._textLines[i].join(''); // Convert grapheme array to string
|
|
26057
26472
|
const lineWidth = this.getLineWidth(i);
|
|
26058
|
-
maxActualLineWidth = Math.max(maxActualLineWidth, lineWidth);
|
|
26059
26473
|
|
|
26060
26474
|
// RTL detection - regex for Arabic, Hebrew, and other RTL characters
|
|
26061
26475
|
const rtlRegex = /[\u0590-\u05FF\u0600-\u06FF\u0750-\u077F\uFB50-\uFDFF\uFE70-\uFEFF]/;
|
|
@@ -26075,11 +26489,6 @@
|
|
|
26075
26489
|
var _this$canvas2;
|
|
26076
26490
|
// Set width to exactly what's needed + minimal safety margin
|
|
26077
26491
|
const newWidth = maxRequiredWidth + 1; // Add just 1px safety margin
|
|
26078
|
-
console.log(`Safety snap: ${this.width.toFixed(0)}px -> ${newWidth.toFixed(0)}px`, {
|
|
26079
|
-
maxActualLineWidth: maxActualLineWidth.toFixed(1),
|
|
26080
|
-
maxRequiredWidth: maxRequiredWidth.toFixed(1),
|
|
26081
|
-
difference: (newWidth - this.width).toFixed(1)
|
|
26082
|
-
});
|
|
26083
26492
|
|
|
26084
26493
|
// Store original position before width change
|
|
26085
26494
|
const originalLeft = this.left;
|
|
@@ -26095,19 +26504,12 @@
|
|
|
26095
26504
|
// Only compensate position when resizing from left handle
|
|
26096
26505
|
// Right handle resize doesn't shift the text position
|
|
26097
26506
|
if (resizeOrigin === 'left') {
|
|
26098
|
-
console.log('🔧 Compensating for left-side resize', {
|
|
26099
|
-
originalLeft,
|
|
26100
|
-
widthIncrease,
|
|
26101
|
-
newLeft: originalLeft - widthIncrease
|
|
26102
|
-
});
|
|
26103
26507
|
// When resizing from left, the expansion pushes text right
|
|
26104
26508
|
// Compensate by moving the textbox left by the width increase
|
|
26105
26509
|
this.set({
|
|
26106
26510
|
'left': originalLeft - widthIncrease,
|
|
26107
26511
|
'top': originalTop
|
|
26108
26512
|
});
|
|
26109
|
-
} else {
|
|
26110
|
-
console.log('✅ Right-side resize, no compensation needed');
|
|
26111
26513
|
}
|
|
26112
26514
|
this.setCoords();
|
|
26113
26515
|
|