@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
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import type { XY } from '../../Point';
|
|
2
|
+
import { Point } from '../../Point';
|
|
3
|
+
import { kRect } from '../../constants';
|
|
4
|
+
|
|
5
|
+
export interface CornerRadiusOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Corner radius value
|
|
8
|
+
*/
|
|
9
|
+
radius: number;
|
|
10
|
+
/**
|
|
11
|
+
* Whether to apply radius as percentage of the smallest dimension
|
|
12
|
+
*/
|
|
13
|
+
radiusAsPercentage?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface RoundedCornerPoint {
|
|
17
|
+
/**
|
|
18
|
+
* Original corner point
|
|
19
|
+
*/
|
|
20
|
+
corner: XY;
|
|
21
|
+
/**
|
|
22
|
+
* Start point of the rounded corner arc
|
|
23
|
+
*/
|
|
24
|
+
start: XY;
|
|
25
|
+
/**
|
|
26
|
+
* End point of the rounded corner arc
|
|
27
|
+
*/
|
|
28
|
+
end: XY;
|
|
29
|
+
/**
|
|
30
|
+
* First control point for bezier curve
|
|
31
|
+
*/
|
|
32
|
+
cp1: XY;
|
|
33
|
+
/**
|
|
34
|
+
* Second control point for bezier curve
|
|
35
|
+
*/
|
|
36
|
+
cp2: XY;
|
|
37
|
+
/**
|
|
38
|
+
* Actual radius used (may be different from requested if constrained)
|
|
39
|
+
*/
|
|
40
|
+
actualRadius: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Calculate the distance between two points
|
|
45
|
+
*/
|
|
46
|
+
export function pointDistance(p1: XY, p2: XY): number {
|
|
47
|
+
return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Normalize a vector
|
|
52
|
+
*/
|
|
53
|
+
export function normalizeVector(vector: XY): XY {
|
|
54
|
+
const length = Math.sqrt(vector.x * vector.x + vector.y * vector.y);
|
|
55
|
+
if (length === 0) return { x: 0, y: 0 };
|
|
56
|
+
return { x: vector.x / length, y: vector.y / length };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Calculate the angle between two vectors
|
|
61
|
+
*/
|
|
62
|
+
export function angleBetweenVectors(v1: XY, v2: XY): number {
|
|
63
|
+
const dot = v1.x * v2.x + v1.y * v2.y;
|
|
64
|
+
const det = v1.x * v2.y - v1.y * v2.x;
|
|
65
|
+
return Math.atan2(det, dot);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get the maximum allowed radius for a corner based on adjacent edge lengths
|
|
70
|
+
*/
|
|
71
|
+
export function getMaxRadius(
|
|
72
|
+
prevPoint: XY,
|
|
73
|
+
currentPoint: XY,
|
|
74
|
+
nextPoint: XY,
|
|
75
|
+
): number {
|
|
76
|
+
const dist1 = pointDistance(prevPoint, currentPoint);
|
|
77
|
+
const dist2 = pointDistance(currentPoint, nextPoint);
|
|
78
|
+
return Math.min(dist1, dist2) / 2;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Calculate rounded corner data for a single corner
|
|
83
|
+
*/
|
|
84
|
+
export function calculateRoundedCorner(
|
|
85
|
+
prevPoint: XY,
|
|
86
|
+
currentPoint: XY,
|
|
87
|
+
nextPoint: XY,
|
|
88
|
+
radius: number,
|
|
89
|
+
): RoundedCornerPoint {
|
|
90
|
+
// Calculate edge vectors
|
|
91
|
+
const edge1 = {
|
|
92
|
+
x: currentPoint.x - prevPoint.x,
|
|
93
|
+
y: currentPoint.y - prevPoint.y,
|
|
94
|
+
};
|
|
95
|
+
const edge2 = {
|
|
96
|
+
x: nextPoint.x - currentPoint.x,
|
|
97
|
+
y: nextPoint.y - currentPoint.y,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// Normalize edge vectors
|
|
101
|
+
const norm1 = normalizeVector(edge1);
|
|
102
|
+
const norm2 = normalizeVector(edge2);
|
|
103
|
+
|
|
104
|
+
// Calculate the maximum allowed radius
|
|
105
|
+
const maxRadius = getMaxRadius(prevPoint, currentPoint, nextPoint);
|
|
106
|
+
const actualRadius = Math.min(radius, maxRadius);
|
|
107
|
+
|
|
108
|
+
// Calculate start and end points of the rounded corner
|
|
109
|
+
const startPoint = {
|
|
110
|
+
x: currentPoint.x - norm1.x * actualRadius,
|
|
111
|
+
y: currentPoint.y - norm1.y * actualRadius,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const endPoint = {
|
|
115
|
+
x: currentPoint.x + norm2.x * actualRadius,
|
|
116
|
+
y: currentPoint.y + norm2.y * actualRadius,
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// Calculate control points for bezier curve
|
|
120
|
+
// Using the magic number kRect for optimal circular approximation
|
|
121
|
+
const controlOffset = actualRadius * kRect;
|
|
122
|
+
|
|
123
|
+
const cp1 = {
|
|
124
|
+
x: startPoint.x + norm1.x * controlOffset,
|
|
125
|
+
y: startPoint.y + norm1.y * controlOffset,
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const cp2 = {
|
|
129
|
+
x: endPoint.x - norm2.x * controlOffset,
|
|
130
|
+
y: endPoint.y - norm2.y * controlOffset,
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
corner: currentPoint,
|
|
135
|
+
start: startPoint,
|
|
136
|
+
end: endPoint,
|
|
137
|
+
cp1,
|
|
138
|
+
cp2,
|
|
139
|
+
actualRadius,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Apply corner radius to a polygon defined by points
|
|
145
|
+
*/
|
|
146
|
+
export function applyCornerRadiusToPolygon(
|
|
147
|
+
points: XY[],
|
|
148
|
+
radius: number,
|
|
149
|
+
radiusAsPercentage = false,
|
|
150
|
+
): RoundedCornerPoint[] {
|
|
151
|
+
if (points.length < 3) {
|
|
152
|
+
throw new Error('Polygon must have at least 3 points');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Calculate bounding box if radius is percentage-based
|
|
156
|
+
let actualRadius = radius;
|
|
157
|
+
if (radiusAsPercentage) {
|
|
158
|
+
const minX = Math.min(...points.map((p) => p.x));
|
|
159
|
+
const maxX = Math.max(...points.map((p) => p.x));
|
|
160
|
+
const minY = Math.min(...points.map((p) => p.y));
|
|
161
|
+
const maxY = Math.max(...points.map((p) => p.y));
|
|
162
|
+
const width = maxX - minX;
|
|
163
|
+
const height = maxY - minY;
|
|
164
|
+
const minDimension = Math.min(width, height);
|
|
165
|
+
actualRadius = (radius / 100) * minDimension;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const roundedCorners: RoundedCornerPoint[] = [];
|
|
169
|
+
|
|
170
|
+
for (let i = 0; i < points.length; i++) {
|
|
171
|
+
const prevIndex = (i - 1 + points.length) % points.length;
|
|
172
|
+
const nextIndex = (i + 1) % points.length;
|
|
173
|
+
|
|
174
|
+
const prevPoint = points[prevIndex];
|
|
175
|
+
const currentPoint = points[i];
|
|
176
|
+
const nextPoint = points[nextIndex];
|
|
177
|
+
|
|
178
|
+
const roundedCorner = calculateRoundedCorner(
|
|
179
|
+
prevPoint,
|
|
180
|
+
currentPoint,
|
|
181
|
+
nextPoint,
|
|
182
|
+
actualRadius,
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
roundedCorners.push(roundedCorner);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return roundedCorners;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Render a rounded polygon to a canvas context
|
|
193
|
+
*/
|
|
194
|
+
export function renderRoundedPolygon(
|
|
195
|
+
ctx: CanvasRenderingContext2D,
|
|
196
|
+
roundedCorners: RoundedCornerPoint[],
|
|
197
|
+
closed = true,
|
|
198
|
+
) {
|
|
199
|
+
if (roundedCorners.length === 0) return;
|
|
200
|
+
|
|
201
|
+
ctx.beginPath();
|
|
202
|
+
|
|
203
|
+
// Start at the first corner's start point
|
|
204
|
+
const firstCorner = roundedCorners[0];
|
|
205
|
+
ctx.moveTo(firstCorner.start.x, firstCorner.start.y);
|
|
206
|
+
|
|
207
|
+
for (let i = 0; i < roundedCorners.length; i++) {
|
|
208
|
+
const corner = roundedCorners[i];
|
|
209
|
+
const nextIndex = (i + 1) % roundedCorners.length;
|
|
210
|
+
const nextCorner = roundedCorners[nextIndex];
|
|
211
|
+
|
|
212
|
+
// Draw the rounded corner using bezier curve
|
|
213
|
+
ctx.bezierCurveTo(
|
|
214
|
+
corner.cp1.x,
|
|
215
|
+
corner.cp1.y,
|
|
216
|
+
corner.cp2.x,
|
|
217
|
+
corner.cp2.y,
|
|
218
|
+
corner.end.x,
|
|
219
|
+
corner.end.y,
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
// Draw line to next corner's start point (if not the last segment in open path)
|
|
223
|
+
if (i < roundedCorners.length - 1 || closed) {
|
|
224
|
+
ctx.lineTo(nextCorner.start.x, nextCorner.start.y);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (closed) {
|
|
229
|
+
ctx.closePath();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Generate SVG path data for a rounded polygon
|
|
235
|
+
*/
|
|
236
|
+
export function generateRoundedPolygonPath(
|
|
237
|
+
roundedCorners: RoundedCornerPoint[],
|
|
238
|
+
closed = true,
|
|
239
|
+
): string {
|
|
240
|
+
if (roundedCorners.length === 0) return '';
|
|
241
|
+
|
|
242
|
+
const pathData: string[] = [];
|
|
243
|
+
const firstCorner = roundedCorners[0];
|
|
244
|
+
|
|
245
|
+
// Move to first corner's start point
|
|
246
|
+
pathData.push(`M ${firstCorner.start.x} ${firstCorner.start.y}`);
|
|
247
|
+
|
|
248
|
+
for (let i = 0; i < roundedCorners.length; i++) {
|
|
249
|
+
const corner = roundedCorners[i];
|
|
250
|
+
const nextIndex = (i + 1) % roundedCorners.length;
|
|
251
|
+
const nextCorner = roundedCorners[nextIndex];
|
|
252
|
+
|
|
253
|
+
// Add bezier curve for the rounded corner
|
|
254
|
+
pathData.push(
|
|
255
|
+
`C ${corner.cp1.x} ${corner.cp1.y} ${corner.cp2.x} ${corner.cp2.y} ${corner.end.x} ${corner.end.y}`,
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
// Add line to next corner's start point (if not the last segment in open path)
|
|
259
|
+
if (i < roundedCorners.length - 1 || closed) {
|
|
260
|
+
pathData.push(`L ${nextCorner.start.x} ${nextCorner.start.y}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (closed) {
|
|
265
|
+
pathData.push('Z');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return pathData.join(' ');
|
|
269
|
+
}
|