@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.mjs
CHANGED
|
@@ -354,7 +354,7 @@ class Cache {
|
|
|
354
354
|
}
|
|
355
355
|
const cache = new Cache();
|
|
356
356
|
|
|
357
|
-
var version = "7.0.
|
|
357
|
+
var version = "7.0.1-beta2";
|
|
358
358
|
|
|
359
359
|
// use this syntax so babel plugin see this import here
|
|
360
360
|
const VERSION = version;
|
|
@@ -17834,10 +17834,189 @@ _defineProperty(Line, "ATTRIBUTE_NAMES", SHARED_ATTRIBUTES.concat(coordProps));
|
|
|
17834
17834
|
classRegistry.setClass(Line);
|
|
17835
17835
|
classRegistry.setSVGClass(Line);
|
|
17836
17836
|
|
|
17837
|
+
/**
|
|
17838
|
+
* Calculate the distance between two points
|
|
17839
|
+
*/
|
|
17840
|
+
function pointDistance(p1, p2) {
|
|
17841
|
+
return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
|
|
17842
|
+
}
|
|
17843
|
+
|
|
17844
|
+
/**
|
|
17845
|
+
* Normalize a vector
|
|
17846
|
+
*/
|
|
17847
|
+
function normalizeVector(vector) {
|
|
17848
|
+
const length = Math.sqrt(vector.x * vector.x + vector.y * vector.y);
|
|
17849
|
+
if (length === 0) return {
|
|
17850
|
+
x: 0,
|
|
17851
|
+
y: 0
|
|
17852
|
+
};
|
|
17853
|
+
return {
|
|
17854
|
+
x: vector.x / length,
|
|
17855
|
+
y: vector.y / length
|
|
17856
|
+
};
|
|
17857
|
+
}
|
|
17858
|
+
|
|
17859
|
+
/**
|
|
17860
|
+
* Get the maximum allowed radius for a corner based on adjacent edge lengths
|
|
17861
|
+
*/
|
|
17862
|
+
function getMaxRadius(prevPoint, currentPoint, nextPoint) {
|
|
17863
|
+
const dist1 = pointDistance(prevPoint, currentPoint);
|
|
17864
|
+
const dist2 = pointDistance(currentPoint, nextPoint);
|
|
17865
|
+
return Math.min(dist1, dist2) / 2;
|
|
17866
|
+
}
|
|
17867
|
+
|
|
17868
|
+
/**
|
|
17869
|
+
* Calculate rounded corner data for a single corner
|
|
17870
|
+
*/
|
|
17871
|
+
function calculateRoundedCorner(prevPoint, currentPoint, nextPoint, radius) {
|
|
17872
|
+
// Calculate edge vectors
|
|
17873
|
+
const edge1 = {
|
|
17874
|
+
x: currentPoint.x - prevPoint.x,
|
|
17875
|
+
y: currentPoint.y - prevPoint.y
|
|
17876
|
+
};
|
|
17877
|
+
const edge2 = {
|
|
17878
|
+
x: nextPoint.x - currentPoint.x,
|
|
17879
|
+
y: nextPoint.y - currentPoint.y
|
|
17880
|
+
};
|
|
17881
|
+
|
|
17882
|
+
// Normalize edge vectors
|
|
17883
|
+
const norm1 = normalizeVector(edge1);
|
|
17884
|
+
const norm2 = normalizeVector(edge2);
|
|
17885
|
+
|
|
17886
|
+
// Calculate the maximum allowed radius
|
|
17887
|
+
const maxRadius = getMaxRadius(prevPoint, currentPoint, nextPoint);
|
|
17888
|
+
const actualRadius = Math.min(radius, maxRadius);
|
|
17889
|
+
|
|
17890
|
+
// Calculate start and end points of the rounded corner
|
|
17891
|
+
const startPoint = {
|
|
17892
|
+
x: currentPoint.x - norm1.x * actualRadius,
|
|
17893
|
+
y: currentPoint.y - norm1.y * actualRadius
|
|
17894
|
+
};
|
|
17895
|
+
const endPoint = {
|
|
17896
|
+
x: currentPoint.x + norm2.x * actualRadius,
|
|
17897
|
+
y: currentPoint.y + norm2.y * actualRadius
|
|
17898
|
+
};
|
|
17899
|
+
|
|
17900
|
+
// Calculate control points for bezier curve
|
|
17901
|
+
// Using the magic number kRect for optimal circular approximation
|
|
17902
|
+
const controlOffset = actualRadius * kRect;
|
|
17903
|
+
const cp1 = {
|
|
17904
|
+
x: startPoint.x + norm1.x * controlOffset,
|
|
17905
|
+
y: startPoint.y + norm1.y * controlOffset
|
|
17906
|
+
};
|
|
17907
|
+
const cp2 = {
|
|
17908
|
+
x: endPoint.x - norm2.x * controlOffset,
|
|
17909
|
+
y: endPoint.y - norm2.y * controlOffset
|
|
17910
|
+
};
|
|
17911
|
+
return {
|
|
17912
|
+
corner: currentPoint,
|
|
17913
|
+
start: startPoint,
|
|
17914
|
+
end: endPoint,
|
|
17915
|
+
cp1,
|
|
17916
|
+
cp2,
|
|
17917
|
+
actualRadius
|
|
17918
|
+
};
|
|
17919
|
+
}
|
|
17920
|
+
|
|
17921
|
+
/**
|
|
17922
|
+
* Apply corner radius to a polygon defined by points
|
|
17923
|
+
*/
|
|
17924
|
+
function applyCornerRadiusToPolygon(points, radius) {
|
|
17925
|
+
let radiusAsPercentage = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
17926
|
+
if (points.length < 3) {
|
|
17927
|
+
throw new Error('Polygon must have at least 3 points');
|
|
17928
|
+
}
|
|
17929
|
+
|
|
17930
|
+
// Calculate bounding box if radius is percentage-based
|
|
17931
|
+
let actualRadius = radius;
|
|
17932
|
+
if (radiusAsPercentage) {
|
|
17933
|
+
const minX = Math.min(...points.map(p => p.x));
|
|
17934
|
+
const maxX = Math.max(...points.map(p => p.x));
|
|
17935
|
+
const minY = Math.min(...points.map(p => p.y));
|
|
17936
|
+
const maxY = Math.max(...points.map(p => p.y));
|
|
17937
|
+
const width = maxX - minX;
|
|
17938
|
+
const height = maxY - minY;
|
|
17939
|
+
const minDimension = Math.min(width, height);
|
|
17940
|
+
actualRadius = radius / 100 * minDimension;
|
|
17941
|
+
}
|
|
17942
|
+
const roundedCorners = [];
|
|
17943
|
+
for (let i = 0; i < points.length; i++) {
|
|
17944
|
+
const prevIndex = (i - 1 + points.length) % points.length;
|
|
17945
|
+
const nextIndex = (i + 1) % points.length;
|
|
17946
|
+
const prevPoint = points[prevIndex];
|
|
17947
|
+
const currentPoint = points[i];
|
|
17948
|
+
const nextPoint = points[nextIndex];
|
|
17949
|
+
const roundedCorner = calculateRoundedCorner(prevPoint, currentPoint, nextPoint, actualRadius);
|
|
17950
|
+
roundedCorners.push(roundedCorner);
|
|
17951
|
+
}
|
|
17952
|
+
return roundedCorners;
|
|
17953
|
+
}
|
|
17954
|
+
|
|
17955
|
+
/**
|
|
17956
|
+
* Render a rounded polygon to a canvas context
|
|
17957
|
+
*/
|
|
17958
|
+
function renderRoundedPolygon(ctx, roundedCorners) {
|
|
17959
|
+
let closed = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
|
|
17960
|
+
if (roundedCorners.length === 0) return;
|
|
17961
|
+
ctx.beginPath();
|
|
17962
|
+
|
|
17963
|
+
// Start at the first corner's start point
|
|
17964
|
+
const firstCorner = roundedCorners[0];
|
|
17965
|
+
ctx.moveTo(firstCorner.start.x, firstCorner.start.y);
|
|
17966
|
+
for (let i = 0; i < roundedCorners.length; i++) {
|
|
17967
|
+
const corner = roundedCorners[i];
|
|
17968
|
+
const nextIndex = (i + 1) % roundedCorners.length;
|
|
17969
|
+
const nextCorner = roundedCorners[nextIndex];
|
|
17970
|
+
|
|
17971
|
+
// Draw the rounded corner using bezier curve
|
|
17972
|
+
ctx.bezierCurveTo(corner.cp1.x, corner.cp1.y, corner.cp2.x, corner.cp2.y, corner.end.x, corner.end.y);
|
|
17973
|
+
|
|
17974
|
+
// Draw line to next corner's start point (if not the last segment in open path)
|
|
17975
|
+
if (i < roundedCorners.length - 1 || closed) {
|
|
17976
|
+
ctx.lineTo(nextCorner.start.x, nextCorner.start.y);
|
|
17977
|
+
}
|
|
17978
|
+
}
|
|
17979
|
+
if (closed) {
|
|
17980
|
+
ctx.closePath();
|
|
17981
|
+
}
|
|
17982
|
+
}
|
|
17983
|
+
|
|
17984
|
+
/**
|
|
17985
|
+
* Generate SVG path data for a rounded polygon
|
|
17986
|
+
*/
|
|
17987
|
+
function generateRoundedPolygonPath(roundedCorners) {
|
|
17988
|
+
let closed = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
|
|
17989
|
+
if (roundedCorners.length === 0) return '';
|
|
17990
|
+
const pathData = [];
|
|
17991
|
+
const firstCorner = roundedCorners[0];
|
|
17992
|
+
|
|
17993
|
+
// Move to first corner's start point
|
|
17994
|
+
pathData.push(`M ${firstCorner.start.x} ${firstCorner.start.y}`);
|
|
17995
|
+
for (let i = 0; i < roundedCorners.length; i++) {
|
|
17996
|
+
const corner = roundedCorners[i];
|
|
17997
|
+
const nextIndex = (i + 1) % roundedCorners.length;
|
|
17998
|
+
const nextCorner = roundedCorners[nextIndex];
|
|
17999
|
+
|
|
18000
|
+
// Add bezier curve for the rounded corner
|
|
18001
|
+
pathData.push(`C ${corner.cp1.x} ${corner.cp1.y} ${corner.cp2.x} ${corner.cp2.y} ${corner.end.x} ${corner.end.y}`);
|
|
18002
|
+
|
|
18003
|
+
// Add line to next corner's start point (if not the last segment in open path)
|
|
18004
|
+
if (i < roundedCorners.length - 1 || closed) {
|
|
18005
|
+
pathData.push(`L ${nextCorner.start.x} ${nextCorner.start.y}`);
|
|
18006
|
+
}
|
|
18007
|
+
}
|
|
18008
|
+
if (closed) {
|
|
18009
|
+
pathData.push('Z');
|
|
18010
|
+
}
|
|
18011
|
+
return pathData.join(' ');
|
|
18012
|
+
}
|
|
18013
|
+
|
|
17837
18014
|
const triangleDefaultValues = {
|
|
17838
18015
|
width: 100,
|
|
17839
|
-
height: 100
|
|
18016
|
+
height: 100,
|
|
18017
|
+
cornerRadius: 0
|
|
17840
18018
|
};
|
|
18019
|
+
const TRIANGLE_PROPS = ['cornerRadius'];
|
|
17841
18020
|
class Triangle extends FabricObject {
|
|
17842
18021
|
static getDefaults() {
|
|
17843
18022
|
return {
|
|
@@ -17856,34 +18035,90 @@ class Triangle extends FabricObject {
|
|
|
17856
18035
|
this.setOptions(options);
|
|
17857
18036
|
}
|
|
17858
18037
|
|
|
18038
|
+
/**
|
|
18039
|
+
* Get triangle points as an array of XY coordinates
|
|
18040
|
+
* @private
|
|
18041
|
+
*/
|
|
18042
|
+
_getTrianglePoints() {
|
|
18043
|
+
const widthBy2 = this.width / 2;
|
|
18044
|
+
const heightBy2 = this.height / 2;
|
|
18045
|
+
return [{
|
|
18046
|
+
x: -widthBy2,
|
|
18047
|
+
y: heightBy2
|
|
18048
|
+
},
|
|
18049
|
+
// bottom left
|
|
18050
|
+
{
|
|
18051
|
+
x: 0,
|
|
18052
|
+
y: -heightBy2
|
|
18053
|
+
},
|
|
18054
|
+
// top center
|
|
18055
|
+
{
|
|
18056
|
+
x: widthBy2,
|
|
18057
|
+
y: heightBy2
|
|
18058
|
+
} // bottom right
|
|
18059
|
+
];
|
|
18060
|
+
}
|
|
18061
|
+
|
|
17859
18062
|
/**
|
|
17860
18063
|
* @private
|
|
17861
18064
|
* @param {CanvasRenderingContext2D} ctx Context to render on
|
|
17862
18065
|
*/
|
|
17863
18066
|
_render(ctx) {
|
|
17864
|
-
|
|
17865
|
-
|
|
17866
|
-
|
|
17867
|
-
|
|
17868
|
-
|
|
17869
|
-
|
|
17870
|
-
|
|
18067
|
+
if (this.cornerRadius > 0) {
|
|
18068
|
+
// Render rounded triangle
|
|
18069
|
+
const points = this._getTrianglePoints();
|
|
18070
|
+
const roundedCorners = applyCornerRadiusToPolygon(points, this.cornerRadius);
|
|
18071
|
+
renderRoundedPolygon(ctx, roundedCorners, true);
|
|
18072
|
+
} else {
|
|
18073
|
+
// Render sharp triangle (original implementation)
|
|
18074
|
+
const widthBy2 = this.width / 2;
|
|
18075
|
+
const heightBy2 = this.height / 2;
|
|
18076
|
+
ctx.beginPath();
|
|
18077
|
+
ctx.moveTo(-widthBy2, heightBy2);
|
|
18078
|
+
ctx.lineTo(0, -heightBy2);
|
|
18079
|
+
ctx.lineTo(widthBy2, heightBy2);
|
|
18080
|
+
ctx.closePath();
|
|
18081
|
+
}
|
|
17871
18082
|
this._renderPaintInOrder(ctx);
|
|
17872
18083
|
}
|
|
17873
18084
|
|
|
18085
|
+
/**
|
|
18086
|
+
* Returns object representation of an instance
|
|
18087
|
+
* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
|
|
18088
|
+
* @return {Object} object representation of an instance
|
|
18089
|
+
*/
|
|
18090
|
+
toObject() {
|
|
18091
|
+
let propertiesToInclude = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
|
|
18092
|
+
return super.toObject([...TRIANGLE_PROPS, ...propertiesToInclude]);
|
|
18093
|
+
}
|
|
18094
|
+
|
|
17874
18095
|
/**
|
|
17875
18096
|
* Returns svg representation of an instance
|
|
17876
18097
|
* @return {Array} an array of strings with the specific svg representation
|
|
17877
18098
|
* of the instance
|
|
17878
18099
|
*/
|
|
17879
18100
|
_toSVG() {
|
|
17880
|
-
|
|
17881
|
-
|
|
17882
|
-
points =
|
|
17883
|
-
|
|
18101
|
+
if (this.cornerRadius > 0) {
|
|
18102
|
+
// Generate rounded triangle as path
|
|
18103
|
+
const points = this._getTrianglePoints();
|
|
18104
|
+
const roundedCorners = applyCornerRadiusToPolygon(points, this.cornerRadius);
|
|
18105
|
+
const pathData = generateRoundedPolygonPath(roundedCorners, true);
|
|
18106
|
+
return ['<path ', 'COMMON_PARTS', `d="${pathData}" />`];
|
|
18107
|
+
} else {
|
|
18108
|
+
// Original sharp triangle implementation
|
|
18109
|
+
const widthBy2 = this.width / 2;
|
|
18110
|
+
const heightBy2 = this.height / 2;
|
|
18111
|
+
const points = `${-widthBy2} ${heightBy2},0 ${-heightBy2},${widthBy2} ${heightBy2}`;
|
|
18112
|
+
return ['<polygon ', 'COMMON_PARTS', 'points="', points, '" />'];
|
|
18113
|
+
}
|
|
17884
18114
|
}
|
|
17885
18115
|
}
|
|
18116
|
+
/**
|
|
18117
|
+
* Corner radius for rounded triangle corners
|
|
18118
|
+
* @type Number
|
|
18119
|
+
*/
|
|
17886
18120
|
_defineProperty(Triangle, "type", 'Triangle');
|
|
18121
|
+
_defineProperty(Triangle, "cacheProperties", [...cacheProperties, ...TRIANGLE_PROPS]);
|
|
17887
18122
|
_defineProperty(Triangle, "ownDefaults", triangleDefaultValues);
|
|
17888
18123
|
classRegistry.setClass(Triangle);
|
|
17889
18124
|
classRegistry.setSVGClass(Triangle);
|
|
@@ -18048,7 +18283,8 @@ const polylineDefaultValues = {
|
|
|
18048
18283
|
/**
|
|
18049
18284
|
* @deprecated transient option soon to be removed in favor of a different design
|
|
18050
18285
|
*/
|
|
18051
|
-
exactBoundingBox: false
|
|
18286
|
+
exactBoundingBox: false,
|
|
18287
|
+
cornerRadius: 0
|
|
18052
18288
|
};
|
|
18053
18289
|
class Polyline extends FabricObject {
|
|
18054
18290
|
static getDefaults() {
|
|
@@ -18262,7 +18498,7 @@ class Polyline extends FabricObject {
|
|
|
18262
18498
|
toObject() {
|
|
18263
18499
|
let propertiesToInclude = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
|
|
18264
18500
|
return {
|
|
18265
|
-
...super.toObject(propertiesToInclude),
|
|
18501
|
+
...super.toObject(['cornerRadius', ...propertiesToInclude]),
|
|
18266
18502
|
points: this.points.map(_ref => {
|
|
18267
18503
|
let {
|
|
18268
18504
|
x,
|
|
@@ -18282,14 +18518,28 @@ class Polyline extends FabricObject {
|
|
|
18282
18518
|
* of the instance
|
|
18283
18519
|
*/
|
|
18284
18520
|
_toSVG() {
|
|
18285
|
-
|
|
18286
|
-
|
|
18287
|
-
|
|
18288
|
-
|
|
18289
|
-
|
|
18290
|
-
|
|
18521
|
+
if (this.cornerRadius > 0 && this.points.length >= 3) {
|
|
18522
|
+
// Generate rounded polygon/polyline as path
|
|
18523
|
+
const diffX = this.pathOffset.x;
|
|
18524
|
+
const diffY = this.pathOffset.y;
|
|
18525
|
+
const adjustedPoints = this.points.map(point => ({
|
|
18526
|
+
x: point.x - diffX,
|
|
18527
|
+
y: point.y - diffY
|
|
18528
|
+
}));
|
|
18529
|
+
const roundedCorners = applyCornerRadiusToPolygon(adjustedPoints, this.cornerRadius);
|
|
18530
|
+
const pathData = generateRoundedPolygonPath(roundedCorners, !this.isOpen());
|
|
18531
|
+
return ['<path ', 'COMMON_PARTS', `d="${pathData}" />\n`];
|
|
18532
|
+
} else {
|
|
18533
|
+
// Original sharp corners implementation
|
|
18534
|
+
const points = [];
|
|
18535
|
+
const diffX = this.pathOffset.x;
|
|
18536
|
+
const diffY = this.pathOffset.y;
|
|
18537
|
+
const NUM_FRACTION_DIGITS = config.NUM_FRACTION_DIGITS;
|
|
18538
|
+
for (let i = 0, len = this.points.length; i < len; i++) {
|
|
18539
|
+
points.push(toFixed(this.points[i].x - diffX, NUM_FRACTION_DIGITS), ',', toFixed(this.points[i].y - diffY, NUM_FRACTION_DIGITS), ' ');
|
|
18540
|
+
}
|
|
18541
|
+
return [`<${this.constructor.type.toLowerCase()} `, 'COMMON_PARTS', `points="${points.join('')}" />\n`];
|
|
18291
18542
|
}
|
|
18292
|
-
return [`<${this.constructor.type.toLowerCase()} `, 'COMMON_PARTS', `points="${points.join('')}" />\n`];
|
|
18293
18543
|
}
|
|
18294
18544
|
|
|
18295
18545
|
/**
|
|
@@ -18305,13 +18555,24 @@ class Polyline extends FabricObject {
|
|
|
18305
18555
|
// NaN comes from parseFloat of a empty string in parser
|
|
18306
18556
|
return;
|
|
18307
18557
|
}
|
|
18308
|
-
|
|
18309
|
-
|
|
18310
|
-
|
|
18311
|
-
|
|
18312
|
-
|
|
18558
|
+
if (this.cornerRadius > 0 && len >= 3) {
|
|
18559
|
+
// Render with rounded corners
|
|
18560
|
+
const adjustedPoints = this.points.map(point => ({
|
|
18561
|
+
x: point.x - x,
|
|
18562
|
+
y: point.y - y
|
|
18563
|
+
}));
|
|
18564
|
+
const roundedCorners = applyCornerRadiusToPolygon(adjustedPoints, this.cornerRadius);
|
|
18565
|
+
renderRoundedPolygon(ctx, roundedCorners, !this.isOpen());
|
|
18566
|
+
} else {
|
|
18567
|
+
// Original sharp corners implementation
|
|
18568
|
+
ctx.beginPath();
|
|
18569
|
+
ctx.moveTo(this.points[0].x - x, this.points[0].y - y);
|
|
18570
|
+
for (let i = 0; i < len; i++) {
|
|
18571
|
+
const point = this.points[i];
|
|
18572
|
+
ctx.lineTo(point.x - x, point.y - y);
|
|
18573
|
+
}
|
|
18574
|
+
!this.isOpen() && ctx.closePath();
|
|
18313
18575
|
}
|
|
18314
|
-
!this.isOpen() && ctx.closePath();
|
|
18315
18576
|
this._renderPaintInOrder(ctx);
|
|
18316
18577
|
}
|
|
18317
18578
|
|
|
@@ -18376,10 +18637,15 @@ class Polyline extends FabricObject {
|
|
|
18376
18637
|
* @type Boolean
|
|
18377
18638
|
* @default false
|
|
18378
18639
|
*/
|
|
18640
|
+
/**
|
|
18641
|
+
* Corner radius for rounded corners
|
|
18642
|
+
* @type Number
|
|
18643
|
+
* @default 0
|
|
18644
|
+
*/
|
|
18379
18645
|
_defineProperty(Polyline, "ownDefaults", polylineDefaultValues);
|
|
18380
18646
|
_defineProperty(Polyline, "type", 'Polyline');
|
|
18381
18647
|
_defineProperty(Polyline, "layoutProperties", [SKEW_X, SKEW_Y, 'strokeLineCap', 'strokeLineJoin', 'strokeMiterLimit', 'strokeWidth', 'strokeUniform', 'points']);
|
|
18382
|
-
_defineProperty(Polyline, "cacheProperties", [...cacheProperties, 'points']);
|
|
18648
|
+
_defineProperty(Polyline, "cacheProperties", [...cacheProperties, 'points', 'cornerRadius']);
|
|
18383
18649
|
_defineProperty(Polyline, "ATTRIBUTE_NAMES", [...SHARED_ATTRIBUTES]);
|
|
18384
18650
|
classRegistry.setClass(Polyline);
|
|
18385
18651
|
classRegistry.setSVGClass(Polyline);
|
|
@@ -20189,6 +20455,7 @@ class FabricText extends StyledText {
|
|
|
20189
20455
|
*/
|
|
20190
20456
|
enlargeSpaces() {
|
|
20191
20457
|
let diffSpace, currentLineWidth, numberOfSpaces, accumulatedSpace, line, charBound, spaces;
|
|
20458
|
+
const isRtl = this.direction === 'rtl';
|
|
20192
20459
|
for (let i = 0, len = this._textLines.length; i < len; i++) {
|
|
20193
20460
|
if (this.textAlign !== JUSTIFY && (i === len - 1 || this.isEndOfWrapping(i))) {
|
|
20194
20461
|
continue;
|
|
@@ -20199,15 +20466,44 @@ class FabricText extends StyledText {
|
|
|
20199
20466
|
if (currentLineWidth < this.width && (spaces = this.textLines[i].match(this._reSpacesAndTabs))) {
|
|
20200
20467
|
numberOfSpaces = spaces.length;
|
|
20201
20468
|
diffSpace = (this.width - currentLineWidth) / numberOfSpaces;
|
|
20202
|
-
|
|
20203
|
-
|
|
20204
|
-
|
|
20205
|
-
|
|
20206
|
-
|
|
20207
|
-
|
|
20208
|
-
|
|
20209
|
-
|
|
20210
|
-
|
|
20469
|
+
if (isRtl) {
|
|
20470
|
+
for (let j = 0; j < line.length; j++) {
|
|
20471
|
+
if (this._reSpaceAndTab.test(line[j])) ;
|
|
20472
|
+
}
|
|
20473
|
+
|
|
20474
|
+
// For RTL, we need to work backwards through the visual positions
|
|
20475
|
+
// but still update logical positions correctly
|
|
20476
|
+
let spaceCount = 0;
|
|
20477
|
+
for (let j = 0; j <= line.length; j++) {
|
|
20478
|
+
charBound = this.__charBounds[i][j];
|
|
20479
|
+
if (charBound) {
|
|
20480
|
+
if (this._reSpaceAndTab.test(line[j])) {
|
|
20481
|
+
charBound.width += diffSpace;
|
|
20482
|
+
charBound.kernedWidth += diffSpace;
|
|
20483
|
+
spaceCount++;
|
|
20484
|
+
}
|
|
20485
|
+
|
|
20486
|
+
// For RTL, shift all characters to the right by the total expansion
|
|
20487
|
+
// minus the expansion that comes after this character
|
|
20488
|
+
const remainingSpaces = numberOfSpaces - spaceCount;
|
|
20489
|
+
const shiftAmount = remainingSpaces * diffSpace;
|
|
20490
|
+
charBound.left += shiftAmount;
|
|
20491
|
+
}
|
|
20492
|
+
}
|
|
20493
|
+
} else {
|
|
20494
|
+
// LTR processing (original logic)
|
|
20495
|
+
for (let j = 0; j <= line.length; j++) {
|
|
20496
|
+
charBound = this.__charBounds[i][j];
|
|
20497
|
+
if (charBound) {
|
|
20498
|
+
if (this._reSpaceAndTab.test(line[j])) {
|
|
20499
|
+
charBound.width += diffSpace;
|
|
20500
|
+
charBound.kernedWidth += diffSpace;
|
|
20501
|
+
charBound.left += accumulatedSpace;
|
|
20502
|
+
accumulatedSpace += diffSpace;
|
|
20503
|
+
} else {
|
|
20504
|
+
charBound.left += accumulatedSpace;
|
|
20505
|
+
}
|
|
20506
|
+
}
|
|
20211
20507
|
}
|
|
20212
20508
|
}
|
|
20213
20509
|
}
|
|
@@ -20865,7 +21161,15 @@ class FabricText extends StyledText {
|
|
|
20865
21161
|
if (currentDirection !== this.direction) {
|
|
20866
21162
|
ctx.canvas.setAttribute('dir', isLtr ? 'ltr' : 'rtl');
|
|
20867
21163
|
ctx.direction = isLtr ? 'ltr' : 'rtl';
|
|
20868
|
-
|
|
21164
|
+
|
|
21165
|
+
// For justify alignments, we need to set the correct canvas text alignment
|
|
21166
|
+
// This is crucial for RTL text to render in the correct order
|
|
21167
|
+
if (isJustify) {
|
|
21168
|
+
// Justify uses LEFT alignment as a base, letting the character positioning handle justification
|
|
21169
|
+
ctx.textAlign = LEFT;
|
|
21170
|
+
} else {
|
|
21171
|
+
ctx.textAlign = isLtr ? LEFT : RIGHT;
|
|
21172
|
+
}
|
|
20869
21173
|
}
|
|
20870
21174
|
top -= lineHeight * this._fontSizeFraction / this.lineHeight;
|
|
20871
21175
|
if (shortCut) {
|
|
@@ -21101,9 +21405,21 @@ class FabricText extends StyledText {
|
|
|
21101
21405
|
direction = this.direction,
|
|
21102
21406
|
isEndOfWrapping = this.isEndOfWrapping(lineIndex);
|
|
21103
21407
|
let leftOffset = 0;
|
|
21104
|
-
|
|
21105
|
-
|
|
21408
|
+
|
|
21409
|
+
// Handle justify alignments (excluding last lines and wrapped line ends)
|
|
21410
|
+
const isJustifyLine = textAlign === JUSTIFY || textAlign === JUSTIFY_CENTER && !isEndOfWrapping || textAlign === JUSTIFY_RIGHT && !isEndOfWrapping || textAlign === JUSTIFY_LEFT && !isEndOfWrapping;
|
|
21411
|
+
if (isJustifyLine) {
|
|
21412
|
+
// Justify lines should start at the left edge for LTR and right edge for RTL
|
|
21413
|
+
// The space distribution is handled by enlargeSpaces()
|
|
21414
|
+
if (direction === 'rtl') {
|
|
21415
|
+
// For RTL justify, we need to account for the line being right-aligned
|
|
21416
|
+
return 0;
|
|
21417
|
+
} else {
|
|
21418
|
+
return 0;
|
|
21419
|
+
}
|
|
21106
21420
|
}
|
|
21421
|
+
|
|
21422
|
+
// Handle non-justify alignments
|
|
21107
21423
|
if (textAlign === CENTER) {
|
|
21108
21424
|
leftOffset = lineDiff / 2;
|
|
21109
21425
|
}
|
|
@@ -21116,6 +21432,8 @@ class FabricText extends StyledText {
|
|
|
21116
21432
|
if (textAlign === JUSTIFY_RIGHT) {
|
|
21117
21433
|
leftOffset = lineDiff;
|
|
21118
21434
|
}
|
|
21435
|
+
|
|
21436
|
+
// Apply RTL adjustments for non-justify alignments
|
|
21119
21437
|
if (direction === 'rtl') {
|
|
21120
21438
|
if (textAlign === RIGHT || textAlign === JUSTIFY || textAlign === JUSTIFY_RIGHT) {
|
|
21121
21439
|
leftOffset = 0;
|
|
@@ -22111,13 +22429,86 @@ class OverlayEditor {
|
|
|
22111
22429
|
this.textarea.style.fontFamily = target.fontFamily || 'Arial';
|
|
22112
22430
|
this.textarea.style.fontWeight = String(target.fontWeight || 'normal');
|
|
22113
22431
|
this.textarea.style.fontStyle = target.fontStyle || 'normal';
|
|
22114
|
-
|
|
22432
|
+
// Handle text alignment and justification
|
|
22433
|
+
const textAlign = target.textAlign || 'left';
|
|
22434
|
+
let cssTextAlign = textAlign;
|
|
22435
|
+
|
|
22436
|
+
// Detect text direction from content for proper justify handling
|
|
22437
|
+
const autoDetectedDirection = this.firstStrongDir(this.textarea.value || '');
|
|
22438
|
+
|
|
22439
|
+
// DEBUG: Log alignment details
|
|
22440
|
+
console.log('🔍 ALIGNMENT DEBUG:');
|
|
22441
|
+
console.log(' Fabric textAlign:', textAlign);
|
|
22442
|
+
console.log(' Fabric direction:', target.direction);
|
|
22443
|
+
console.log(' Text content:', JSON.stringify(target.text));
|
|
22444
|
+
console.log(' Detected direction:', autoDetectedDirection);
|
|
22445
|
+
|
|
22446
|
+
// Map fabric.js justify to CSS
|
|
22447
|
+
if (textAlign.includes('justify')) {
|
|
22448
|
+
// Try to match fabric.js justify behavior more precisely
|
|
22449
|
+
try {
|
|
22450
|
+
// For justify, we need to replicate fabric.js space expansion
|
|
22451
|
+
// Use CSS justify but with specific settings to match fabric.js better
|
|
22452
|
+
cssTextAlign = 'justify';
|
|
22453
|
+
|
|
22454
|
+
// Set text-align-last based on justify type and detected direction
|
|
22455
|
+
// Smart justify: respect detected direction even when fabric alignment doesn't match
|
|
22456
|
+
if (textAlign === 'justify') {
|
|
22457
|
+
this.textarea.style.textAlignLast = autoDetectedDirection === 'rtl' ? 'right' : 'left';
|
|
22458
|
+
} else if (textAlign === 'justify-left') {
|
|
22459
|
+
// If text is RTL but fabric says justify-left, override to justify-right for better UX
|
|
22460
|
+
if (autoDetectedDirection === 'rtl') {
|
|
22461
|
+
this.textarea.style.textAlignLast = 'right';
|
|
22462
|
+
console.log(' → Overrode justify-left to justify-right for RTL text');
|
|
22463
|
+
} else {
|
|
22464
|
+
this.textarea.style.textAlignLast = 'left';
|
|
22465
|
+
}
|
|
22466
|
+
} else if (textAlign === 'justify-right') {
|
|
22467
|
+
// If text is LTR but fabric says justify-right, override to justify-left for better UX
|
|
22468
|
+
if (autoDetectedDirection === 'ltr') {
|
|
22469
|
+
this.textarea.style.textAlignLast = 'left';
|
|
22470
|
+
console.log(' → Overrode justify-right to justify-left for LTR text');
|
|
22471
|
+
} else {
|
|
22472
|
+
this.textarea.style.textAlignLast = 'right';
|
|
22473
|
+
}
|
|
22474
|
+
} else if (textAlign === 'justify-center') {
|
|
22475
|
+
this.textarea.style.textAlignLast = 'center';
|
|
22476
|
+
}
|
|
22477
|
+
|
|
22478
|
+
// Enhanced justify settings for better fabric.js matching
|
|
22479
|
+
this.textarea.style.textJustify = 'inter-word';
|
|
22480
|
+
this.textarea.style.wordSpacing = 'normal';
|
|
22481
|
+
|
|
22482
|
+
// Additional CSS properties for better justify matching
|
|
22483
|
+
this.textarea.style.textAlign = 'justify';
|
|
22484
|
+
this.textarea.style.textAlignLast = this.textarea.style.textAlignLast;
|
|
22485
|
+
|
|
22486
|
+
// Try to force better justify behavior
|
|
22487
|
+
this.textarea.style.textJustifyTrim = 'none';
|
|
22488
|
+
this.textarea.style.textAutospace = 'none';
|
|
22489
|
+
console.log(' → Applied justify alignment:', textAlign, 'with last-line:', this.textarea.style.textAlignLast);
|
|
22490
|
+
} catch (error) {
|
|
22491
|
+
console.warn(' → Justify setup failed, falling back to standard alignment:', error);
|
|
22492
|
+
cssTextAlign = textAlign.replace('justify-', '').replace('justify', 'left');
|
|
22493
|
+
}
|
|
22494
|
+
} else {
|
|
22495
|
+
this.textarea.style.textAlignLast = 'auto';
|
|
22496
|
+
this.textarea.style.textJustify = 'auto';
|
|
22497
|
+
this.textarea.style.wordSpacing = 'normal';
|
|
22498
|
+
console.log(' → Applied standard alignment:', cssTextAlign);
|
|
22499
|
+
}
|
|
22500
|
+
this.textarea.style.textAlign = cssTextAlign;
|
|
22115
22501
|
this.textarea.style.color = ((_target$fill = target.fill) === null || _target$fill === void 0 ? void 0 : _target$fill.toString()) || '#000';
|
|
22116
22502
|
this.textarea.style.letterSpacing = `${letterSpacingPx}px`;
|
|
22117
|
-
|
|
22503
|
+
|
|
22504
|
+
// Use the already detected direction from above
|
|
22505
|
+
const fabricDirection = target.direction;
|
|
22506
|
+
|
|
22507
|
+
// Use auto-detected direction for better BiDi support, but respect fabric direction if it makes sense
|
|
22508
|
+
this.textarea.style.direction = autoDetectedDirection || fabricDirection || 'ltr';
|
|
22118
22509
|
this.textarea.style.fontVariant = 'normal';
|
|
22119
22510
|
this.textarea.style.fontStretch = 'normal';
|
|
22120
|
-
this.textarea.style.textRendering = 'optimizeLegibility'
|
|
22511
|
+
this.textarea.style.textRendering = 'auto'; // Changed from 'optimizeLegibility' to match canvas
|
|
22121
22512
|
this.textarea.style.fontKerning = 'normal';
|
|
22122
22513
|
this.textarea.style.fontFeatureSettings = 'normal';
|
|
22123
22514
|
this.textarea.style.fontVariationSettings = 'normal';
|
|
@@ -22128,14 +22519,58 @@ class OverlayEditor {
|
|
|
22128
22519
|
this.textarea.style.overflowWrap = 'break-word';
|
|
22129
22520
|
this.textarea.style.whiteSpace = 'pre-wrap';
|
|
22130
22521
|
this.textarea.style.hyphens = 'none';
|
|
22131
|
-
this.textarea.style.webkitFontSmoothing = 'antialiased';
|
|
22132
|
-
this.textarea.style.mozOsxFontSmoothing = 'grayscale';
|
|
22133
22522
|
|
|
22134
|
-
//
|
|
22135
|
-
|
|
22523
|
+
// DEBUG: Log final CSS properties
|
|
22524
|
+
console.log('🎨 FINAL TEXTAREA CSS:');
|
|
22525
|
+
console.log(' textAlign:', this.textarea.style.textAlign);
|
|
22526
|
+
console.log(' textAlignLast:', this.textarea.style.textAlignLast);
|
|
22527
|
+
console.log(' direction:', this.textarea.style.direction);
|
|
22528
|
+
console.log(' unicodeBidi:', this.textarea.style.unicodeBidi);
|
|
22529
|
+
console.log(' width:', this.textarea.style.width);
|
|
22530
|
+
console.log(' textJustify:', this.textarea.style.textJustify);
|
|
22531
|
+
console.log(' wordSpacing:', this.textarea.style.wordSpacing);
|
|
22532
|
+
console.log(' whiteSpace:', this.textarea.style.whiteSpace);
|
|
22533
|
+
|
|
22534
|
+
// If justify, log Fabric object dimensions for comparison
|
|
22535
|
+
if (textAlign.includes('justify')) {
|
|
22536
|
+
var _calcTextWidth, _ref;
|
|
22537
|
+
console.log('🔧 FABRIC OBJECT JUSTIFY INFO:');
|
|
22538
|
+
console.log(' Fabric width:', target.width);
|
|
22539
|
+
console.log(' Fabric calcTextWidth:', (_calcTextWidth = (_ref = target).calcTextWidth) === null || _calcTextWidth === void 0 ? void 0 : _calcTextWidth.call(_ref));
|
|
22540
|
+
console.log(' Fabric textAlign:', target.textAlign);
|
|
22541
|
+
console.log(' Text lines:', target.textLines);
|
|
22542
|
+
}
|
|
22543
|
+
|
|
22544
|
+
// Debug font properties matching
|
|
22545
|
+
console.log('🔤 FONT PROPERTIES COMPARISON:');
|
|
22546
|
+
console.log(' Fabric fontFamily:', target.fontFamily);
|
|
22547
|
+
console.log(' Fabric fontWeight:', target.fontWeight);
|
|
22548
|
+
console.log(' Fabric fontStyle:', target.fontStyle);
|
|
22549
|
+
console.log(' Fabric fontSize:', target.fontSize);
|
|
22550
|
+
console.log(' → Textarea fontFamily:', this.textarea.style.fontFamily);
|
|
22551
|
+
console.log(' → Textarea fontWeight:', this.textarea.style.fontWeight);
|
|
22552
|
+
console.log(' → Textarea fontStyle:', this.textarea.style.fontStyle);
|
|
22553
|
+
console.log(' → Textarea fontSize:', this.textarea.style.fontSize);
|
|
22554
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
22136
22555
|
|
|
22137
|
-
//
|
|
22138
|
-
|
|
22556
|
+
// Enhanced font rendering to better match fabric.js canvas rendering
|
|
22557
|
+
// Default to auto for more natural rendering
|
|
22558
|
+
this.textarea.style.webkitFontSmoothing = 'auto';
|
|
22559
|
+
this.textarea.style.mozOsxFontSmoothing = 'auto';
|
|
22560
|
+
this.textarea.style.fontSmooth = 'auto';
|
|
22561
|
+
this.textarea.style.textSizeAdjust = 'none';
|
|
22562
|
+
|
|
22563
|
+
// For bold fonts, use subpixel rendering to match canvas thickness better
|
|
22564
|
+
const fontWeight = String(target.fontWeight || 'normal');
|
|
22565
|
+
const isBold = fontWeight === 'bold' || fontWeight === '700' || parseInt(fontWeight) >= 600;
|
|
22566
|
+
if (isBold) {
|
|
22567
|
+
this.textarea.style.webkitFontSmoothing = 'subpixel-antialiased';
|
|
22568
|
+
this.textarea.style.mozOsxFontSmoothing = 'unset';
|
|
22569
|
+
console.log('🔤 Applied enhanced bold rendering for better thickness matching');
|
|
22570
|
+
}
|
|
22571
|
+
console.log('🎨 FONT SMOOTHING APPLIED:');
|
|
22572
|
+
console.log(' webkitFontSmoothing:', this.textarea.style.webkitFontSmoothing);
|
|
22573
|
+
console.log(' mozOsxFontSmoothing:', this.textarea.style.mozOsxFontSmoothing);
|
|
22139
22574
|
|
|
22140
22575
|
// Initial bounds are set correctly by Fabric.js - don't force update here
|
|
22141
22576
|
}
|
|
@@ -22370,6 +22805,23 @@ class OverlayEditor {
|
|
|
22370
22805
|
// Handle commit/cancel after restoring visibility
|
|
22371
22806
|
if (commit && !this.isComposing) {
|
|
22372
22807
|
const finalText = this.textarea.value;
|
|
22808
|
+
|
|
22809
|
+
// Auto-detect text direction and update fabric object if needed
|
|
22810
|
+
const detectedDirection = this.firstStrongDir(finalText);
|
|
22811
|
+
const currentDirection = this.target.direction || 'ltr';
|
|
22812
|
+
if (detectedDirection && detectedDirection !== currentDirection) {
|
|
22813
|
+
console.log(`🔄 Overlay Exit: Auto-detected direction change from "${currentDirection}" to "${detectedDirection}"`);
|
|
22814
|
+
console.log(` Text content: "${finalText.substring(0, 50)}..."`);
|
|
22815
|
+
|
|
22816
|
+
// Update the fabric object's direction
|
|
22817
|
+
this.target.set('direction', detectedDirection);
|
|
22818
|
+
|
|
22819
|
+
// Force a re-render to apply the direction change
|
|
22820
|
+
this.canvas.requestRenderAll();
|
|
22821
|
+
console.log(`✅ Fabric object direction updated to: ${detectedDirection}`);
|
|
22822
|
+
} else {
|
|
22823
|
+
console.log(`📝 Overlay Exit: Direction unchanged (${currentDirection}), text: "${finalText.substring(0, 30)}..."`);
|
|
22824
|
+
}
|
|
22373
22825
|
if (this.onCommit) {
|
|
22374
22826
|
this.onCommit(finalText);
|
|
22375
22827
|
}
|
|
@@ -25960,30 +26412,17 @@ class Textbox extends IText {
|
|
|
25960
26412
|
// Detect resize origin during resizing
|
|
25961
26413
|
this.on('resizing', e => {
|
|
25962
26414
|
// Check transform origin to determine which side is being resized
|
|
25963
|
-
console.log('🔍 Resize event data:', e);
|
|
25964
26415
|
if (e.transform) {
|
|
25965
26416
|
const {
|
|
25966
|
-
originX
|
|
25967
|
-
originY
|
|
26417
|
+
originX
|
|
25968
26418
|
} = e.transform;
|
|
25969
|
-
console.log('🔍 Transform origins:', {
|
|
25970
|
-
originX,
|
|
25971
|
-
originY
|
|
25972
|
-
});
|
|
25973
26419
|
// originX tells us which side is the anchor - opposite side is being dragged
|
|
25974
26420
|
resizeOrigin = originX === 'right' ? 'left' : originX === 'left' ? 'right' : null;
|
|
25975
|
-
console.log('🎯 Setting resizeOrigin to:', resizeOrigin);
|
|
25976
26421
|
} else if (e.originX) {
|
|
25977
26422
|
const {
|
|
25978
|
-
originX
|
|
25979
|
-
originY
|
|
26423
|
+
originX
|
|
25980
26424
|
} = e;
|
|
25981
|
-
console.log('🔍 Event origins:', {
|
|
25982
|
-
originX,
|
|
25983
|
-
originY
|
|
25984
|
-
});
|
|
25985
26425
|
resizeOrigin = originX === 'right' ? 'left' : originX === 'left' ? 'right' : null;
|
|
25986
|
-
console.log('🎯 Setting resizeOrigin to:', resizeOrigin);
|
|
25987
26426
|
}
|
|
25988
26427
|
});
|
|
25989
26428
|
|
|
@@ -25991,9 +26430,6 @@ class Textbox extends IText {
|
|
|
25991
26430
|
// Use 'modified' event which fires after user releases the mouse
|
|
25992
26431
|
this.on('modified', () => {
|
|
25993
26432
|
const currentResizeOrigin = resizeOrigin; // Capture the value before reset
|
|
25994
|
-
console.log('✅ Modified event fired - resize complete, triggering safety snap', {
|
|
25995
|
-
resizeOrigin: currentResizeOrigin
|
|
25996
|
-
});
|
|
25997
26433
|
// Small delay to ensure text layout is updated
|
|
25998
26434
|
setTimeout(() => this.safetySnapWidth(currentResizeOrigin), 10);
|
|
25999
26435
|
resizeOrigin = null; // Reset after capturing
|
|
@@ -26003,7 +26439,6 @@ class Textbox extends IText {
|
|
|
26003
26439
|
(_this$canvas = this.canvas) === null || _this$canvas === void 0 || _this$canvas.on('object:modified', e => {
|
|
26004
26440
|
if (e.target === this) {
|
|
26005
26441
|
const currentResizeOrigin = resizeOrigin; // Capture the value before reset
|
|
26006
|
-
console.log('✅ Canvas object:modified fired for this textbox');
|
|
26007
26442
|
setTimeout(() => this.safetySnapWidth(currentResizeOrigin), 10);
|
|
26008
26443
|
resizeOrigin = null; // Reset after capturing
|
|
26009
26444
|
}
|
|
@@ -26018,38 +26453,17 @@ class Textbox extends IText {
|
|
|
26018
26453
|
* @param resizeOrigin - Which side was used for resizing ('left' or 'right')
|
|
26019
26454
|
*/
|
|
26020
26455
|
safetySnapWidth(resizeOrigin) {
|
|
26021
|
-
var _this$_textLines;
|
|
26022
|
-
console.log('🔍 safetySnapWidth called', {
|
|
26023
|
-
isWrapping: this.isWrapping,
|
|
26024
|
-
hasTextLines: !!this._textLines,
|
|
26025
|
-
lineCount: ((_this$_textLines = this._textLines) === null || _this$_textLines === void 0 ? void 0 : _this$_textLines.length) || 0,
|
|
26026
|
-
currentWidth: this.width,
|
|
26027
|
-
type: this.type,
|
|
26028
|
-
text: this.text
|
|
26029
|
-
});
|
|
26030
|
-
|
|
26031
26456
|
// For Textbox objects, we always want to check for clipping regardless of isWrapping flag
|
|
26032
26457
|
if (!this._textLines || this.type.toLowerCase() !== 'textbox' || this._textLines.length === 0) {
|
|
26033
|
-
var _this$_textLines2;
|
|
26034
|
-
console.log('❌ Early return - missing requirements', {
|
|
26035
|
-
hasTextLines: !!this._textLines,
|
|
26036
|
-
typeMatch: this.type.toLowerCase() === 'textbox',
|
|
26037
|
-
actualType: this.type,
|
|
26038
|
-
hasLines: ((_this$_textLines2 = this._textLines) === null || _this$_textLines2 === void 0 ? void 0 : _this$_textLines2.length) > 0
|
|
26039
|
-
});
|
|
26040
26458
|
return;
|
|
26041
26459
|
}
|
|
26042
26460
|
const lineCount = this._textLines.length;
|
|
26043
26461
|
if (lineCount === 0) return;
|
|
26044
|
-
|
|
26045
|
-
// Check all lines, not just the last one
|
|
26046
|
-
let maxActualLineWidth = 0; // Actual measured width without buffers
|
|
26047
26462
|
let maxRequiredWidth = 0; // Width including RTL buffer
|
|
26048
26463
|
|
|
26049
26464
|
for (let i = 0; i < lineCount; i++) {
|
|
26050
26465
|
const lineText = this._textLines[i].join(''); // Convert grapheme array to string
|
|
26051
26466
|
const lineWidth = this.getLineWidth(i);
|
|
26052
|
-
maxActualLineWidth = Math.max(maxActualLineWidth, lineWidth);
|
|
26053
26467
|
|
|
26054
26468
|
// RTL detection - regex for Arabic, Hebrew, and other RTL characters
|
|
26055
26469
|
const rtlRegex = /[\u0590-\u05FF\u0600-\u06FF\u0750-\u077F\uFB50-\uFDFF\uFE70-\uFEFF]/;
|
|
@@ -26069,11 +26483,6 @@ class Textbox extends IText {
|
|
|
26069
26483
|
var _this$canvas2;
|
|
26070
26484
|
// Set width to exactly what's needed + minimal safety margin
|
|
26071
26485
|
const newWidth = maxRequiredWidth + 1; // Add just 1px safety margin
|
|
26072
|
-
console.log(`Safety snap: ${this.width.toFixed(0)}px -> ${newWidth.toFixed(0)}px`, {
|
|
26073
|
-
maxActualLineWidth: maxActualLineWidth.toFixed(1),
|
|
26074
|
-
maxRequiredWidth: maxRequiredWidth.toFixed(1),
|
|
26075
|
-
difference: (newWidth - this.width).toFixed(1)
|
|
26076
|
-
});
|
|
26077
26486
|
|
|
26078
26487
|
// Store original position before width change
|
|
26079
26488
|
const originalLeft = this.left;
|
|
@@ -26089,19 +26498,12 @@ class Textbox extends IText {
|
|
|
26089
26498
|
// Only compensate position when resizing from left handle
|
|
26090
26499
|
// Right handle resize doesn't shift the text position
|
|
26091
26500
|
if (resizeOrigin === 'left') {
|
|
26092
|
-
console.log('🔧 Compensating for left-side resize', {
|
|
26093
|
-
originalLeft,
|
|
26094
|
-
widthIncrease,
|
|
26095
|
-
newLeft: originalLeft - widthIncrease
|
|
26096
|
-
});
|
|
26097
26501
|
// When resizing from left, the expansion pushes text right
|
|
26098
26502
|
// Compensate by moving the textbox left by the width increase
|
|
26099
26503
|
this.set({
|
|
26100
26504
|
'left': originalLeft - widthIncrease,
|
|
26101
26505
|
'top': originalTop
|
|
26102
26506
|
});
|
|
26103
|
-
} else {
|
|
26104
|
-
console.log('✅ Right-side resize, no compensation needed');
|
|
26105
26507
|
}
|
|
26106
26508
|
this.setCoords();
|
|
26107
26509
|
|