@nasser-sw/fabric 7.0.0-beta1 → 7.0.1-beta10
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/0 +0 -0
- package/debug/{konva → konva-master}/CHANGELOG.md +2 -1
- package/debug/{konva → konva-master}/README.md +7 -3
- package/debug/{konva → konva-master}/package.json +1 -1
- package/debug/{konva → konva-master}/release.sh +1 -4
- package/debug/{konva → konva-master}/src/Canvas.ts +37 -0
- package/debug/{konva → konva-master}/src/shapes/Text.ts +2 -2
- package/dist/index.js +2198 -272
- 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 +2198 -272
- package/dist/index.mjs.map +1 -1
- package/dist/index.node.cjs +2198 -272
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.mjs +2198 -272
- 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/Line.d.ts +33 -86
- package/dist/src/shapes/Line.d.ts.map +1 -1
- package/dist/src/shapes/Line.min.mjs +1 -1
- package/dist/src/shapes/Line.min.mjs.map +1 -1
- package/dist/src/shapes/Line.mjs +405 -159
- package/dist/src/shapes/Line.mjs.map +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 +19 -0
- 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 +302 -16
- package/dist/src/shapes/Text/Text.mjs.map +1 -1
- package/dist/src/shapes/Textbox.d.ts +56 -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 +633 -11
- 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/examples/arabicTextExample.d.ts +60 -0
- package/dist/src/text/examples/arabicTextExample.d.ts.map +1 -0
- package/dist/src/text/measure.d.ts +9 -0
- package/dist/src/text/measure.d.ts.map +1 -1
- package/dist/src/text/measure.min.mjs +1 -1
- package/dist/src/text/measure.min.mjs.map +1 -1
- package/dist/src/text/measure.mjs +175 -4
- package/dist/src/text/measure.mjs.map +1 -1
- package/dist/src/text/overlayEditor.d.ts +8 -0
- 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 +395 -56
- package/dist/src/text/overlayEditor.mjs.map +1 -1
- package/dist/src/text/scriptUtils.d.ts +142 -0
- package/dist/src/text/scriptUtils.d.ts.map +1 -0
- package/dist/src/text/scriptUtils.min.mjs +2 -0
- package/dist/src/text/scriptUtils.min.mjs.map +1 -0
- package/dist/src/text/scriptUtils.mjs +212 -0
- package/dist/src/text/scriptUtils.mjs.map +1 -0
- 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/CustomLine.d.ts +10 -0
- package/dist-extensions/src/shapes/CustomLine.d.ts.map +1 -0
- package/dist-extensions/src/shapes/Line.d.ts +33 -86
- package/dist-extensions/src/shapes/Line.d.ts.map +1 -1
- 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 +19 -0
- package/dist-extensions/src/shapes/Text/Text.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Textbox.d.ts +56 -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/measure.d.ts +9 -0
- package/dist-extensions/src/text/measure.d.ts.map +1 -1
- package/dist-extensions/src/text/overlayEditor.d.ts +8 -0
- package/dist-extensions/src/text/overlayEditor.d.ts.map +1 -1
- package/dist-extensions/src/text/scriptUtils.d.ts +142 -0
- package/dist-extensions/src/text/scriptUtils.d.ts.map +1 -0
- 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 +3552 -0
- package/fabric-test2.html +647 -0
- package/fabric.ts +182 -182
- package/fonts/STV Bold.ttf +0 -0
- package/fonts/STV Light.ttf +0 -0
- package/fonts/STV Regular.ttf +0 -0
- package/package.json +164 -164
- package/src/shapes/Line.ts +484 -157
- package/src/shapes/Polyline.ts +70 -29
- package/src/shapes/Text/Text.ts +317 -19
- package/src/shapes/Textbox.ts +663 -12
- package/src/shapes/Triangle.spec.ts +76 -0
- package/src/shapes/Triangle.ts +85 -15
- package/src/text/measure.ts +200 -50
- package/src/text/overlayEditor.ts +504 -94
- package/src/util/misc/cornerRadius.spec.ts +141 -0
- package/src/util/misc/cornerRadius.ts +269 -0
- /package/debug/{konva → konva-master}/LICENSE +0 -0
- /package/debug/{konva → konva-master}/gulpfile.mjs +0 -0
- /package/debug/{konva → konva-master}/resources/doc-includes/ContainerParams.txt +0 -0
- /package/debug/{konva → konva-master}/resources/doc-includes/NodeParams.txt +0 -0
- /package/debug/{konva → konva-master}/resources/doc-includes/ShapeParams.txt +0 -0
- /package/debug/{konva → konva-master}/resources/jsdoc.conf.json +0 -0
- /package/debug/{konva → konva-master}/rollup.config.mjs +0 -0
- /package/debug/{konva → konva-master}/src/Animation.ts +0 -0
- /package/debug/{konva → konva-master}/src/BezierFunctions.ts +0 -0
- /package/debug/{konva → konva-master}/src/Container.ts +0 -0
- /package/debug/{konva → konva-master}/src/Context.ts +0 -0
- /package/debug/{konva → konva-master}/src/Core.ts +0 -0
- /package/debug/{konva → konva-master}/src/DragAndDrop.ts +0 -0
- /package/debug/{konva → konva-master}/src/Factory.ts +0 -0
- /package/debug/{konva → konva-master}/src/FastLayer.ts +0 -0
- /package/debug/{konva → konva-master}/src/Global.ts +0 -0
- /package/debug/{konva → konva-master}/src/Group.ts +0 -0
- /package/debug/{konva → konva-master}/src/Layer.ts +0 -0
- /package/debug/{konva → konva-master}/src/Node.ts +0 -0
- /package/debug/{konva → konva-master}/src/PointerEvents.ts +0 -0
- /package/debug/{konva → konva-master}/src/Shape.ts +0 -0
- /package/debug/{konva → konva-master}/src/Stage.ts +0 -0
- /package/debug/{konva → konva-master}/src/Tween.ts +0 -0
- /package/debug/{konva → konva-master}/src/Util.ts +0 -0
- /package/debug/{konva → konva-master}/src/Validators.ts +0 -0
- /package/debug/{konva → konva-master}/src/_CoreInternals.ts +0 -0
- /package/debug/{konva → konva-master}/src/_FullInternals.ts +0 -0
- /package/debug/{konva → konva-master}/src/canvas-backend.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Blur.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Brighten.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Brightness.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Contrast.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Emboss.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Enhance.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Grayscale.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/HSL.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/HSV.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Invert.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Kaleidoscope.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Mask.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Noise.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Pixelate.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Posterize.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/RGB.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/RGBA.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Sepia.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Solarize.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Threshold.ts +0 -0
- /package/debug/{konva → konva-master}/src/index.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Arc.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Arrow.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Circle.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Ellipse.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Image.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Label.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Line.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Path.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Rect.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/RegularPolygon.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Ring.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Sprite.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Star.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/TextPath.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Transformer.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Wedge.ts +0 -0
- /package/debug/{konva → konva-master}/src/skia-backend.ts +0 -0
- /package/debug/{konva → konva-master}/src/types.ts +0 -0
- /package/debug/{konva → konva-master}/tsconfig.json +0 -0
- /package/debug/{konva → konva-master}/tsconfig.test.json +0 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import {
|
|
2
|
+
pointDistance,
|
|
3
|
+
normalizeVector,
|
|
4
|
+
angleBetweenVectors,
|
|
5
|
+
getMaxRadius,
|
|
6
|
+
calculateRoundedCorner,
|
|
7
|
+
applyCornerRadiusToPolygon,
|
|
8
|
+
generateRoundedPolygonPath,
|
|
9
|
+
} from './cornerRadius';
|
|
10
|
+
|
|
11
|
+
describe('cornerRadius utilities', () => {
|
|
12
|
+
describe('pointDistance', () => {
|
|
13
|
+
it('should calculate distance between two points correctly', () => {
|
|
14
|
+
expect(pointDistance({ x: 0, y: 0 }, { x: 3, y: 4 })).toBe(5);
|
|
15
|
+
expect(pointDistance({ x: 1, y: 1 }, { x: 1, y: 1 })).toBe(0);
|
|
16
|
+
expect(pointDistance({ x: 0, y: 0 }, { x: 1, y: 0 })).toBe(1);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('normalizeVector', () => {
|
|
21
|
+
it('should normalize vectors correctly', () => {
|
|
22
|
+
const result = normalizeVector({ x: 3, y: 4 });
|
|
23
|
+
expect(result.x).toBeCloseTo(0.6);
|
|
24
|
+
expect(result.y).toBeCloseTo(0.8);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should handle zero vector', () => {
|
|
28
|
+
const result = normalizeVector({ x: 0, y: 0 });
|
|
29
|
+
expect(result).toEqual({ x: 0, y: 0 });
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe('getMaxRadius', () => {
|
|
34
|
+
it('should return half of the shortest adjacent edge', () => {
|
|
35
|
+
const prevPoint = { x: 0, y: 0 };
|
|
36
|
+
const currentPoint = { x: 10, y: 0 };
|
|
37
|
+
const nextPoint = { x: 10, y: 5 };
|
|
38
|
+
|
|
39
|
+
expect(getMaxRadius(prevPoint, currentPoint, nextPoint)).toBe(2.5);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('calculateRoundedCorner', () => {
|
|
44
|
+
it('should calculate rounded corner data correctly', () => {
|
|
45
|
+
const prevPoint = { x: 0, y: 0 };
|
|
46
|
+
const currentPoint = { x: 10, y: 0 };
|
|
47
|
+
const nextPoint = { x: 10, y: 10 };
|
|
48
|
+
const radius = 2;
|
|
49
|
+
|
|
50
|
+
const result = calculateRoundedCorner(prevPoint, currentPoint, nextPoint, radius);
|
|
51
|
+
|
|
52
|
+
expect(result.corner).toEqual(currentPoint);
|
|
53
|
+
expect(result.actualRadius).toBe(radius);
|
|
54
|
+
expect(result.start.x).toBeCloseTo(8);
|
|
55
|
+
expect(result.start.y).toBeCloseTo(0);
|
|
56
|
+
expect(result.end.x).toBeCloseTo(10);
|
|
57
|
+
expect(result.end.y).toBeCloseTo(2);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should constrain radius to maximum allowed', () => {
|
|
61
|
+
const prevPoint = { x: 0, y: 0 };
|
|
62
|
+
const currentPoint = { x: 2, y: 0 };
|
|
63
|
+
const nextPoint = { x: 2, y: 2 };
|
|
64
|
+
const radius = 5; // Request larger radius than possible
|
|
65
|
+
|
|
66
|
+
const result = calculateRoundedCorner(prevPoint, currentPoint, nextPoint, radius);
|
|
67
|
+
|
|
68
|
+
expect(result.actualRadius).toBe(1); // Should be constrained to 1
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('applyCornerRadiusToPolygon', () => {
|
|
73
|
+
it('should apply corner radius to a simple triangle', () => {
|
|
74
|
+
const points = [
|
|
75
|
+
{ x: 0, y: 0 },
|
|
76
|
+
{ x: 10, y: 0 },
|
|
77
|
+
{ x: 5, y: 10 }
|
|
78
|
+
];
|
|
79
|
+
const radius = 2;
|
|
80
|
+
|
|
81
|
+
const result = applyCornerRadiusToPolygon(points, radius);
|
|
82
|
+
|
|
83
|
+
expect(result).toHaveLength(3);
|
|
84
|
+
expect(result[0].actualRadius).toBeCloseTo(2);
|
|
85
|
+
expect(result[1].actualRadius).toBeCloseTo(2);
|
|
86
|
+
expect(result[2].actualRadius).toBeCloseTo(2);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should handle percentage-based radius', () => {
|
|
90
|
+
const points = [
|
|
91
|
+
{ x: 0, y: 0 },
|
|
92
|
+
{ x: 100, y: 0 },
|
|
93
|
+
{ x: 100, y: 100 },
|
|
94
|
+
{ x: 0, y: 100 }
|
|
95
|
+
];
|
|
96
|
+
const radius = 10; // 10%
|
|
97
|
+
|
|
98
|
+
const result = applyCornerRadiusToPolygon(points, radius, true);
|
|
99
|
+
|
|
100
|
+
expect(result).toHaveLength(4);
|
|
101
|
+
expect(result[0].actualRadius).toBeCloseTo(10); // 10% of 100px = 10px
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should throw error for polygons with less than 3 points', () => {
|
|
105
|
+
expect(() => {
|
|
106
|
+
applyCornerRadiusToPolygon([{ x: 0, y: 0 }, { x: 1, y: 1 }], 5);
|
|
107
|
+
}).toThrow('Polygon must have at least 3 points');
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('generateRoundedPolygonPath', () => {
|
|
112
|
+
it('should generate valid SVG path data', () => {
|
|
113
|
+
const points = [
|
|
114
|
+
{ x: 0, y: 0 },
|
|
115
|
+
{ x: 10, y: 0 },
|
|
116
|
+
{ x: 5, y: 10 }
|
|
117
|
+
];
|
|
118
|
+
const roundedCorners = applyCornerRadiusToPolygon(points, 2);
|
|
119
|
+
|
|
120
|
+
const pathData = generateRoundedPolygonPath(roundedCorners, true);
|
|
121
|
+
|
|
122
|
+
expect(pathData).toContain('M '); // Should start with move command
|
|
123
|
+
expect(pathData).toContain('C '); // Should contain bezier curve commands
|
|
124
|
+
expect(pathData).toContain('L '); // Should contain line commands
|
|
125
|
+
expect(pathData).toContain('Z'); // Should end with close command for closed path
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should generate open path when closed=false', () => {
|
|
129
|
+
const points = [
|
|
130
|
+
{ x: 0, y: 0 },
|
|
131
|
+
{ x: 10, y: 0 },
|
|
132
|
+
{ x: 5, y: 10 }
|
|
133
|
+
];
|
|
134
|
+
const roundedCorners = applyCornerRadiusToPolygon(points, 2);
|
|
135
|
+
|
|
136
|
+
const pathData = generateRoundedPolygonPath(roundedCorners, false);
|
|
137
|
+
|
|
138
|
+
expect(pathData).not.toContain('Z'); // Should not end with close command
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
});
|
|
@@ -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
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|