@leafer/path 1.0.0-alpha.1
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/LICENSE +21 -0
- package/README.md +1 -0
- package/package.json +25 -0
- package/src/BezierHelper.ts +157 -0
- package/src/PathCommandMap.ts +107 -0
- package/src/PathConvert.ts +241 -0
- package/src/PathCreator.ts +64 -0
- package/src/PathHelper.ts +113 -0
- package/src/index.ts +5 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023-present, Chao (Leafer) Wan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# @leafer/path
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@leafer/path",
|
|
3
|
+
"version": "1.0.0-alpha.1",
|
|
4
|
+
"description": "@leafer/path",
|
|
5
|
+
"author": "Chao (Leafer) Wan",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "src/index.ts",
|
|
8
|
+
"files": ["src"],
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/leaferjs/leafer.git"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/leaferjs/leafer/tree/main/packages/path",
|
|
14
|
+
"bugs": "https://github.com/leaferjs/leafer/issues",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"leafer",
|
|
17
|
+
"leaferjs"
|
|
18
|
+
],
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@leafer/math": "1.0.0-alpha.1"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@leafer/interface": "1.0.0-alpha.1"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { IPointData, ITwoPointBoundsData, IPathCommandData } from '@leafer/interface'
|
|
2
|
+
import { TwoPointBoundsHelper } from '@leafer/math'
|
|
3
|
+
|
|
4
|
+
import { PathCommandMap } from './PathCommandMap'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
const tempPoint = {} as IPointData
|
|
8
|
+
const { sin, cos, sqrt, atan2, ceil, abs, PI } = Math
|
|
9
|
+
const { setPoint, addPoint } = TwoPointBoundsHelper
|
|
10
|
+
|
|
11
|
+
export const BezierHelper = {
|
|
12
|
+
|
|
13
|
+
getFromACommand(fromX: number, fromY: number, rx: number, ry: number, rotateAngle: number, largeFlag: number, sweepFlag: number, toX: number, toY: number): IPathCommandData { // [...CCommandData, ...CCommandData]
|
|
14
|
+
|
|
15
|
+
const localToX = toX - fromX
|
|
16
|
+
const localToY = toY - fromY
|
|
17
|
+
|
|
18
|
+
const rotation = rotateAngle * PI / 180
|
|
19
|
+
const sinRotation = sin(rotation)
|
|
20
|
+
const cosRotation = cos(rotation)
|
|
21
|
+
|
|
22
|
+
const ax = -cosRotation * localToX * 0.5 - sinRotation * localToY * 0.5
|
|
23
|
+
const ay = -cosRotation * localToY * 0.5 + sinRotation * localToX * 0.5
|
|
24
|
+
const rxSquare = rx * rx
|
|
25
|
+
const rySquare = ry * ry
|
|
26
|
+
const pySquare = ay * ay
|
|
27
|
+
const pxSquare = ax * ax
|
|
28
|
+
const a = rxSquare * rySquare - rxSquare * pySquare - rySquare * pxSquare
|
|
29
|
+
let sr = 0
|
|
30
|
+
|
|
31
|
+
if (a < 0) {
|
|
32
|
+
const scale = sqrt(1 - a / (rxSquare * rySquare))
|
|
33
|
+
rx *= scale
|
|
34
|
+
ry *= scale
|
|
35
|
+
} else {
|
|
36
|
+
const sign = largeFlag === sweepFlag ? -1 : 1
|
|
37
|
+
sr = sign * sqrt(a / (rxSquare * pySquare + rySquare * pxSquare))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const cx = sr * rx * ay / ry
|
|
41
|
+
const cy = -sr * ry * ax / rx
|
|
42
|
+
const cx1 = cosRotation * cx - sinRotation * cy + localToX * 0.5
|
|
43
|
+
const cy1 = sinRotation * cx + cosRotation * cy + localToY * 0.5
|
|
44
|
+
|
|
45
|
+
let r1 = atan2((ay - cy) / ry, (ax - cx) / rx)
|
|
46
|
+
let r2 = atan2((-ay - cy) / ry, (-ax - cx) / rx)
|
|
47
|
+
let totalRadian = r2 - r1
|
|
48
|
+
|
|
49
|
+
if (sweepFlag === 0 && totalRadian > 0) {
|
|
50
|
+
totalRadian -= 2 * PI
|
|
51
|
+
} else if (sweepFlag === 1 && totalRadian < 0) {
|
|
52
|
+
totalRadian += 2 * PI
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// segments arc
|
|
56
|
+
const data: IPathCommandData = []
|
|
57
|
+
const segments = ceil(abs(totalRadian / PI * 2))
|
|
58
|
+
const segmentRadian = totalRadian / segments
|
|
59
|
+
const sinSegmentRadian2 = sin(segmentRadian / 2)
|
|
60
|
+
const sinSegmentRadian4 = sin(segmentRadian / 4)
|
|
61
|
+
const controlRadian = 8 / 3 * sinSegmentRadian4 * sinSegmentRadian4 / sinSegmentRadian2
|
|
62
|
+
|
|
63
|
+
r1 = 0
|
|
64
|
+
r2 = atan2((ay - cy) / ry, (ax - cx) / rx)
|
|
65
|
+
let startRadian = r2 - r1
|
|
66
|
+
let endRadian = startRadian + segmentRadian
|
|
67
|
+
let cosStart = cos(startRadian)
|
|
68
|
+
let sinStart = sin(startRadian)
|
|
69
|
+
let cosEnd: number, sinEnd: number
|
|
70
|
+
let startX = 0, startY = 0
|
|
71
|
+
|
|
72
|
+
let x: number, y: number, x1: number, y1: number, x2: number, y2: number
|
|
73
|
+
for (let i = 0; i < segments; i++) {
|
|
74
|
+
|
|
75
|
+
cosEnd = cos(endRadian)
|
|
76
|
+
sinEnd = sin(endRadian)
|
|
77
|
+
|
|
78
|
+
// segment bezier
|
|
79
|
+
x = cosRotation * rx * cosEnd - sinRotation * ry * sinEnd + cx1
|
|
80
|
+
y = sinRotation * rx * cosEnd + cosRotation * ry * sinEnd + cy1
|
|
81
|
+
x1 = startX + controlRadian * (-cosRotation * rx * sinStart - sinRotation * ry * cosStart)
|
|
82
|
+
y1 = startY + controlRadian * (-sinRotation * rx * sinStart + cosRotation * ry * cosStart)
|
|
83
|
+
x2 = x + controlRadian * (cosRotation * rx * sinEnd + sinRotation * ry * cosEnd)
|
|
84
|
+
y2 = y + controlRadian * (sinRotation * rx * sinEnd - cosRotation * ry * cosEnd)
|
|
85
|
+
|
|
86
|
+
data.push(PathCommandMap.C, x1 + fromX, y1 + fromY, x2 + fromX, y2 + fromY, x + fromX, y + fromY)
|
|
87
|
+
|
|
88
|
+
startX = x
|
|
89
|
+
startY = y
|
|
90
|
+
startRadian = endRadian
|
|
91
|
+
cosStart = cosEnd
|
|
92
|
+
sinStart = sinEnd
|
|
93
|
+
endRadian += segmentRadian
|
|
94
|
+
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return data
|
|
98
|
+
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
toTwoPointBounds(fromX: number, fromY: number, x1: number, y1: number, x2: number, y2: number, toX: number, toY: number, pointBounds: ITwoPointBoundsData): void {
|
|
102
|
+
|
|
103
|
+
const tList = []
|
|
104
|
+
let a, b, c, t, t1, t2, v, sqrtV
|
|
105
|
+
let f = fromX, z1 = x1, z2 = x2, o = toX
|
|
106
|
+
|
|
107
|
+
for (let i = 0; i < 2; ++i) {
|
|
108
|
+
|
|
109
|
+
if (i == 1) {
|
|
110
|
+
f = fromY, z1 = y1, z2 = y2, o = toY
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
a = -3 * f + 9 * z1 - 9 * z2 + 3 * o
|
|
114
|
+
b = 6 * f - 12 * z1 + 6 * z2
|
|
115
|
+
c = 3 * z1 - 3 * f
|
|
116
|
+
|
|
117
|
+
if (Math.abs(a) < 1e-12) {
|
|
118
|
+
if (Math.abs(b) < 1e-12) continue
|
|
119
|
+
t = -c / b
|
|
120
|
+
if (0 < t && t < 1) tList.push(t)
|
|
121
|
+
continue
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
v = b * b - 4 * c * a
|
|
125
|
+
sqrtV = Math.sqrt(v)
|
|
126
|
+
if (v < 0) continue
|
|
127
|
+
|
|
128
|
+
t1 = (-b + sqrtV) / (2 * a)
|
|
129
|
+
if (0 < t1 && t1 < 1) tList.push(t1)
|
|
130
|
+
t2 = (-b - sqrtV) / (2 * a)
|
|
131
|
+
if (0 < t2 && t2 < 1) tList.push(t2)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
setPoint(pointBounds, fromX, fromY)
|
|
135
|
+
addPoint(pointBounds, toX, toY)
|
|
136
|
+
|
|
137
|
+
for (let i = 0, len = tList.length; i < len; i++) {
|
|
138
|
+
B.getPointAndSet(tList[i], fromX, fromY, x1, y1, x2, y2, toX, toY, tempPoint)
|
|
139
|
+
addPoint(pointBounds, tempPoint.x, tempPoint.y)
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
getPointAndSet(t: number, fromX: number, fromY: number, x1: number, y1: number, x2: number, y2: number, toX: number, toY: number, setPoint: IPointData): void {
|
|
144
|
+
const o = 1 - t, a = o * o * o, b = 3 * o * o * t, c = 3 * o * t * t, d = t * t * t
|
|
145
|
+
setPoint.x = a * fromX + b * x1 + c * x2 + d * toX
|
|
146
|
+
setPoint.y = a * fromY + b * y1 + c * y2 + d * toY
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
getPoint(t: number, fromX: number, fromY: number, x1: number, y1: number, x2: number, y2: number, toX: number, toY: number): IPointData {
|
|
150
|
+
const point = {} as IPointData
|
|
151
|
+
B.getPointAndSet(t, fromX, fromY, x1, y1, x2, y2, toX, toY, point)
|
|
152
|
+
return point
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const B = BezierHelper
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { INumberMap, IStringMap } from '@leafer/interface'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export const PathCommandMap: INumberMap = {
|
|
5
|
+
M: 1, //moveto
|
|
6
|
+
m: 10,
|
|
7
|
+
L: 2, //lineto
|
|
8
|
+
l: 20,
|
|
9
|
+
H: 3, //horizontal lineto
|
|
10
|
+
h: 30,
|
|
11
|
+
V: 4, //vertical lineto
|
|
12
|
+
v: 40,
|
|
13
|
+
C: 5, //curveto
|
|
14
|
+
c: 50,
|
|
15
|
+
S: 6, //smooth curveto
|
|
16
|
+
s: 60,
|
|
17
|
+
Q: 7, //quadratic Belzier curve
|
|
18
|
+
q: 70,
|
|
19
|
+
T: 8, //smooth quadratic Belzier curveto
|
|
20
|
+
t: 80,
|
|
21
|
+
A: 9, //elliptical Arc
|
|
22
|
+
a: 90,
|
|
23
|
+
Z: 11, //closepath
|
|
24
|
+
z: 11,
|
|
25
|
+
|
|
26
|
+
// 非svg标准的canvas绘图命令
|
|
27
|
+
rect: 100,
|
|
28
|
+
roundRect: 101,
|
|
29
|
+
ellipse: 102,
|
|
30
|
+
arc: 103,
|
|
31
|
+
arcTo: 104
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const PathCommandLengthMap: INumberMap = {
|
|
35
|
+
M: 3, //moveto
|
|
36
|
+
m: 3,
|
|
37
|
+
L: 3, //lineto
|
|
38
|
+
l: 3,
|
|
39
|
+
H: 2, //horizontal lineto
|
|
40
|
+
h: 2,
|
|
41
|
+
V: 2, //vertical lineto
|
|
42
|
+
v: 2,
|
|
43
|
+
C: 7, //curveto
|
|
44
|
+
c: 7,
|
|
45
|
+
S: 5, //smooth curveto
|
|
46
|
+
s: 5,
|
|
47
|
+
Q: 5, //quadratic Belzier curve
|
|
48
|
+
q: 5,
|
|
49
|
+
T: 3, //smooth quadratic Belzier curveto
|
|
50
|
+
t: 3,
|
|
51
|
+
A: 8, //elliptical Arc
|
|
52
|
+
a: 8,
|
|
53
|
+
Z: 1, //closepath
|
|
54
|
+
z: 1,
|
|
55
|
+
|
|
56
|
+
// 非svg标准的canvas绘图命令
|
|
57
|
+
rect: 5,
|
|
58
|
+
roundRect: 6,
|
|
59
|
+
ellipse: 9,
|
|
60
|
+
arc: 7,
|
|
61
|
+
arcTo: 6
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const PathCommandNeedConvertMap: INumberMap = { // convert to: M L C Q Z
|
|
65
|
+
// M: 1, //moveto
|
|
66
|
+
m: 10,
|
|
67
|
+
// L: 2, //lineto
|
|
68
|
+
l: 20,
|
|
69
|
+
H: 3, //horizontal lineto
|
|
70
|
+
h: 30,
|
|
71
|
+
V: 4, //vertical lineto
|
|
72
|
+
v: 40,
|
|
73
|
+
// C: 5, //curveto
|
|
74
|
+
c: 50,
|
|
75
|
+
S: 6, //smooth curveto
|
|
76
|
+
s: 60,
|
|
77
|
+
// Q: 7, //quadratic Belzier curve
|
|
78
|
+
q: 70,
|
|
79
|
+
T: 8, //smooth quadratic Belzier curveto
|
|
80
|
+
t: 80,
|
|
81
|
+
A: 9, //elliptical Arc
|
|
82
|
+
a: 90,
|
|
83
|
+
// Z: 11, //closepath
|
|
84
|
+
// z: 11
|
|
85
|
+
|
|
86
|
+
// 非svg标准的canvas绘图命令
|
|
87
|
+
rect: 100,
|
|
88
|
+
roundRect: 101,
|
|
89
|
+
ellipse: 102,
|
|
90
|
+
arc: 103,
|
|
91
|
+
arcTo: 104
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
const P = PathCommandMap
|
|
96
|
+
|
|
97
|
+
export const NumberPathCommandMap: IStringMap = {}
|
|
98
|
+
for (let key in P) {
|
|
99
|
+
NumberPathCommandMap[P[key]] = key
|
|
100
|
+
}
|
|
101
|
+
// {1: 'M'}
|
|
102
|
+
|
|
103
|
+
export const NumberPathCommandLengthMap: INumberMap = {}
|
|
104
|
+
for (let key in P) {
|
|
105
|
+
NumberPathCommandLengthMap[P[key]] = PathCommandLengthMap[key]
|
|
106
|
+
}
|
|
107
|
+
// {1: 3}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { IPathCommandData } from '@leafer/interface'
|
|
2
|
+
import { StringNumberMap } from '@leafer/math'
|
|
3
|
+
import { PathCommandMap as Command, PathCommandNeedConvertMap as NeedConvertCommand, PathCommandLengthMap as CommandLength, NumberPathCommandMap as CommandName, NumberPathCommandLengthMap as NumberCommandLength } from './PathCommandMap'
|
|
4
|
+
|
|
5
|
+
import { BezierHelper } from './BezierHelper'
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
interface ICurrentCommand {
|
|
9
|
+
name?: number
|
|
10
|
+
length?: number
|
|
11
|
+
index?: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
const { M, m, L, l, H, h, V, v, C, c, S, s, Q, q, T, t, A, a, Z, z } = Command
|
|
16
|
+
const { getFromACommand } = BezierHelper
|
|
17
|
+
|
|
18
|
+
export const PathConvert = {
|
|
19
|
+
|
|
20
|
+
current: {} as ICurrentCommand,
|
|
21
|
+
|
|
22
|
+
stringify(data: IPathCommandData): string {
|
|
23
|
+
let i = 0, len = data.length, count: number, str: string = '', command: number, lastCommand: number
|
|
24
|
+
while (i < len) {
|
|
25
|
+
command = data[i]
|
|
26
|
+
count = NumberCommandLength[command]
|
|
27
|
+
if (command === lastCommand) {
|
|
28
|
+
str += ' ' // 重复的命令可以省略
|
|
29
|
+
} else {
|
|
30
|
+
str += CommandName[command]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
for (let j = 1; j < count; j++) {
|
|
34
|
+
str += data[i + j];
|
|
35
|
+
(j === count - 1) || (str += ' ')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
lastCommand = command
|
|
39
|
+
i += count
|
|
40
|
+
}
|
|
41
|
+
return str
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
parse(pathString: string, convert: boolean = true): IPathCommandData {
|
|
45
|
+
|
|
46
|
+
let needConvert: boolean, char: string, num = ''
|
|
47
|
+
const data: IPathCommandData = []
|
|
48
|
+
|
|
49
|
+
for (let i = 0, len = pathString.length; i < len; i++) {
|
|
50
|
+
|
|
51
|
+
char = pathString[i]
|
|
52
|
+
|
|
53
|
+
if (StringNumberMap[char]) {
|
|
54
|
+
|
|
55
|
+
num += char
|
|
56
|
+
|
|
57
|
+
} else if (Command[char]) {
|
|
58
|
+
|
|
59
|
+
if (num) { pushData(data, Number(num)); num = '' }
|
|
60
|
+
|
|
61
|
+
current.name = Command[char]
|
|
62
|
+
current.length = CommandLength[char]
|
|
63
|
+
current.index = 0
|
|
64
|
+
pushData(data, current.name)
|
|
65
|
+
|
|
66
|
+
if (!needConvert && NeedConvertCommand[char]) needConvert = true
|
|
67
|
+
|
|
68
|
+
} else {
|
|
69
|
+
|
|
70
|
+
if (char === '-') { // L-34-35
|
|
71
|
+
if (num) { pushData(data, Number(num)) }
|
|
72
|
+
num = char
|
|
73
|
+
} else {
|
|
74
|
+
if (num) { pushData(data, Number(num)); num = '' }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (num) pushData(data, Number(num))
|
|
82
|
+
|
|
83
|
+
//console.log(pathString, P._data)
|
|
84
|
+
return (convert && needConvert) ? PathConvert.convertToSimple(data) : data
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
convertToSimple(old: IPathCommandData): IPathCommandData {
|
|
88
|
+
|
|
89
|
+
let x = 0, y = 0, x1 = 0, y1 = 0, i = 0, len = old.length, controlX: number, controlY: number, command: number, lastCommand: number, smooth: boolean
|
|
90
|
+
const data: IPathCommandData = []
|
|
91
|
+
|
|
92
|
+
while (i < len) {
|
|
93
|
+
|
|
94
|
+
command = old[i]
|
|
95
|
+
|
|
96
|
+
switch (command) {
|
|
97
|
+
//moveto x,y
|
|
98
|
+
case m:
|
|
99
|
+
old[i + 1] += x
|
|
100
|
+
old[i + 2] += y
|
|
101
|
+
case M:
|
|
102
|
+
x = old[i + 1]
|
|
103
|
+
y = old[i + 2]
|
|
104
|
+
data.push(M, x, y)
|
|
105
|
+
i += 3
|
|
106
|
+
break
|
|
107
|
+
|
|
108
|
+
//horizontal lineto x
|
|
109
|
+
case h:
|
|
110
|
+
old[i + 1] += x
|
|
111
|
+
case H:
|
|
112
|
+
x = old[i + 1]
|
|
113
|
+
data.push(L, x, y)
|
|
114
|
+
i += 2
|
|
115
|
+
break
|
|
116
|
+
|
|
117
|
+
//vertical lineto y
|
|
118
|
+
case v:
|
|
119
|
+
old[i + 1] += y
|
|
120
|
+
case V:
|
|
121
|
+
y = old[i + 1]
|
|
122
|
+
data.push(L, x, y)
|
|
123
|
+
i += 2
|
|
124
|
+
break
|
|
125
|
+
|
|
126
|
+
//lineto x,y
|
|
127
|
+
case l:
|
|
128
|
+
old[i + 1] += x
|
|
129
|
+
old[i + 2] += y
|
|
130
|
+
case L:
|
|
131
|
+
x = old[i + 1]
|
|
132
|
+
y = old[i + 2]
|
|
133
|
+
data.push(L, x, y)
|
|
134
|
+
i += 3
|
|
135
|
+
break
|
|
136
|
+
|
|
137
|
+
//smooth bezierCurveTo x2,y2,x,y
|
|
138
|
+
case s: //smooth
|
|
139
|
+
old[i + 1] += x
|
|
140
|
+
old[i + 2] += y
|
|
141
|
+
old[i + 3] += x
|
|
142
|
+
old[i + 4] += y
|
|
143
|
+
command = S
|
|
144
|
+
case S:
|
|
145
|
+
smooth = (lastCommand === C) || (lastCommand === S)
|
|
146
|
+
x1 = smooth ? (x * 2 - controlX) : old[i + 1]
|
|
147
|
+
y1 = smooth ? (y * 2 - controlY) : old[i + 2]
|
|
148
|
+
controlX = old[i + 1]
|
|
149
|
+
controlY = old[i + 2]
|
|
150
|
+
x = old[i + 3]
|
|
151
|
+
y = old[i + 4]
|
|
152
|
+
data.push(C, x1, y1, controlX, controlY, x, y)
|
|
153
|
+
i += 5
|
|
154
|
+
break
|
|
155
|
+
|
|
156
|
+
//bezierCurveTo x1,y1,x2,y2,x,y
|
|
157
|
+
case c:
|
|
158
|
+
old[i + 1] += x
|
|
159
|
+
old[i + 2] += y
|
|
160
|
+
old[i + 3] += x
|
|
161
|
+
old[i + 4] += y
|
|
162
|
+
old[i + 5] += x
|
|
163
|
+
old[i + 6] += y
|
|
164
|
+
command = C
|
|
165
|
+
case C:
|
|
166
|
+
controlX = old[i + 3]
|
|
167
|
+
controlY = old[i + 4]
|
|
168
|
+
x = old[i + 5]
|
|
169
|
+
y = old[i + 6]
|
|
170
|
+
data.push(C, old[i + 1], old[i + 2], controlX, controlY, x, y)
|
|
171
|
+
i += 7
|
|
172
|
+
break
|
|
173
|
+
|
|
174
|
+
//smooth quadraticCurveTo x,y
|
|
175
|
+
case t:
|
|
176
|
+
old[i + 1] += x
|
|
177
|
+
old[i + 2] += y
|
|
178
|
+
command = T
|
|
179
|
+
case T: //smooth
|
|
180
|
+
smooth = (lastCommand === Q) || (lastCommand === T)
|
|
181
|
+
controlX = smooth ? (x * 2 - controlX) : old[i + 1]
|
|
182
|
+
controlY = smooth ? (y * 2 - controlY) : old[i + 2]
|
|
183
|
+
x = old[i + 1]
|
|
184
|
+
y = old[i + 2]
|
|
185
|
+
data.push(Q, controlX, controlY, x, y)
|
|
186
|
+
i += 3
|
|
187
|
+
break
|
|
188
|
+
|
|
189
|
+
//quadraticCurveTo x1,y1,x,y
|
|
190
|
+
case q:
|
|
191
|
+
old[i + 1] += x
|
|
192
|
+
old[i + 2] += y
|
|
193
|
+
old[i + 3] += x
|
|
194
|
+
old[i + 4] += y
|
|
195
|
+
command = Q
|
|
196
|
+
case Q:
|
|
197
|
+
controlX = old[i + 1]
|
|
198
|
+
controlY = old[i + 2]
|
|
199
|
+
x = old[i + 3]
|
|
200
|
+
y = old[i + 4]
|
|
201
|
+
data.push(Q, controlX, controlY, x, y)
|
|
202
|
+
i += 5
|
|
203
|
+
break
|
|
204
|
+
|
|
205
|
+
//ellipticalArc rx ry x-axis-rotation large-arc-flag sweep-flag x y
|
|
206
|
+
case a:
|
|
207
|
+
old[i + 6] += x
|
|
208
|
+
old[i + 7] += y
|
|
209
|
+
case A:
|
|
210
|
+
data.push(...getFromACommand(x, y, old[i + 1], old[i + 2], old[i + 3], old[i + 4], old[i + 5], old[i + 6], old[i + 7])) // convert bezier
|
|
211
|
+
x = old[i + 6]
|
|
212
|
+
y = old[i + 7]
|
|
213
|
+
i += 8
|
|
214
|
+
break
|
|
215
|
+
case z:
|
|
216
|
+
case Z:
|
|
217
|
+
data.push(Z)
|
|
218
|
+
i++
|
|
219
|
+
break
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
lastCommand = command
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return data
|
|
226
|
+
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
pushData(data: IPathCommandData, num: number) {
|
|
230
|
+
if (current.index === current.length) { // 单个命令,多个数据的情况
|
|
231
|
+
current.index = 1
|
|
232
|
+
data.push(current.name)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
data.push(num)
|
|
236
|
+
current.index++
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const { current, pushData } = PathConvert
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { IPathCommandData } from '@leafer/interface'
|
|
2
|
+
import { PathCommandMap } from './PathCommandMap'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
let data: IPathCommandData
|
|
6
|
+
const { M, L, C, Q, Z, rect, roundRect, ellipse, arc, arcTo } = PathCommandMap
|
|
7
|
+
|
|
8
|
+
export const PathCreator = {
|
|
9
|
+
|
|
10
|
+
begin(commandData: IPathCommandData): void {
|
|
11
|
+
data = commandData
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
end(): void {
|
|
15
|
+
data = undefined
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
// draw
|
|
19
|
+
|
|
20
|
+
moveTo(x: number, y: number): void {
|
|
21
|
+
data.push(M, x, y)
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
lineTo(x: number, y: number): void {
|
|
25
|
+
data.push(L, x, y)
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
bezierCurveTo(x1: number, y1: number, x2: number, y2: number, x: number, y: number): void {
|
|
29
|
+
data.push(C, x1, y1, x2, y2, x, y)
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
quadraticCurveTo(x1: number, y1: number, x: number, y: number): void {
|
|
33
|
+
data.push(Q, x1, y1, x, y)
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
close(end?: boolean): void {
|
|
37
|
+
data.push(Z)
|
|
38
|
+
if (end) data = undefined
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
// 非svg标准的canvas绘图命令
|
|
43
|
+
|
|
44
|
+
rect(x: number, y: number, width: number, height: number): void {
|
|
45
|
+
data.push(rect, x, y, width, height)
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
roundRect(x: number, y: number, width: number, height: number, cornerRadius?: number | number[]): void {
|
|
49
|
+
data.push(roundRect, x, y, width, height, cornerRadius as unknown as number)
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
ellipse(x: number, y: number, radiusX: number, radiusY: number, rotation: number, startAngle: number, endAngle: number, counterclockwise?: boolean): void {
|
|
53
|
+
data.push(ellipse, x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise as unknown as number)
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
arc(x: number, y: number, radius: number, startAngle: number, endAngle: number, counterclockwise?: boolean): void {
|
|
57
|
+
data.push(arc, x, y, radius, startAngle, endAngle, counterclockwise as unknown as number)
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): void {
|
|
61
|
+
data.push(arcTo, x1, y1, x2, y2, radius)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { ICanvasDrawPath, ITwoPointBoundsData, IPathCommandData } from '@leafer/interface'
|
|
2
|
+
import { TwoPointBoundsHelper } from '@leafer/math'
|
|
3
|
+
|
|
4
|
+
import { BezierHelper } from './BezierHelper'
|
|
5
|
+
import { PathCommandMap as Command } from './PathCommandMap'
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
const { M, L, C, Q, Z, ellipse: E } = Command
|
|
9
|
+
const { toTwoPointBounds } = BezierHelper
|
|
10
|
+
const { add, addPoint, setPoint } = TwoPointBoundsHelper
|
|
11
|
+
|
|
12
|
+
const tempPointBounds = {} as ITwoPointBoundsData
|
|
13
|
+
|
|
14
|
+
export const PathHelper = {
|
|
15
|
+
|
|
16
|
+
applyCorner(data: IPathCommandData, cornerRadius: number, cornerSmoothing?: number): IPathCommandData {
|
|
17
|
+
return data
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
toTwoPointBounds(data: IPathCommandData, setPointBounds: ITwoPointBoundsData): void {
|
|
21
|
+
|
|
22
|
+
let command: number
|
|
23
|
+
let i: number = 0, x: number, y: number, x1: number, y1: number, toX: number, toY: number
|
|
24
|
+
|
|
25
|
+
const len = data.length
|
|
26
|
+
|
|
27
|
+
while (i < len) {
|
|
28
|
+
command = data[i]
|
|
29
|
+
|
|
30
|
+
if (i === 0) {
|
|
31
|
+
(command === M) ? setPoint(setPointBounds, data[1], data[2]) : setPoint(setPointBounds, 0, 0)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
switch (command) {
|
|
35
|
+
case M: //moveto x,y
|
|
36
|
+
case L: //lineto x,y
|
|
37
|
+
x = data[i + 1]
|
|
38
|
+
y = data[i + 2]
|
|
39
|
+
addPoint(setPointBounds, x, y)
|
|
40
|
+
i += 3
|
|
41
|
+
break
|
|
42
|
+
case C: //bezierCurveTo x1,y1,x2,y2,x,y
|
|
43
|
+
toX = data[i + 5]
|
|
44
|
+
toY = data[i + 6]
|
|
45
|
+
toTwoPointBounds(x, y, data[i + 1], data[i + 2], data[i + 3], data[i + 4], toX, toY, tempPointBounds)
|
|
46
|
+
add(setPointBounds, tempPointBounds)
|
|
47
|
+
x = toX
|
|
48
|
+
y = toY
|
|
49
|
+
i += 7
|
|
50
|
+
break
|
|
51
|
+
case Q: //quadraticCurveTo x1,y1,x,y
|
|
52
|
+
x1 = data[i + 1]
|
|
53
|
+
y1 = data[i + 2]
|
|
54
|
+
toX = data[i + 3]
|
|
55
|
+
toY = data[i + 4]
|
|
56
|
+
toTwoPointBounds(x, y, x1, y1, x1, y1, toX, toY, tempPointBounds)
|
|
57
|
+
add(setPointBounds, tempPointBounds)
|
|
58
|
+
x = toX
|
|
59
|
+
y = toY
|
|
60
|
+
i += 5
|
|
61
|
+
break
|
|
62
|
+
case Z: //closepath
|
|
63
|
+
i += 1
|
|
64
|
+
break
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 增加1px的扩展,否则会有问题
|
|
69
|
+
setPointBounds.minX--
|
|
70
|
+
setPointBounds.minY--
|
|
71
|
+
setPointBounds.maxX++
|
|
72
|
+
setPointBounds.maxY++
|
|
73
|
+
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
drawData(drawer: ICanvasDrawPath, data: IPathCommandData): void {
|
|
77
|
+
let command: number
|
|
78
|
+
let i = 0, len = data.length
|
|
79
|
+
|
|
80
|
+
while (i < len) {
|
|
81
|
+
command = data[i]
|
|
82
|
+
switch (command) {
|
|
83
|
+
case M: //moveto x,y
|
|
84
|
+
drawer.moveTo(data[i + 1], data[i + 2])
|
|
85
|
+
i += 3
|
|
86
|
+
break
|
|
87
|
+
case L: //lineto x,y
|
|
88
|
+
drawer.lineTo(data[i + 1], data[i + 2])
|
|
89
|
+
i += 3
|
|
90
|
+
break
|
|
91
|
+
case C: //bezierCurveTo x1,y1,x2,y2,x,y
|
|
92
|
+
drawer.bezierCurveTo(data[i + 1], data[i + 2], data[i + 3], data[i + 4], data[i + 5], data[i + 6])
|
|
93
|
+
i += 7
|
|
94
|
+
break
|
|
95
|
+
case Q: //quadraticCurveTo x1,y1,x,y
|
|
96
|
+
drawer.quadraticCurveTo(data[i + 1], data[i + 2], data[i + 3], data[i + 4])
|
|
97
|
+
i += 5
|
|
98
|
+
break
|
|
99
|
+
case Z: //closepath
|
|
100
|
+
drawer.closePath()
|
|
101
|
+
i += 1
|
|
102
|
+
break
|
|
103
|
+
|
|
104
|
+
// 非svg标准的canvas绘图命令
|
|
105
|
+
case E:
|
|
106
|
+
drawer.ellipse(data[i + 1], data[i + 2], data[i + 3], data[i + 4], data[i + 5], data[i + 6], data[i + 7], data[i + 8] as unknown as boolean)
|
|
107
|
+
i += 9
|
|
108
|
+
break
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { PathConvert } from './PathConvert'
|
|
2
|
+
export { PathHelper } from './PathHelper'
|
|
3
|
+
export { PathCreator } from './PathCreator'
|
|
4
|
+
export { BezierHelper } from './BezierHelper'
|
|
5
|
+
export { PathCommandMap, PathCommandNeedConvertMap, NumberPathCommandMap, NumberPathCommandLengthMap } from './PathCommandMap'
|