@leafer/path 1.0.0-beta.15 → 1.0.0-beta.16
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/package.json +5 -4
- package/src/BezierHelper.ts +256 -0
- package/src/EllipseHelper.ts +70 -0
- package/src/PathBounds.ts +138 -0
- package/src/PathCommandDataHelper.ts +105 -0
- package/src/PathCommandMap.ts +126 -0
- package/src/PathConvert.ts +325 -0
- package/src/PathCorner.ts +10 -0
- package/src/PathCreator.ts +96 -0
- package/src/PathDrawer.ts +85 -0
- package/src/PathHelper.ts +8 -0
- package/src/RectHelper.ts +22 -0
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leafer/path",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.16",
|
|
4
4
|
"description": "@leafer/path",
|
|
5
5
|
"author": "Chao (Leafer) Wan",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"main": "src/index.ts",
|
|
8
8
|
"types": "types/index.d.ts",
|
|
9
9
|
"files": [
|
|
10
|
+
"src",
|
|
10
11
|
"types",
|
|
11
12
|
"dist"
|
|
12
13
|
],
|
|
@@ -21,10 +22,10 @@
|
|
|
21
22
|
"leaferjs"
|
|
22
23
|
],
|
|
23
24
|
"dependencies": {
|
|
24
|
-
"@leafer/math": "1.0.0-beta.
|
|
25
|
-
"@leafer/debug": "1.0.0-beta.
|
|
25
|
+
"@leafer/math": "1.0.0-beta.16",
|
|
26
|
+
"@leafer/debug": "1.0.0-beta.16"
|
|
26
27
|
},
|
|
27
28
|
"devDependencies": {
|
|
28
|
-
"@leafer/interface": "1.0.0-beta.
|
|
29
|
+
"@leafer/interface": "1.0.0-beta.16"
|
|
29
30
|
}
|
|
30
31
|
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { IPointData, ITwoPointBoundsData, IPathCommandData } from '@leafer/interface'
|
|
2
|
+
import { OneRadian, PI2, PI_2, PointHelper, TwoPointBoundsHelper } from '@leafer/math'
|
|
3
|
+
|
|
4
|
+
import { PathCommandMap } from './PathCommandMap'
|
|
5
|
+
import { RectHelper } from './RectHelper'
|
|
6
|
+
import { PathHelper } from './PathHelper'
|
|
7
|
+
|
|
8
|
+
const { sin, cos, atan2, ceil, abs, PI, sqrt, pow } = Math
|
|
9
|
+
const { setPoint, addPoint } = TwoPointBoundsHelper
|
|
10
|
+
const { set } = PointHelper
|
|
11
|
+
const { M, L, C, Q, Z } = PathCommandMap
|
|
12
|
+
const tempPoint = {} as IPointData
|
|
13
|
+
|
|
14
|
+
export const BezierHelper = {
|
|
15
|
+
|
|
16
|
+
points(data: IPathCommandData, points: number[], curve?: boolean | number, close?: boolean): void {
|
|
17
|
+
data.push(M, points[0], points[1])
|
|
18
|
+
|
|
19
|
+
if (curve && points.length > 5) {
|
|
20
|
+
|
|
21
|
+
let aX: number, aY: number, bX: number, bY: number, cX: number, cY: number, c1X: number, c1Y: number, c2X: number, c2Y: number
|
|
22
|
+
let ba: number, cb: number, d: number, len = points.length
|
|
23
|
+
const t = curve === true ? 0.5 : curve as number
|
|
24
|
+
|
|
25
|
+
if (close) {
|
|
26
|
+
points = [points[len - 2], points[len - 1], ...points, points[0], points[1], points[2], points[3]]
|
|
27
|
+
len = points.length
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
for (let i = 2; i < len - 2; i += 2) {
|
|
31
|
+
aX = points[i - 2]
|
|
32
|
+
aY = points[i - 1]
|
|
33
|
+
|
|
34
|
+
bX = points[i]
|
|
35
|
+
bY = points[i + 1]
|
|
36
|
+
|
|
37
|
+
cX = points[i + 2]
|
|
38
|
+
cY = points[i + 3]
|
|
39
|
+
|
|
40
|
+
ba = sqrt(pow(bX - aX, 2) + pow(bY - aY, 2))
|
|
41
|
+
cb = sqrt(pow(cX - bX, 2) + pow(cY - bY, 2))
|
|
42
|
+
|
|
43
|
+
d = ba + cb
|
|
44
|
+
ba = (t * ba) / d
|
|
45
|
+
cb = (t * cb) / d
|
|
46
|
+
|
|
47
|
+
cX -= aX
|
|
48
|
+
cY -= aY
|
|
49
|
+
|
|
50
|
+
c1X = bX - ba * cX
|
|
51
|
+
c1Y = bY - ba * cY
|
|
52
|
+
|
|
53
|
+
if (i === 2) {
|
|
54
|
+
if (!close) data.push(Q, c1X, c1Y, bX, bY)
|
|
55
|
+
} else {
|
|
56
|
+
data.push(C, c2X, c2Y, c1X, c1Y, bX, bY)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
c2X = bX + cb * cX
|
|
60
|
+
c2Y = bY + cb * cY
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!close) data.push(Q, c2X, c2Y, points[len - 2], points[len - 1])
|
|
64
|
+
|
|
65
|
+
} else {
|
|
66
|
+
|
|
67
|
+
for (let i = 2, len = points.length; i < len; i += 2) {
|
|
68
|
+
data.push(L, points[i], points[i + 1])
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (close) data.push(Z)
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
rect(data: IPathCommandData, x: number, y: number, width: number, height: number) {
|
|
77
|
+
PathHelper.creator.path = data
|
|
78
|
+
PathHelper.creator.moveTo(x, y).lineTo(x + width, y).lineTo(x + width, y + height).lineTo(x, y + height).lineTo(x, y)
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
roundRect(data: IPathCommandData, x: number, y: number, width: number, height: number, radius: number | number[]): void {
|
|
82
|
+
PathHelper.creator.path = []
|
|
83
|
+
RectHelper.drawRoundRect(PathHelper.creator, x, y, width, height, radius)
|
|
84
|
+
data.push(...PathHelper.convertToCanvasData(PathHelper.creator.path, true))
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
arcTo(data: IPathCommandData | null | void, fromX: number, fromY: number, x1: number, y1: number, toX: number, toY: number, radius: number, setPointBounds?: ITwoPointBoundsData, setEndPoint?: IPointData, setStartPoint?: IPointData): void {
|
|
88
|
+
const BAx = x1 - fromX
|
|
89
|
+
const BAy = y1 - fromY
|
|
90
|
+
const CBx = toX - x1
|
|
91
|
+
const CBy = toY - y1
|
|
92
|
+
|
|
93
|
+
let startRadian = atan2(BAy, BAx)
|
|
94
|
+
let endRadian = atan2(CBy, CBx)
|
|
95
|
+
let totalRadian = endRadian - startRadian
|
|
96
|
+
if (totalRadian < 0) totalRadian += PI2
|
|
97
|
+
|
|
98
|
+
if (totalRadian === PI || (abs(BAx + BAy) < 1.e-12) || (abs(CBx + CBy) < 1.e-12)) { // invalid
|
|
99
|
+
if (data) data.push(L, x1, y1)
|
|
100
|
+
if (setPointBounds) {
|
|
101
|
+
setPoint(setPointBounds, fromX, fromY)
|
|
102
|
+
addPoint(setPointBounds, x1, y1)
|
|
103
|
+
}
|
|
104
|
+
if (setStartPoint) set(setStartPoint, fromX, fromY)
|
|
105
|
+
if (setEndPoint) set(setEndPoint, x1, y1)
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const anticlockwise = BAx * CBy - CBx * BAy < 0
|
|
110
|
+
const sign = anticlockwise ? -1 : 1
|
|
111
|
+
const c = radius / cos(totalRadian / 2)
|
|
112
|
+
|
|
113
|
+
const centerX = x1 + c * cos(startRadian + totalRadian / 2 + PI_2 * sign)
|
|
114
|
+
const centerY = y1 + c * sin(startRadian + totalRadian / 2 + PI_2 * sign)
|
|
115
|
+
startRadian -= PI_2 * sign
|
|
116
|
+
endRadian -= PI_2 * sign
|
|
117
|
+
|
|
118
|
+
return ellipse(data, centerX, centerY, radius, radius, 0, startRadian / OneRadian, endRadian / OneRadian, anticlockwise, setPointBounds, setEndPoint, setStartPoint)
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
arc(data: IPathCommandData | null | void, x: number, y: number, radius: number, startAngle: number, endAngle: number, anticlockwise?: boolean, setPointBounds?: ITwoPointBoundsData, setEndPoint?: IPointData, setStartPoint?: IPointData): void {
|
|
122
|
+
return ellipse(data, x, y, radius, radius, 0, startAngle, endAngle, anticlockwise, setPointBounds, setEndPoint, setStartPoint)
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
ellipse(data: IPathCommandData | null | void, cx: number, cy: number, radiusX: number, radiusY: number, rotation: number, startAngle: number, endAngle: number, anticlockwise?: boolean, setPointBounds?: ITwoPointBoundsData, setEndPoint?: IPointData, setStartPoint?: IPointData): void {
|
|
126
|
+
const rotationRadian = rotation * OneRadian
|
|
127
|
+
const rotationSin = sin(rotationRadian)
|
|
128
|
+
const rotationCos = cos(rotationRadian)
|
|
129
|
+
|
|
130
|
+
let startRadian = startAngle * OneRadian
|
|
131
|
+
let endRadian = endAngle * OneRadian
|
|
132
|
+
|
|
133
|
+
if (startRadian > PI) startRadian -= PI2
|
|
134
|
+
if (endRadian < 0) endRadian += PI2
|
|
135
|
+
|
|
136
|
+
let totalRadian = endRadian - startRadian
|
|
137
|
+
if (totalRadian < 0) totalRadian += PI2
|
|
138
|
+
else if (totalRadian > PI2) totalRadian -= PI2
|
|
139
|
+
|
|
140
|
+
if (anticlockwise) totalRadian -= PI2
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
const parts = ceil(abs(totalRadian / PI_2))
|
|
144
|
+
const partRadian = totalRadian / parts
|
|
145
|
+
const partRadian4Sin = sin(partRadian / 4)
|
|
146
|
+
const control = 8 / 3 * partRadian4Sin * partRadian4Sin / sin(partRadian / 2)
|
|
147
|
+
|
|
148
|
+
endRadian = startRadian + partRadian
|
|
149
|
+
|
|
150
|
+
let startCos = cos(startRadian)
|
|
151
|
+
let startSin = sin(startRadian)
|
|
152
|
+
let endCos: number, endSin: number
|
|
153
|
+
|
|
154
|
+
let x: number, y: number, x1: number, y1: number, x2: number, y2: number
|
|
155
|
+
|
|
156
|
+
let startX = x = rotationCos * radiusX * startCos - rotationSin * radiusY * startSin
|
|
157
|
+
let startY = y = rotationSin * radiusX * startCos + rotationCos * radiusY * startSin
|
|
158
|
+
|
|
159
|
+
let fromX = cx + x, fromY = cy + y
|
|
160
|
+
|
|
161
|
+
if (data) data.push(L, fromX, fromY)
|
|
162
|
+
if (setPointBounds) setPoint(setPointBounds, fromX, fromY)
|
|
163
|
+
if (setStartPoint) set(setStartPoint, fromX, fromY)
|
|
164
|
+
|
|
165
|
+
for (let i = 0; i < parts; i++) {
|
|
166
|
+
|
|
167
|
+
endCos = cos(endRadian)
|
|
168
|
+
endSin = sin(endRadian)
|
|
169
|
+
|
|
170
|
+
x = rotationCos * radiusX * endCos - rotationSin * radiusY * endSin
|
|
171
|
+
y = rotationSin * radiusX * endCos + rotationCos * radiusY * endSin
|
|
172
|
+
x1 = cx + startX - control * (rotationCos * radiusX * startSin + rotationSin * radiusY * startCos)
|
|
173
|
+
y1 = cy + startY - control * (rotationSin * radiusX * startSin - rotationCos * radiusY * startCos)
|
|
174
|
+
x2 = cx + x + control * (rotationCos * radiusX * endSin + rotationSin * radiusY * endCos)
|
|
175
|
+
y2 = cy + y + control * (rotationSin * radiusX * endSin - rotationCos * radiusY * endCos)
|
|
176
|
+
|
|
177
|
+
if (data) data.push(C, x1, y1, x2, y2, cx + x, cy + y)
|
|
178
|
+
if (setPointBounds) toTwoPointBounds(cx + startX, cy + startY, x1, y1, x2, y2, cx + x, cy + y, setPointBounds, true)
|
|
179
|
+
|
|
180
|
+
startX = x
|
|
181
|
+
startY = y
|
|
182
|
+
startCos = endCos
|
|
183
|
+
startSin = endSin
|
|
184
|
+
startRadian = endRadian
|
|
185
|
+
endRadian += partRadian
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (setEndPoint) set(setEndPoint, cx + x, cy + y)
|
|
189
|
+
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
quadraticCurveTo(data: IPathCommandData, fromX: number, fromY: number, x1: number, y1: number, toX: number, toY: number): void {
|
|
193
|
+
data.push(C, (fromX + 2 * x1) / 3, (fromY + 2 * y1) / 3, (toX + 2 * x1) / 3, (toY + 2 * y1) / 3, toX, toY)
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
toTwoPointBoundsByQuadraticCurve(fromX: number, fromY: number, x1: number, y1: number, toX: number, toY: number, pointBounds: ITwoPointBoundsData, addMode?: boolean): void {
|
|
197
|
+
toTwoPointBounds(fromX, fromY, (fromX + 2 * x1) / 3, (fromY + 2 * y1) / 3, (toX + 2 * x1) / 3, (toY + 2 * y1) / 3, toX, toY, pointBounds, addMode)
|
|
198
|
+
},
|
|
199
|
+
|
|
200
|
+
toTwoPointBounds(fromX: number, fromY: number, x1: number, y1: number, x2: number, y2: number, toX: number, toY: number, pointBounds: ITwoPointBoundsData, addMode?: boolean): void {
|
|
201
|
+
|
|
202
|
+
const tList = []
|
|
203
|
+
let a, b, c, t, t1, t2, v, sqrtV
|
|
204
|
+
let f = fromX, z1 = x1, z2 = x2, o = toX
|
|
205
|
+
|
|
206
|
+
for (let i = 0; i < 2; ++i) {
|
|
207
|
+
|
|
208
|
+
if (i == 1) {
|
|
209
|
+
f = fromY, z1 = y1, z2 = y2, o = toY
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
a = -3 * f + 9 * z1 - 9 * z2 + 3 * o
|
|
213
|
+
b = 6 * f - 12 * z1 + 6 * z2
|
|
214
|
+
c = 3 * z1 - 3 * f
|
|
215
|
+
|
|
216
|
+
if (Math.abs(a) < 1e-12) {
|
|
217
|
+
if (Math.abs(b) < 1e-12) continue
|
|
218
|
+
t = -c / b
|
|
219
|
+
if (0 < t && t < 1) tList.push(t)
|
|
220
|
+
continue
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
v = b * b - 4 * c * a
|
|
224
|
+
sqrtV = Math.sqrt(v)
|
|
225
|
+
if (v < 0) continue
|
|
226
|
+
|
|
227
|
+
t1 = (-b + sqrtV) / (2 * a)
|
|
228
|
+
if (0 < t1 && t1 < 1) tList.push(t1)
|
|
229
|
+
t2 = (-b - sqrtV) / (2 * a)
|
|
230
|
+
if (0 < t2 && t2 < 1) tList.push(t2)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
addMode ? addPoint(pointBounds, fromX, fromY) : setPoint(pointBounds, fromX, fromY)
|
|
234
|
+
addPoint(pointBounds, toX, toY)
|
|
235
|
+
|
|
236
|
+
for (let i = 0, len = tList.length; i < len; i++) {
|
|
237
|
+
getPointAndSet(tList[i], fromX, fromY, x1, y1, x2, y2, toX, toY, tempPoint)
|
|
238
|
+
addPoint(pointBounds, tempPoint.x, tempPoint.y)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
},
|
|
242
|
+
|
|
243
|
+
getPointAndSet(t: number, fromX: number, fromY: number, x1: number, y1: number, x2: number, y2: number, toX: number, toY: number, setPoint: IPointData): void {
|
|
244
|
+
const o = 1 - t, a = o * o * o, b = 3 * o * o * t, c = 3 * o * t * t, d = t * t * t
|
|
245
|
+
setPoint.x = a * fromX + b * x1 + c * x2 + d * toX
|
|
246
|
+
setPoint.y = a * fromY + b * y1 + c * y2 + d * toY
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
getPoint(t: number, fromX: number, fromY: number, x1: number, y1: number, x2: number, y2: number, toX: number, toY: number): IPointData {
|
|
250
|
+
const point = {} as IPointData
|
|
251
|
+
getPointAndSet(t, fromX, fromY, x1, y1, x2, y2, toX, toY, point)
|
|
252
|
+
return point
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const { getPointAndSet, toTwoPointBounds, ellipse } = BezierHelper
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { IPathCommandData } from '@leafer/interface'
|
|
2
|
+
import { OneRadian, PI2 } from '@leafer/math'
|
|
3
|
+
|
|
4
|
+
import { PathCommandMap } from './PathCommandMap'
|
|
5
|
+
import { BezierHelper } from './BezierHelper'
|
|
6
|
+
|
|
7
|
+
const { sin, cos, sqrt, atan2 } = Math
|
|
8
|
+
const { ellipse } = BezierHelper
|
|
9
|
+
|
|
10
|
+
export const EllipseHelper = {
|
|
11
|
+
|
|
12
|
+
// svg
|
|
13
|
+
ellipticalArc(data: IPathCommandData, fromX: number, fromY: number, radiusX: number, radiusY: number, rotation: number, largeFlag: number, sweepFlag: number, toX: number, toY: number, curveMode?: boolean): void {
|
|
14
|
+
|
|
15
|
+
const halfX = (toX - fromX) / 2
|
|
16
|
+
const halfY = (toY - fromY) / 2
|
|
17
|
+
|
|
18
|
+
const rotationRadian = rotation * OneRadian
|
|
19
|
+
const rotationSin = sin(rotationRadian)
|
|
20
|
+
const rotationCos = cos(rotationRadian)
|
|
21
|
+
|
|
22
|
+
const px = -rotationCos * halfX - rotationSin * halfY
|
|
23
|
+
const py = -rotationCos * halfY + rotationSin * halfX
|
|
24
|
+
const rxSquare = radiusX * radiusX
|
|
25
|
+
const rySquare = radiusY * radiusY
|
|
26
|
+
const pySquare = py * py
|
|
27
|
+
const pxSquare = px * px
|
|
28
|
+
|
|
29
|
+
const a = rxSquare * rySquare - rxSquare * pySquare - rySquare * pxSquare
|
|
30
|
+
let s = 0
|
|
31
|
+
|
|
32
|
+
if (a < 0) {
|
|
33
|
+
const t = sqrt(1 - a / (rxSquare * rySquare))
|
|
34
|
+
radiusX *= t
|
|
35
|
+
radiusY *= t
|
|
36
|
+
} else {
|
|
37
|
+
s = (largeFlag === sweepFlag ? -1 : 1) * sqrt(a / (rxSquare * pySquare + rySquare * pxSquare))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const cx = s * radiusX * py / radiusY
|
|
41
|
+
const cy = -s * radiusY * px / radiusX
|
|
42
|
+
|
|
43
|
+
const startRadian = atan2((py - cy) / radiusY, (px - cx) / radiusX)
|
|
44
|
+
const endRadian = atan2((-py - cy) / radiusY, (-px - cx) / radiusX)
|
|
45
|
+
|
|
46
|
+
let totalRadian = endRadian - startRadian
|
|
47
|
+
|
|
48
|
+
if (sweepFlag === 0 && totalRadian > 0) {
|
|
49
|
+
totalRadian -= PI2
|
|
50
|
+
} else if (sweepFlag === 1 && totalRadian < 0) {
|
|
51
|
+
totalRadian += PI2
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const centerX = fromX + halfX + rotationCos * cx - rotationSin * cy
|
|
55
|
+
const centerY = fromY + halfY + rotationSin * cx + rotationCos * cy
|
|
56
|
+
|
|
57
|
+
const anticlockwise = totalRadian < 0 ? 1 : 0
|
|
58
|
+
|
|
59
|
+
if (curveMode) {
|
|
60
|
+
ellipse(data, centerX, centerY, radiusX, radiusY, rotation, startRadian / OneRadian, endRadian / OneRadian, anticlockwise as unknown as boolean)
|
|
61
|
+
} else {
|
|
62
|
+
if (radiusX === radiusY && !rotation) {
|
|
63
|
+
data.push(PathCommandMap.O, centerX, centerY, radiusX, startRadian / OneRadian, endRadian / OneRadian, anticlockwise)
|
|
64
|
+
} else {
|
|
65
|
+
data.push(PathCommandMap.G, centerX, centerY, radiusX, radiusY, rotation, startRadian / OneRadian, endRadian / OneRadian, anticlockwise)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { ITwoPointBoundsData, IPathCommandData, IBoundsData, IPointData } from '@leafer/interface'
|
|
2
|
+
import { TwoPointBoundsHelper } from '@leafer/math'
|
|
3
|
+
import { Debug } from '@leafer/debug'
|
|
4
|
+
|
|
5
|
+
import { BezierHelper } from './BezierHelper'
|
|
6
|
+
import { PathCommandMap as Command } from './PathCommandMap'
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
const { M, L, C, Q, Z, N, D, X, G, F, O, P, U } = Command
|
|
10
|
+
const { toTwoPointBounds, toTwoPointBoundsByQuadraticCurve, arcTo, arc, ellipse } = BezierHelper
|
|
11
|
+
const { add, copy, addPoint, setPoint, addBounds, toBounds } = TwoPointBoundsHelper
|
|
12
|
+
const debug = Debug.get('PathBounds')
|
|
13
|
+
|
|
14
|
+
let radius: number, radiusX: number, radiusY: number
|
|
15
|
+
const tempPointBounds = {} as ITwoPointBoundsData
|
|
16
|
+
const setPointBounds = {} as ITwoPointBoundsData
|
|
17
|
+
const setEndPoint = {} as IPointData
|
|
18
|
+
|
|
19
|
+
export const PathBounds = {
|
|
20
|
+
|
|
21
|
+
toBounds(data: IPathCommandData, setBounds: IBoundsData): void {
|
|
22
|
+
PathBounds.toTwoPointBounds(data, setPointBounds)
|
|
23
|
+
toBounds(setPointBounds, setBounds)
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
toTwoPointBounds(data: IPathCommandData, setPointBounds: ITwoPointBoundsData): void {
|
|
27
|
+
if (!data || !data.length) return setPoint(setPointBounds, 0, 0)
|
|
28
|
+
|
|
29
|
+
let command: number
|
|
30
|
+
let i: number = 0, x: number = 0, y: number = 0, x1: number, y1: number, toX: number, toY: number
|
|
31
|
+
|
|
32
|
+
const len = data.length
|
|
33
|
+
|
|
34
|
+
while (i < len) {
|
|
35
|
+
command = data[i]
|
|
36
|
+
|
|
37
|
+
if (i === 0) {
|
|
38
|
+
if (command === Z || command === C || command === Q) {
|
|
39
|
+
setPoint(setPointBounds, x, y)
|
|
40
|
+
} else {
|
|
41
|
+
setPoint(setPointBounds, data[i + 1], data[i + 2])
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
switch (command) {
|
|
46
|
+
case M: //moveto(x, y)
|
|
47
|
+
case L: //lineto(x, y)
|
|
48
|
+
x = data[i + 1]
|
|
49
|
+
y = data[i + 2]
|
|
50
|
+
addPoint(setPointBounds, x, y)
|
|
51
|
+
i += 3
|
|
52
|
+
break
|
|
53
|
+
case C: //bezierCurveTo(x1, y1, x2, y2, x,y)
|
|
54
|
+
toX = data[i + 5]
|
|
55
|
+
toY = data[i + 6]
|
|
56
|
+
toTwoPointBounds(x, y, data[i + 1], data[i + 2], data[i + 3], data[i + 4], toX, toY, tempPointBounds)
|
|
57
|
+
add(setPointBounds, tempPointBounds)
|
|
58
|
+
x = toX
|
|
59
|
+
y = toY
|
|
60
|
+
i += 7
|
|
61
|
+
break
|
|
62
|
+
case Q: //quadraticCurveTo(x1, y1, x, y)
|
|
63
|
+
x1 = data[i + 1]
|
|
64
|
+
y1 = data[i + 2]
|
|
65
|
+
toX = data[i + 3]
|
|
66
|
+
toY = data[i + 4]
|
|
67
|
+
toTwoPointBoundsByQuadraticCurve(x, y, x1, y1, toX, toY, tempPointBounds)
|
|
68
|
+
add(setPointBounds, tempPointBounds)
|
|
69
|
+
x = toX
|
|
70
|
+
y = toY
|
|
71
|
+
i += 5
|
|
72
|
+
break
|
|
73
|
+
case Z: //closepath()
|
|
74
|
+
i += 1
|
|
75
|
+
break
|
|
76
|
+
|
|
77
|
+
// canvas command
|
|
78
|
+
|
|
79
|
+
case N: // rect(x, y, width, height)
|
|
80
|
+
x = data[i + 1]
|
|
81
|
+
y = data[i + 2]
|
|
82
|
+
addBounds(setPointBounds, x, y, data[i + 3], data[i + 4])
|
|
83
|
+
i += 5
|
|
84
|
+
break
|
|
85
|
+
case D: // roundRect(x, y, width, height, radius1, radius2, radius3, radius4)
|
|
86
|
+
case X: // simple roundRect(x, y, width, height, radius)
|
|
87
|
+
x = data[i + 1]
|
|
88
|
+
y = data[i + 2]
|
|
89
|
+
addBounds(setPointBounds, x, y, data[i + 3], data[i + 4])
|
|
90
|
+
i += (command === D ? 9 : 6)
|
|
91
|
+
break
|
|
92
|
+
case G: // ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)
|
|
93
|
+
ellipse(null, 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, tempPointBounds, setEndPoint)
|
|
94
|
+
i === 0 ? copy(setPointBounds, tempPointBounds) : add(setPointBounds, tempPointBounds)
|
|
95
|
+
x = setEndPoint.x
|
|
96
|
+
y = setEndPoint.y
|
|
97
|
+
i += 9
|
|
98
|
+
break
|
|
99
|
+
case F: // simple ellipse(x, y, radiusX, radiusY)
|
|
100
|
+
x = data[i + 1]
|
|
101
|
+
y = data[i + 2]
|
|
102
|
+
radiusX = data[i + 3]
|
|
103
|
+
radiusY = data[i + 4]
|
|
104
|
+
addBounds(setPointBounds, x - radiusX, y - radiusY, radiusX * 2, radiusY * 2)
|
|
105
|
+
x += radiusX
|
|
106
|
+
i += 5
|
|
107
|
+
break
|
|
108
|
+
case O: // arc(x, y, radius, startAngle, endAngle, anticlockwise)
|
|
109
|
+
arc(null, data[i + 1], data[i + 2], data[i + 3], data[i + 4], data[i + 5], data[i + 6] as unknown as boolean, tempPointBounds, setEndPoint)
|
|
110
|
+
i === 0 ? copy(setPointBounds, tempPointBounds) : add(setPointBounds, tempPointBounds)
|
|
111
|
+
x = setEndPoint.x
|
|
112
|
+
y = setEndPoint.y
|
|
113
|
+
i += 7
|
|
114
|
+
break
|
|
115
|
+
case P: // simple arc(x, y, radius)
|
|
116
|
+
x = data[i + 1]
|
|
117
|
+
y = data[i + 2]
|
|
118
|
+
radius = data[i + 3]
|
|
119
|
+
addBounds(setPointBounds, x - radius, y - radius, radius * 2, radius * 2)
|
|
120
|
+
x += radius
|
|
121
|
+
i += 4
|
|
122
|
+
break
|
|
123
|
+
case U: // arcTo(x1, y1, x2, y2, radius)
|
|
124
|
+
arcTo(null, x, y, data[i + 1], data[i + 2], data[i + 3], data[i + 4], data[i + 5], tempPointBounds, setEndPoint)
|
|
125
|
+
i === 0 ? copy(setPointBounds, tempPointBounds) : add(setPointBounds, tempPointBounds)
|
|
126
|
+
x = setEndPoint.x
|
|
127
|
+
y = setEndPoint.y
|
|
128
|
+
i += 6
|
|
129
|
+
break
|
|
130
|
+
default:
|
|
131
|
+
debug.error(`command: ${command} [index:${i}]`, data)
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { IPathCommandData, IPointData } from '@leafer/interface'
|
|
2
|
+
import { PathCommandMap } from './PathCommandMap'
|
|
3
|
+
import { BezierHelper } from './BezierHelper'
|
|
4
|
+
import { MathHelper } from '@leafer/math'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
const { M, L, C, Q, Z, N, D, X, G, F, O, P, U } = PathCommandMap
|
|
8
|
+
const startPoint = {} as IPointData
|
|
9
|
+
|
|
10
|
+
export const PathCommandDataHelper = {
|
|
11
|
+
|
|
12
|
+
beginPath(data: IPathCommandData): void {
|
|
13
|
+
data.length = 0
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
// svg and canvas
|
|
17
|
+
|
|
18
|
+
moveTo(data: IPathCommandData, x: number, y: number): void {
|
|
19
|
+
data.push(M, x, y)
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
lineTo(data: IPathCommandData, x: number, y: number): void {
|
|
23
|
+
data.push(L, x, y)
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
bezierCurveTo(data: IPathCommandData, x1: number, y1: number, x2: number, y2: number, x: number, y: number): void {
|
|
27
|
+
data.push(C, x1, y1, x2, y2, x, y)
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
quadraticCurveTo(data: IPathCommandData, x1: number, y1: number, x: number, y: number): void {
|
|
31
|
+
data.push(Q, x1, y1, x, y)
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
closePath(data: IPathCommandData): void {
|
|
35
|
+
data.push(Z)
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
// canvas
|
|
39
|
+
|
|
40
|
+
rect(data: IPathCommandData, x: number, y: number, width: number, height: number): void {
|
|
41
|
+
data.push(N, x, y, width, height)
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
roundRect(data: IPathCommandData, x: number, y: number, width: number, height: number, cornerRadius: number | number[]): void {
|
|
45
|
+
if (typeof cornerRadius === 'number') {
|
|
46
|
+
data.push(X, x, y, width, height, cornerRadius)
|
|
47
|
+
} else {
|
|
48
|
+
const fourCorners = MathHelper.fourNumber(cornerRadius)
|
|
49
|
+
if (fourCorners) {
|
|
50
|
+
data.push(D, x, y, width, height, ...fourCorners)
|
|
51
|
+
} else {
|
|
52
|
+
data.push(N, x, y, width, height)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
ellipse(data: IPathCommandData, x: number, y: number, radiusX: number, radiusY: number, rotation?: number, startAngle?: number, endAngle?: number, anticlockwise?: boolean): void {
|
|
58
|
+
if (rotation === undefined) {
|
|
59
|
+
data.push(F, x, y, radiusX, radiusY)
|
|
60
|
+
} else {
|
|
61
|
+
if (startAngle === undefined) startAngle = 0
|
|
62
|
+
if (endAngle === undefined) endAngle = 360
|
|
63
|
+
data.push(G, x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise ? 1 : 0)
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
arc(data: IPathCommandData, x: number, y: number, radius: number, startAngle?: number, endAngle?: number, anticlockwise?: boolean): void {
|
|
68
|
+
if (startAngle === undefined) {
|
|
69
|
+
data.push(P, x, y, radius)
|
|
70
|
+
} else {
|
|
71
|
+
if (endAngle === undefined) endAngle = 360
|
|
72
|
+
data.push(O, x, y, radius, startAngle, endAngle, anticlockwise ? 1 : 0)
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
arcTo(data: IPathCommandData, x1: number, y1: number, x2: number, y2: number, radius: number): void {
|
|
77
|
+
data.push(U, x1, y1, x2, y2, radius)
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
// new
|
|
81
|
+
|
|
82
|
+
drawEllipse(data: IPathCommandData, x: number, y: number, radiusX: number, radiusY: number, rotation?: number, startAngle?: number, endAngle?: number, anticlockwise?: boolean): void {
|
|
83
|
+
if (rotation === undefined) rotation = 0
|
|
84
|
+
if (startAngle === undefined) startAngle = 0
|
|
85
|
+
if (endAngle === undefined) endAngle = 360
|
|
86
|
+
BezierHelper.ellipse(null, x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise, null, null, startPoint)
|
|
87
|
+
data.push(M, startPoint.x, startPoint.y)
|
|
88
|
+
ellipse(data, x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
drawArc(data: IPathCommandData, x: number, y: number, radius: number, startAngle?: number, endAngle?: number, anticlockwise?: boolean): void {
|
|
92
|
+
if (startAngle === undefined) startAngle = 0
|
|
93
|
+
if (endAngle === undefined) endAngle = 360
|
|
94
|
+
BezierHelper.arc(null, x, y, radius, startAngle, endAngle, anticlockwise, null, null, startPoint)
|
|
95
|
+
data.push(M, startPoint.x, startPoint.y)
|
|
96
|
+
arc(data, x, y, radius, startAngle, endAngle, anticlockwise)
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
drawPoints(data: IPathCommandData, points: number[], curve?: boolean | number, close?: boolean): void {
|
|
100
|
+
BezierHelper.points(data, points, curve, close)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const { ellipse, arc } = PathCommandDataHelper
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { INumberMap, IStringMap } from '@leafer/interface'
|
|
2
|
+
|
|
3
|
+
export const CanvasCommandOnlyMap: INumberMap = {
|
|
4
|
+
|
|
5
|
+
N: 21, // rect
|
|
6
|
+
D: 22, // roundRect
|
|
7
|
+
X: 23, // simple roundRect
|
|
8
|
+
G: 24, // ellipse
|
|
9
|
+
F: 25, // simple ellipse
|
|
10
|
+
O: 26, // arc
|
|
11
|
+
P: 27, // simple arc
|
|
12
|
+
U: 28 // arcTo
|
|
13
|
+
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const PathCommandMap: INumberMap = {
|
|
17
|
+
|
|
18
|
+
// svg and canvas
|
|
19
|
+
|
|
20
|
+
M: 1, // moveto
|
|
21
|
+
m: 10,
|
|
22
|
+
L: 2, // lineto
|
|
23
|
+
l: 20,
|
|
24
|
+
H: 3, // horizontal lineto
|
|
25
|
+
h: 30,
|
|
26
|
+
V: 4, // vertical lineto
|
|
27
|
+
v: 40,
|
|
28
|
+
C: 5, // curveto
|
|
29
|
+
c: 50,
|
|
30
|
+
S: 6, // smooth curveto
|
|
31
|
+
s: 60,
|
|
32
|
+
Q: 7, // quadratic Belzier curve
|
|
33
|
+
q: 70,
|
|
34
|
+
T: 8, // smooth quadratic Belzier curveto
|
|
35
|
+
t: 80,
|
|
36
|
+
A: 9, //e lliptical Arc
|
|
37
|
+
a: 90,
|
|
38
|
+
Z: 11, // closepath
|
|
39
|
+
z: 11,
|
|
40
|
+
|
|
41
|
+
R: 12, // Catmull Rom
|
|
42
|
+
|
|
43
|
+
// canvas
|
|
44
|
+
...CanvasCommandOnlyMap
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const PathCommandLengthMap: INumberMap = {
|
|
48
|
+
|
|
49
|
+
M: 3, //moveto
|
|
50
|
+
m: 3,
|
|
51
|
+
L: 3, //lineto
|
|
52
|
+
l: 3,
|
|
53
|
+
H: 2, //horizontal lineto
|
|
54
|
+
h: 2,
|
|
55
|
+
V: 2, //vertical lineto
|
|
56
|
+
v: 2,
|
|
57
|
+
C: 7, //curveto
|
|
58
|
+
c: 7,
|
|
59
|
+
S: 5, //smooth curveto
|
|
60
|
+
s: 5,
|
|
61
|
+
Q: 5, //quadratic Belzier curve
|
|
62
|
+
q: 5,
|
|
63
|
+
T: 3, //smooth quadratic Belzier curveto
|
|
64
|
+
t: 3,
|
|
65
|
+
A: 8, //elliptical Arc
|
|
66
|
+
a: 8,
|
|
67
|
+
Z: 1, //closepath
|
|
68
|
+
z: 1,
|
|
69
|
+
|
|
70
|
+
// canvas
|
|
71
|
+
|
|
72
|
+
N: 5, // rect
|
|
73
|
+
D: 9, // roundRect
|
|
74
|
+
X: 6, // simple roundRect
|
|
75
|
+
G: 9, // ellipse
|
|
76
|
+
F: 5, // simple ellipse
|
|
77
|
+
O: 7, // arc
|
|
78
|
+
P: 4, // simple arc
|
|
79
|
+
U: 6 // arcTo
|
|
80
|
+
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const NeedConvertToCanvasCommandMap: INumberMap = { // convert to: M L C Q Z
|
|
84
|
+
|
|
85
|
+
// M: 1, //moveto
|
|
86
|
+
m: 10,
|
|
87
|
+
// L: 2, //lineto
|
|
88
|
+
l: 20,
|
|
89
|
+
H: 3, //horizontal lineto
|
|
90
|
+
h: 30,
|
|
91
|
+
V: 4, //vertical lineto
|
|
92
|
+
v: 40,
|
|
93
|
+
// C: 5, //curveto
|
|
94
|
+
c: 50,
|
|
95
|
+
S: 6, //smooth curveto
|
|
96
|
+
s: 60,
|
|
97
|
+
// Q: 7, //quadratic Belzier curve
|
|
98
|
+
q: 70,
|
|
99
|
+
T: 8, //smooth quadratic Belzier curveto
|
|
100
|
+
t: 80,
|
|
101
|
+
A: 9, //elliptical Arc
|
|
102
|
+
a: 90,
|
|
103
|
+
// Z: 11, //closepath
|
|
104
|
+
// z: 11
|
|
105
|
+
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
export const NeedConvertToCurveCommandMap: INumberMap = {
|
|
110
|
+
...NeedConvertToCanvasCommandMap,
|
|
111
|
+
...CanvasCommandOnlyMap
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const P = PathCommandMap
|
|
115
|
+
|
|
116
|
+
export const PathNumberCommandMap: IStringMap = {}
|
|
117
|
+
for (let key in P) {
|
|
118
|
+
PathNumberCommandMap[P[key]] = key
|
|
119
|
+
}
|
|
120
|
+
// {1: 'M'}
|
|
121
|
+
|
|
122
|
+
export const PathNumberCommandLengthMap: INumberMap = {}
|
|
123
|
+
for (let key in P) {
|
|
124
|
+
PathNumberCommandLengthMap[P[key]] = PathCommandLengthMap[key]
|
|
125
|
+
}
|
|
126
|
+
// {1: 3}
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import { IPathCommandData, IPointData } from '@leafer/interface'
|
|
2
|
+
import { StringNumberMap } from '@leafer/math'
|
|
3
|
+
import { Debug } from '@leafer/debug'
|
|
4
|
+
|
|
5
|
+
import { PathCommandMap as Command, NeedConvertToCanvasCommandMap, NeedConvertToCurveCommandMap, PathCommandLengthMap, PathNumberCommandMap, PathNumberCommandLengthMap } from './PathCommandMap'
|
|
6
|
+
import { BezierHelper } from './BezierHelper'
|
|
7
|
+
import { EllipseHelper } from './EllipseHelper'
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
interface ICurrentCommand {
|
|
11
|
+
name?: number
|
|
12
|
+
length?: number
|
|
13
|
+
index?: number
|
|
14
|
+
dot?: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
const { M, m, L, l, H, h, V, v, C, c, S, s, Q, q, T, t, A, a, Z, z, N, D, X, G, F, O, P, U } = Command
|
|
19
|
+
const { rect, roundRect, arcTo, arc, ellipse, quadraticCurveTo } = BezierHelper
|
|
20
|
+
const { ellipticalArc } = EllipseHelper
|
|
21
|
+
const debug = Debug.get('PathConvert')
|
|
22
|
+
|
|
23
|
+
const setEndPoint = {} as IPointData
|
|
24
|
+
|
|
25
|
+
export const PathConvert = {
|
|
26
|
+
|
|
27
|
+
current: { dot: 0 } as ICurrentCommand,
|
|
28
|
+
|
|
29
|
+
stringify(data: IPathCommandData): string {
|
|
30
|
+
let i = 0, len = data.length, count: number, str: string = '', command: number, lastCommand: number
|
|
31
|
+
while (i < len) {
|
|
32
|
+
command = data[i]
|
|
33
|
+
count = PathNumberCommandLengthMap[command]
|
|
34
|
+
if (command === lastCommand) {
|
|
35
|
+
str += ' ' // 重复的命令可以省略
|
|
36
|
+
} else {
|
|
37
|
+
str += PathNumberCommandMap[command]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
for (let j = 1; j < count; j++) {
|
|
41
|
+
str += data[i + j];
|
|
42
|
+
(j === count - 1) || (str += ' ')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
lastCommand = command
|
|
46
|
+
i += count
|
|
47
|
+
}
|
|
48
|
+
return str
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
parse(pathString: string, curveMode?: boolean): IPathCommandData {
|
|
52
|
+
|
|
53
|
+
let needConvert: boolean, char: string, lastChar: string, num = ''
|
|
54
|
+
const data: IPathCommandData = []
|
|
55
|
+
const convertCommand = curveMode ? NeedConvertToCurveCommandMap : NeedConvertToCanvasCommandMap
|
|
56
|
+
|
|
57
|
+
for (let i = 0, len = pathString.length; i < len; i++) {
|
|
58
|
+
|
|
59
|
+
char = pathString[i]
|
|
60
|
+
|
|
61
|
+
if (StringNumberMap[char]) {
|
|
62
|
+
|
|
63
|
+
if (char === '.') {
|
|
64
|
+
current.dot++
|
|
65
|
+
if (current.dot > 1) {
|
|
66
|
+
pushData(data, num); num = '' // .375.375
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
num += char
|
|
71
|
+
|
|
72
|
+
} else if (Command[char]) {
|
|
73
|
+
|
|
74
|
+
if (num) { pushData(data, num); num = '' }
|
|
75
|
+
|
|
76
|
+
current.name = Command[char]
|
|
77
|
+
current.length = PathCommandLengthMap[char]
|
|
78
|
+
current.index = 0
|
|
79
|
+
pushData(data, current.name)
|
|
80
|
+
|
|
81
|
+
if (!needConvert && convertCommand[char]) needConvert = true
|
|
82
|
+
|
|
83
|
+
} else {
|
|
84
|
+
|
|
85
|
+
if (char === '-' || char === '+') {
|
|
86
|
+
|
|
87
|
+
if (lastChar === 'e' || lastChar === 'E') { // L45e-12 21e+22
|
|
88
|
+
num += char
|
|
89
|
+
} else {
|
|
90
|
+
if (num) pushData(data, num) // L-34-35 L+12+28
|
|
91
|
+
num = char
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
} else {
|
|
95
|
+
if (num) { pushData(data, num); num = '' }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
lastChar = char
|
|
101
|
+
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (num) pushData(data, num)
|
|
105
|
+
|
|
106
|
+
return needConvert ? PathConvert.toCanvasData(data, curveMode) : data
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
toCanvasData(old: IPathCommandData, curveMode?: boolean): IPathCommandData {
|
|
110
|
+
|
|
111
|
+
let x = 0, y = 0, x1 = 0, y1 = 0, i = 0, len = old.length, controlX: number, controlY: number, command: number, lastCommand: number, smooth: boolean
|
|
112
|
+
const data: IPathCommandData = []
|
|
113
|
+
|
|
114
|
+
while (i < len) {
|
|
115
|
+
|
|
116
|
+
command = old[i]
|
|
117
|
+
|
|
118
|
+
switch (command) {
|
|
119
|
+
//moveto(x, y)
|
|
120
|
+
case m:
|
|
121
|
+
old[i + 1] += x
|
|
122
|
+
old[i + 2] += y
|
|
123
|
+
case M:
|
|
124
|
+
x = old[i + 1]
|
|
125
|
+
y = old[i + 2]
|
|
126
|
+
data.push(M, x, y)
|
|
127
|
+
i += 3
|
|
128
|
+
break
|
|
129
|
+
|
|
130
|
+
//horizontal lineto(x)
|
|
131
|
+
case h:
|
|
132
|
+
old[i + 1] += x
|
|
133
|
+
case H:
|
|
134
|
+
x = old[i + 1]
|
|
135
|
+
data.push(L, x, y)
|
|
136
|
+
i += 2
|
|
137
|
+
break
|
|
138
|
+
|
|
139
|
+
//vertical lineto(y)
|
|
140
|
+
case v:
|
|
141
|
+
old[i + 1] += y
|
|
142
|
+
case V:
|
|
143
|
+
y = old[i + 1]
|
|
144
|
+
data.push(L, x, y)
|
|
145
|
+
i += 2
|
|
146
|
+
break
|
|
147
|
+
|
|
148
|
+
//lineto(x,y)
|
|
149
|
+
case l:
|
|
150
|
+
old[i + 1] += x
|
|
151
|
+
old[i + 2] += y
|
|
152
|
+
case L:
|
|
153
|
+
x = old[i + 1]
|
|
154
|
+
y = old[i + 2]
|
|
155
|
+
data.push(L, x, y)
|
|
156
|
+
i += 3
|
|
157
|
+
break
|
|
158
|
+
|
|
159
|
+
//smooth bezierCurveTo(x2, y2, x, y)
|
|
160
|
+
case s: //smooth
|
|
161
|
+
old[i + 1] += x
|
|
162
|
+
old[i + 2] += y
|
|
163
|
+
old[i + 3] += x
|
|
164
|
+
old[i + 4] += y
|
|
165
|
+
command = S
|
|
166
|
+
case S:
|
|
167
|
+
smooth = (lastCommand === C) || (lastCommand === S)
|
|
168
|
+
x1 = smooth ? (x * 2 - controlX) : old[i + 1]
|
|
169
|
+
y1 = smooth ? (y * 2 - controlY) : old[i + 2]
|
|
170
|
+
controlX = old[i + 1]
|
|
171
|
+
controlY = old[i + 2]
|
|
172
|
+
x = old[i + 3]
|
|
173
|
+
y = old[i + 4]
|
|
174
|
+
data.push(C, x1, y1, controlX, controlY, x, y)
|
|
175
|
+
i += 5
|
|
176
|
+
break
|
|
177
|
+
|
|
178
|
+
//bezierCurveTo(x1, y1, x2, y2, x, y)
|
|
179
|
+
case c:
|
|
180
|
+
old[i + 1] += x
|
|
181
|
+
old[i + 2] += y
|
|
182
|
+
old[i + 3] += x
|
|
183
|
+
old[i + 4] += y
|
|
184
|
+
old[i + 5] += x
|
|
185
|
+
old[i + 6] += y
|
|
186
|
+
command = C
|
|
187
|
+
case C:
|
|
188
|
+
controlX = old[i + 3]
|
|
189
|
+
controlY = old[i + 4]
|
|
190
|
+
x = old[i + 5]
|
|
191
|
+
y = old[i + 6]
|
|
192
|
+
data.push(C, old[i + 1], old[i + 2], controlX, controlY, x, y)
|
|
193
|
+
i += 7
|
|
194
|
+
break
|
|
195
|
+
|
|
196
|
+
//smooth quadraticCurveTo(x, y)
|
|
197
|
+
case t:
|
|
198
|
+
old[i + 1] += x
|
|
199
|
+
old[i + 2] += y
|
|
200
|
+
command = T
|
|
201
|
+
case T: //smooth
|
|
202
|
+
smooth = (lastCommand === Q) || (lastCommand === T)
|
|
203
|
+
controlX = smooth ? (x * 2 - controlX) : old[i + 1]
|
|
204
|
+
controlY = smooth ? (y * 2 - controlY) : old[i + 2]
|
|
205
|
+
curveMode ? quadraticCurveTo(data, x, y, controlX, controlY, old[i + 1], old[i + 2]) : data.push(Q, controlX, controlY, old[i + 1], old[i + 2])
|
|
206
|
+
x = old[i + 1]
|
|
207
|
+
y = old[i + 2]
|
|
208
|
+
i += 3
|
|
209
|
+
break
|
|
210
|
+
|
|
211
|
+
//quadraticCurveTo(x1, y1, x, y)
|
|
212
|
+
case q:
|
|
213
|
+
old[i + 1] += x
|
|
214
|
+
old[i + 2] += y
|
|
215
|
+
old[i + 3] += x
|
|
216
|
+
old[i + 4] += y
|
|
217
|
+
command = Q
|
|
218
|
+
case Q:
|
|
219
|
+
controlX = old[i + 1]
|
|
220
|
+
controlY = old[i + 2]
|
|
221
|
+
curveMode ? quadraticCurveTo(data, x, y, controlX, controlY, old[i + 3], old[i + 4]) : data.push(Q, controlX, controlY, old[i + 3], old[i + 4])
|
|
222
|
+
x = old[i + 3]
|
|
223
|
+
y = old[i + 4]
|
|
224
|
+
i += 5
|
|
225
|
+
break
|
|
226
|
+
|
|
227
|
+
//ellipticalArc(rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y)
|
|
228
|
+
case a:
|
|
229
|
+
old[i + 6] += x
|
|
230
|
+
old[i + 7] += y
|
|
231
|
+
case A:
|
|
232
|
+
ellipticalArc(data, x, y, old[i + 1], old[i + 2], old[i + 3], old[i + 4], old[i + 5], old[i + 6], old[i + 7], curveMode) // convert to canvas ellipse or curve
|
|
233
|
+
x = old[i + 6]
|
|
234
|
+
y = old[i + 7]
|
|
235
|
+
i += 8
|
|
236
|
+
break
|
|
237
|
+
case z:
|
|
238
|
+
case Z:
|
|
239
|
+
data.push(Z)
|
|
240
|
+
i++
|
|
241
|
+
break
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
// canvas command
|
|
245
|
+
|
|
246
|
+
case N: // rect(x, y, width, height)
|
|
247
|
+
x = old[i + 1]
|
|
248
|
+
y = old[i + 2]
|
|
249
|
+
curveMode ? rect(data, x, y, old[i + 3], old[i + 4]) : copyData(data, old, i, 5)
|
|
250
|
+
i += 5
|
|
251
|
+
break
|
|
252
|
+
case D: // roundRect(x, y, width, height, radius1, radius2, radius3, radius4)
|
|
253
|
+
x = old[i + 1]
|
|
254
|
+
y = old[i + 2]
|
|
255
|
+
curveMode ? roundRect(data, x, y, old[i + 3], old[i + 4], [old[i + 5], old[i + 6], old[i + 7], old[i + 8]]) : copyData(data, old, i, 9)
|
|
256
|
+
i += 9
|
|
257
|
+
break
|
|
258
|
+
case X: // simple roundRect(x, y, width, height, radius)
|
|
259
|
+
x = old[i + 1]
|
|
260
|
+
y = old[i + 2]
|
|
261
|
+
curveMode ? roundRect(data, x, y, old[i + 3], old[i + 4], old[i + 5]) : copyData(data, old, i, 6)
|
|
262
|
+
i += 6
|
|
263
|
+
break
|
|
264
|
+
case G: // ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)
|
|
265
|
+
ellipse(curveMode ? data : copyData(data, old, i, 9), old[i + 1], old[i + 2], old[i + 3], old[i + 4], old[i + 5], old[i + 6], old[i + 7], old[i + 8] as unknown as boolean, null, setEndPoint)
|
|
266
|
+
x = setEndPoint.x
|
|
267
|
+
y = setEndPoint.y
|
|
268
|
+
i += 9
|
|
269
|
+
break
|
|
270
|
+
case F: // simple ellipse(x, y, radiusX, radiusY)
|
|
271
|
+
curveMode ? ellipse(data, old[i + 1], old[i + 2], old[i + 3], old[i + 4], 0, 0, 360, false) : copyData(data, old, i, 5)
|
|
272
|
+
x = old[i + 1] + old[i + 3]
|
|
273
|
+
y = old[i + 2]
|
|
274
|
+
i += 5
|
|
275
|
+
break
|
|
276
|
+
case O: // arc(x, y, radius, startAngle, endAngle, anticlockwise)
|
|
277
|
+
arc(curveMode ? data : copyData(data, old, i, 7), old[i + 1], old[i + 2], old[i + 3], old[i + 4], old[i + 5], old[i + 6] as unknown as boolean, null, setEndPoint)
|
|
278
|
+
x = setEndPoint.x
|
|
279
|
+
y = setEndPoint.y
|
|
280
|
+
i += 7
|
|
281
|
+
break
|
|
282
|
+
case P: // simple arc(x, y, radius)
|
|
283
|
+
curveMode ? arc(data, old[i + 1], old[i + 2], old[i + 3], 0, 360, false) : copyData(data, old, i, 4)
|
|
284
|
+
x = old[i + 1] + old[i + 3]
|
|
285
|
+
y = old[i + 2]
|
|
286
|
+
i += 4
|
|
287
|
+
break
|
|
288
|
+
case U: // arcTo(x1, y1, x2, y2, radius)
|
|
289
|
+
arcTo(curveMode ? data : copyData(data, old, i, 6), x, y, old[i + 1], old[i + 2], old[i + 3], old[i + 4], old[i + 5], null, setEndPoint)
|
|
290
|
+
x = setEndPoint.x
|
|
291
|
+
y = setEndPoint.y
|
|
292
|
+
i += 6
|
|
293
|
+
break
|
|
294
|
+
default:
|
|
295
|
+
debug.error(`command: ${command} [index:${i}]`, old)
|
|
296
|
+
return data
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
lastCommand = command
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return data
|
|
303
|
+
|
|
304
|
+
},
|
|
305
|
+
|
|
306
|
+
copyData(data: IPathCommandData, old: IPathCommandData, index: number, count: number): void {
|
|
307
|
+
for (let i = index, end = index + count; i < end; i++) {
|
|
308
|
+
data.push(old[i])
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
|
|
312
|
+
pushData(data: IPathCommandData, strNum: string | number) {
|
|
313
|
+
if (current.index === current.length) { // 单个命令,多个数据的情况
|
|
314
|
+
current.index = 1
|
|
315
|
+
data.push(current.name)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
data.push(Number(strNum))
|
|
319
|
+
current.index++
|
|
320
|
+
current.dot = 0
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const { current, pushData, copyData } = PathConvert
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { IPathCommandData, IPathDrawer, IPathString } from '@leafer/interface'
|
|
2
|
+
import { PathCommandDataHelper } from './PathCommandDataHelper'
|
|
3
|
+
import { PathHelper } from './PathHelper'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
const { moveTo, lineTo, quadraticCurveTo, bezierCurveTo, closePath, beginPath, rect, roundRect, ellipse, arc, arcTo, drawEllipse, drawArc, drawPoints } = PathCommandDataHelper
|
|
7
|
+
|
|
8
|
+
export class PathCreator implements IPathDrawer {
|
|
9
|
+
|
|
10
|
+
public path: IPathCommandData
|
|
11
|
+
|
|
12
|
+
constructor(path?: IPathCommandData | IPathString) {
|
|
13
|
+
if (path) {
|
|
14
|
+
this.path = typeof path === 'string' ? PathHelper.parse(path) : path
|
|
15
|
+
} else {
|
|
16
|
+
this.path = []
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public beginPath(): PathCreator {
|
|
21
|
+
beginPath(this.path)
|
|
22
|
+
return this
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// svg and canvas
|
|
26
|
+
|
|
27
|
+
public moveTo(x: number, y: number): PathCreator {
|
|
28
|
+
moveTo(this.path, x, y)
|
|
29
|
+
return this
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public lineTo(x: number, y: number): PathCreator {
|
|
33
|
+
lineTo(this.path, x, y)
|
|
34
|
+
return this
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public bezierCurveTo(x1: number, y1: number, x2: number, y2: number, x: number, y: number): PathCreator {
|
|
38
|
+
bezierCurveTo(this.path, x1, y1, x2, y2, x, y)
|
|
39
|
+
return this
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public quadraticCurveTo(x1: number, y1: number, x: number, y: number): PathCreator {
|
|
43
|
+
quadraticCurveTo(this.path, x1, y1, x, y)
|
|
44
|
+
return this
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public closePath(): PathCreator {
|
|
48
|
+
closePath(this.path)
|
|
49
|
+
return this
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// canvas
|
|
53
|
+
|
|
54
|
+
public rect(x: number, y: number, width: number, height: number): PathCreator {
|
|
55
|
+
rect(this.path, x, y, width, height)
|
|
56
|
+
return this
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public roundRect(x: number, y: number, width: number, height: number, cornerRadius: number | number[]): PathCreator {
|
|
60
|
+
roundRect(this.path, x, y, width, height, cornerRadius)
|
|
61
|
+
return this
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public ellipse(x: number, y: number, radiusX: number, radiusY: number, rotation?: number, startAngle?: number, endAngle?: number, anticlockwise?: boolean): PathCreator {
|
|
65
|
+
ellipse(this.path, x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)
|
|
66
|
+
return this
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public arc(x: number, y: number, radius: number, startAngle?: number, endAngle?: number, anticlockwise?: boolean): PathCreator {
|
|
70
|
+
arc(this.path, x, y, radius, startAngle, endAngle, anticlockwise)
|
|
71
|
+
return this
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): PathCreator {
|
|
75
|
+
arcTo(this.path, x1, y1, x2, y2, radius)
|
|
76
|
+
return this
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// moveTo, then draw
|
|
80
|
+
|
|
81
|
+
public drawEllipse(x: number, y: number, radiusX: number, radiusY: number, rotation?: number, startAngle?: number, endAngle?: number, anticlockwise?: boolean): PathCreator {
|
|
82
|
+
drawEllipse(this.path, x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)
|
|
83
|
+
return this
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
public drawArc(x: number, y: number, radius: number, startAngle?: number, endAngle?: number, anticlockwise?: boolean): PathCreator {
|
|
87
|
+
drawArc(this.path, x, y, radius, startAngle, endAngle, anticlockwise)
|
|
88
|
+
return this
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
public drawPoints(points: number[], curve?: boolean | number, close?: boolean): PathCreator {
|
|
92
|
+
drawPoints(this.path, points, curve, close)
|
|
93
|
+
return this
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { IPathDrawer, IPathCommandData } from '@leafer/interface'
|
|
2
|
+
import { OneRadian, PI2 } from '@leafer/math'
|
|
3
|
+
import { Debug } from '@leafer/debug'
|
|
4
|
+
|
|
5
|
+
import { PathCommandMap as Command } from './PathCommandMap'
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
const { M, L, C, Q, Z, N, D, X, G, F, O, P, U } = Command
|
|
9
|
+
const debug = Debug.get('PathDrawer')
|
|
10
|
+
|
|
11
|
+
export const PathDrawer = {
|
|
12
|
+
|
|
13
|
+
drawPathByData(drawer: IPathDrawer, data: IPathCommandData): void {
|
|
14
|
+
if (!data) return
|
|
15
|
+
|
|
16
|
+
let command: number
|
|
17
|
+
let i = 0, len = data.length
|
|
18
|
+
|
|
19
|
+
while (i < len) {
|
|
20
|
+
command = data[i]
|
|
21
|
+
switch (command) {
|
|
22
|
+
case M: //moveto(x, y)
|
|
23
|
+
drawer.moveTo(data[i + 1], data[i + 2])
|
|
24
|
+
i += 3
|
|
25
|
+
break
|
|
26
|
+
case L: //lineto(x, y)
|
|
27
|
+
drawer.lineTo(data[i + 1], data[i + 2])
|
|
28
|
+
i += 3
|
|
29
|
+
break
|
|
30
|
+
case C: //bezierCurveTo(x1, y1, x2, y2, x, y)
|
|
31
|
+
drawer.bezierCurveTo(data[i + 1], data[i + 2], data[i + 3], data[i + 4], data[i + 5], data[i + 6])
|
|
32
|
+
i += 7
|
|
33
|
+
break
|
|
34
|
+
case Q: //quadraticCurveTo(x1, y1, x, y)
|
|
35
|
+
drawer.quadraticCurveTo(data[i + 1], data[i + 2], data[i + 3], data[i + 4])
|
|
36
|
+
i += 5
|
|
37
|
+
break
|
|
38
|
+
case Z: //closepath()
|
|
39
|
+
drawer.closePath()
|
|
40
|
+
i += 1
|
|
41
|
+
break
|
|
42
|
+
|
|
43
|
+
// canvas command
|
|
44
|
+
|
|
45
|
+
case N: // rect(x, y, width, height)
|
|
46
|
+
drawer.rect(data[i + 1], data[i + 2], data[i + 3], data[i + 4])
|
|
47
|
+
i += 5
|
|
48
|
+
break
|
|
49
|
+
case D: // roundRect(x, y, width, height, radius1, radius2, radius3, radius4)
|
|
50
|
+
drawer.roundRect(data[i + 1], data[i + 2], data[i + 3], data[i + 4], [data[i + 5], data[i + 6], data[i + 7], data[i + 8]])
|
|
51
|
+
i += 9
|
|
52
|
+
break
|
|
53
|
+
case X: // simple roundRect(x, y, width, height, radius)
|
|
54
|
+
drawer.roundRect(data[i + 1], data[i + 2], data[i + 3], data[i + 4], data[i + 5])
|
|
55
|
+
i += 6
|
|
56
|
+
break
|
|
57
|
+
case G: // ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)
|
|
58
|
+
drawer.ellipse(data[i + 1], data[i + 2], data[i + 3], data[i + 4], data[i + 5] * OneRadian, data[i + 6] * OneRadian, data[i + 7] * OneRadian, data[i + 8] as unknown as boolean)
|
|
59
|
+
i += 9
|
|
60
|
+
break
|
|
61
|
+
case F: // simple ellipse(x, y, radiusX, radiusY)
|
|
62
|
+
drawer.ellipse(data[i + 1], data[i + 2], data[i + 3], data[i + 4], 0, 0, PI2, false)
|
|
63
|
+
i += 5
|
|
64
|
+
break
|
|
65
|
+
case O: // arc(x, y, radius, startAngle, endAngle, anticlockwise)
|
|
66
|
+
drawer.arc(data[i + 1], data[i + 2], data[i + 3], data[i + 4] * OneRadian, data[i + 5] * OneRadian, data[i + 6] as unknown as boolean)
|
|
67
|
+
i += 7
|
|
68
|
+
break
|
|
69
|
+
case P: // simple arc(x, y, radius)
|
|
70
|
+
drawer.arc(data[i + 1], data[i + 2], data[i + 3], 0, PI2, false)
|
|
71
|
+
i += 4
|
|
72
|
+
break
|
|
73
|
+
case U: // arcTo(x1, y1, x2, y2, radius)
|
|
74
|
+
drawer.arcTo(data[i + 1], data[i + 2], data[i + 3], data[i + 4], data[i + 5])
|
|
75
|
+
i += 6
|
|
76
|
+
break
|
|
77
|
+
default:
|
|
78
|
+
debug.error(`command: ${command} [index:${i}]`, data)
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { IPathCommandData, IPathCreator } from '@leafer/interface'
|
|
2
|
+
|
|
3
|
+
export const PathHelper = {
|
|
4
|
+
// index.ts rewrite
|
|
5
|
+
creator: {} as IPathCreator,
|
|
6
|
+
parse(_pathString: string, _curveMode?: boolean): IPathCommandData { return undefined },
|
|
7
|
+
convertToCanvasData(_old: IPathCommandData, _curveMode?: boolean): IPathCommandData { return undefined }
|
|
8
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { IPathDrawer } from '@leafer/interface'
|
|
2
|
+
import { MathHelper } from '@leafer/math'
|
|
3
|
+
|
|
4
|
+
export const RectHelper = {
|
|
5
|
+
|
|
6
|
+
drawRoundRect(drawer: IPathDrawer, x: number, y: number, width: number, height: number, cornerRadius: number | number[]): void {
|
|
7
|
+
let [topLeft, topRight, bottomRight, bottomLeft] = MathHelper.fourNumber(cornerRadius)
|
|
8
|
+
|
|
9
|
+
const max = Math.min(width / 2, height / 2)
|
|
10
|
+
if (topLeft > max) topLeft = max
|
|
11
|
+
if (topRight > max) topRight = max
|
|
12
|
+
if (bottomRight > max) bottomRight = max
|
|
13
|
+
if (bottomLeft > max) bottomLeft = max
|
|
14
|
+
|
|
15
|
+
topLeft ? drawer.moveTo(x + topLeft, y) : drawer.moveTo(x, y)
|
|
16
|
+
topRight ? drawer.arcTo(x + width, y, x + width, y + height, topRight) : drawer.lineTo(x + width, y)
|
|
17
|
+
bottomRight ? drawer.arcTo(x + width, y + height, x, y + height, bottomRight) : drawer.lineTo(x + width, y + height)
|
|
18
|
+
bottomLeft ? drawer.arcTo(x, y + height, x, y, bottomLeft) : drawer.lineTo(x, y + height)
|
|
19
|
+
topLeft ? drawer.arcTo(x, y, x + width, y, topLeft) : drawer.lineTo(x, y)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
}
|