Latest Threat Research:SANDWORM_MODE: Shai-Hulud-Style npm Worm Hijacks CI Workflows and Poisons AI Toolchains.Details
Socket
Book a DemoInstallSign in
Socket

@leafer/path

Package Overview
Dependencies
Maintainers
1
Versions
116
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@leafer/path - npm Package Compare versions

Comparing version
1.0.0-beta.15
to
1.0.0-beta.16
+256
src/BezierHelper.ts
import { IPointData, ITwoPointBoundsData, IPathCommandData } from '@leafer/interface'
import { OneRadian, PI2, PI_2, PointHelper, TwoPointBoundsHelper } from '@leafer/math'
import { PathCommandMap } from './PathCommandMap'
import { RectHelper } from './RectHelper'
import { PathHelper } from './PathHelper'
const { sin, cos, atan2, ceil, abs, PI, sqrt, pow } = Math
const { setPoint, addPoint } = TwoPointBoundsHelper
const { set } = PointHelper
const { M, L, C, Q, Z } = PathCommandMap
const tempPoint = {} as IPointData
export const BezierHelper = {
points(data: IPathCommandData, points: number[], curve?: boolean | number, close?: boolean): void {
data.push(M, points[0], points[1])
if (curve && points.length > 5) {
let aX: number, aY: number, bX: number, bY: number, cX: number, cY: number, c1X: number, c1Y: number, c2X: number, c2Y: number
let ba: number, cb: number, d: number, len = points.length
const t = curve === true ? 0.5 : curve as number
if (close) {
points = [points[len - 2], points[len - 1], ...points, points[0], points[1], points[2], points[3]]
len = points.length
}
for (let i = 2; i < len - 2; i += 2) {
aX = points[i - 2]
aY = points[i - 1]
bX = points[i]
bY = points[i + 1]
cX = points[i + 2]
cY = points[i + 3]
ba = sqrt(pow(bX - aX, 2) + pow(bY - aY, 2))
cb = sqrt(pow(cX - bX, 2) + pow(cY - bY, 2))
d = ba + cb
ba = (t * ba) / d
cb = (t * cb) / d
cX -= aX
cY -= aY
c1X = bX - ba * cX
c1Y = bY - ba * cY
if (i === 2) {
if (!close) data.push(Q, c1X, c1Y, bX, bY)
} else {
data.push(C, c2X, c2Y, c1X, c1Y, bX, bY)
}
c2X = bX + cb * cX
c2Y = bY + cb * cY
}
if (!close) data.push(Q, c2X, c2Y, points[len - 2], points[len - 1])
} else {
for (let i = 2, len = points.length; i < len; i += 2) {
data.push(L, points[i], points[i + 1])
}
}
if (close) data.push(Z)
},
rect(data: IPathCommandData, x: number, y: number, width: number, height: number) {
PathHelper.creator.path = data
PathHelper.creator.moveTo(x, y).lineTo(x + width, y).lineTo(x + width, y + height).lineTo(x, y + height).lineTo(x, y)
},
roundRect(data: IPathCommandData, x: number, y: number, width: number, height: number, radius: number | number[]): void {
PathHelper.creator.path = []
RectHelper.drawRoundRect(PathHelper.creator, x, y, width, height, radius)
data.push(...PathHelper.convertToCanvasData(PathHelper.creator.path, true))
},
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 {
const BAx = x1 - fromX
const BAy = y1 - fromY
const CBx = toX - x1
const CBy = toY - y1
let startRadian = atan2(BAy, BAx)
let endRadian = atan2(CBy, CBx)
let totalRadian = endRadian - startRadian
if (totalRadian < 0) totalRadian += PI2
if (totalRadian === PI || (abs(BAx + BAy) < 1.e-12) || (abs(CBx + CBy) < 1.e-12)) { // invalid
if (data) data.push(L, x1, y1)
if (setPointBounds) {
setPoint(setPointBounds, fromX, fromY)
addPoint(setPointBounds, x1, y1)
}
if (setStartPoint) set(setStartPoint, fromX, fromY)
if (setEndPoint) set(setEndPoint, x1, y1)
return
}
const anticlockwise = BAx * CBy - CBx * BAy < 0
const sign = anticlockwise ? -1 : 1
const c = radius / cos(totalRadian / 2)
const centerX = x1 + c * cos(startRadian + totalRadian / 2 + PI_2 * sign)
const centerY = y1 + c * sin(startRadian + totalRadian / 2 + PI_2 * sign)
startRadian -= PI_2 * sign
endRadian -= PI_2 * sign
return ellipse(data, centerX, centerY, radius, radius, 0, startRadian / OneRadian, endRadian / OneRadian, anticlockwise, setPointBounds, setEndPoint, setStartPoint)
},
arc(data: IPathCommandData | null | void, x: number, y: number, radius: number, startAngle: number, endAngle: number, anticlockwise?: boolean, setPointBounds?: ITwoPointBoundsData, setEndPoint?: IPointData, setStartPoint?: IPointData): void {
return ellipse(data, x, y, radius, radius, 0, startAngle, endAngle, anticlockwise, setPointBounds, setEndPoint, setStartPoint)
},
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 {
const rotationRadian = rotation * OneRadian
const rotationSin = sin(rotationRadian)
const rotationCos = cos(rotationRadian)
let startRadian = startAngle * OneRadian
let endRadian = endAngle * OneRadian
if (startRadian > PI) startRadian -= PI2
if (endRadian < 0) endRadian += PI2
let totalRadian = endRadian - startRadian
if (totalRadian < 0) totalRadian += PI2
else if (totalRadian > PI2) totalRadian -= PI2
if (anticlockwise) totalRadian -= PI2
const parts = ceil(abs(totalRadian / PI_2))
const partRadian = totalRadian / parts
const partRadian4Sin = sin(partRadian / 4)
const control = 8 / 3 * partRadian4Sin * partRadian4Sin / sin(partRadian / 2)
endRadian = startRadian + partRadian
let startCos = cos(startRadian)
let startSin = sin(startRadian)
let endCos: number, endSin: number
let x: number, y: number, x1: number, y1: number, x2: number, y2: number
let startX = x = rotationCos * radiusX * startCos - rotationSin * radiusY * startSin
let startY = y = rotationSin * radiusX * startCos + rotationCos * radiusY * startSin
let fromX = cx + x, fromY = cy + y
if (data) data.push(L, fromX, fromY)
if (setPointBounds) setPoint(setPointBounds, fromX, fromY)
if (setStartPoint) set(setStartPoint, fromX, fromY)
for (let i = 0; i < parts; i++) {
endCos = cos(endRadian)
endSin = sin(endRadian)
x = rotationCos * radiusX * endCos - rotationSin * radiusY * endSin
y = rotationSin * radiusX * endCos + rotationCos * radiusY * endSin
x1 = cx + startX - control * (rotationCos * radiusX * startSin + rotationSin * radiusY * startCos)
y1 = cy + startY - control * (rotationSin * radiusX * startSin - rotationCos * radiusY * startCos)
x2 = cx + x + control * (rotationCos * radiusX * endSin + rotationSin * radiusY * endCos)
y2 = cy + y + control * (rotationSin * radiusX * endSin - rotationCos * radiusY * endCos)
if (data) data.push(C, x1, y1, x2, y2, cx + x, cy + y)
if (setPointBounds) toTwoPointBounds(cx + startX, cy + startY, x1, y1, x2, y2, cx + x, cy + y, setPointBounds, true)
startX = x
startY = y
startCos = endCos
startSin = endSin
startRadian = endRadian
endRadian += partRadian
}
if (setEndPoint) set(setEndPoint, cx + x, cy + y)
},
quadraticCurveTo(data: IPathCommandData, fromX: number, fromY: number, x1: number, y1: number, toX: number, toY: number): void {
data.push(C, (fromX + 2 * x1) / 3, (fromY + 2 * y1) / 3, (toX + 2 * x1) / 3, (toY + 2 * y1) / 3, toX, toY)
},
toTwoPointBoundsByQuadraticCurve(fromX: number, fromY: number, x1: number, y1: number, toX: number, toY: number, pointBounds: ITwoPointBoundsData, addMode?: boolean): void {
toTwoPointBounds(fromX, fromY, (fromX + 2 * x1) / 3, (fromY + 2 * y1) / 3, (toX + 2 * x1) / 3, (toY + 2 * y1) / 3, toX, toY, pointBounds, addMode)
},
toTwoPointBounds(fromX: number, fromY: number, x1: number, y1: number, x2: number, y2: number, toX: number, toY: number, pointBounds: ITwoPointBoundsData, addMode?: boolean): void {
const tList = []
let a, b, c, t, t1, t2, v, sqrtV
let f = fromX, z1 = x1, z2 = x2, o = toX
for (let i = 0; i < 2; ++i) {
if (i == 1) {
f = fromY, z1 = y1, z2 = y2, o = toY
}
a = -3 * f + 9 * z1 - 9 * z2 + 3 * o
b = 6 * f - 12 * z1 + 6 * z2
c = 3 * z1 - 3 * f
if (Math.abs(a) < 1e-12) {
if (Math.abs(b) < 1e-12) continue
t = -c / b
if (0 < t && t < 1) tList.push(t)
continue
}
v = b * b - 4 * c * a
sqrtV = Math.sqrt(v)
if (v < 0) continue
t1 = (-b + sqrtV) / (2 * a)
if (0 < t1 && t1 < 1) tList.push(t1)
t2 = (-b - sqrtV) / (2 * a)
if (0 < t2 && t2 < 1) tList.push(t2)
}
addMode ? addPoint(pointBounds, fromX, fromY) : setPoint(pointBounds, fromX, fromY)
addPoint(pointBounds, toX, toY)
for (let i = 0, len = tList.length; i < len; i++) {
getPointAndSet(tList[i], fromX, fromY, x1, y1, x2, y2, toX, toY, tempPoint)
addPoint(pointBounds, tempPoint.x, tempPoint.y)
}
},
getPointAndSet(t: number, fromX: number, fromY: number, x1: number, y1: number, x2: number, y2: number, toX: number, toY: number, setPoint: IPointData): void {
const o = 1 - t, a = o * o * o, b = 3 * o * o * t, c = 3 * o * t * t, d = t * t * t
setPoint.x = a * fromX + b * x1 + c * x2 + d * toX
setPoint.y = a * fromY + b * y1 + c * y2 + d * toY
},
getPoint(t: number, fromX: number, fromY: number, x1: number, y1: number, x2: number, y2: number, toX: number, toY: number): IPointData {
const point = {} as IPointData
getPointAndSet(t, fromX, fromY, x1, y1, x2, y2, toX, toY, point)
return point
}
}
const { getPointAndSet, toTwoPointBounds, ellipse } = BezierHelper
import { IPathCommandData } from '@leafer/interface'
import { OneRadian, PI2 } from '@leafer/math'
import { PathCommandMap } from './PathCommandMap'
import { BezierHelper } from './BezierHelper'
const { sin, cos, sqrt, atan2 } = Math
const { ellipse } = BezierHelper
export const EllipseHelper = {
// svg
ellipticalArc(data: IPathCommandData, fromX: number, fromY: number, radiusX: number, radiusY: number, rotation: number, largeFlag: number, sweepFlag: number, toX: number, toY: number, curveMode?: boolean): void {
const halfX = (toX - fromX) / 2
const halfY = (toY - fromY) / 2
const rotationRadian = rotation * OneRadian
const rotationSin = sin(rotationRadian)
const rotationCos = cos(rotationRadian)
const px = -rotationCos * halfX - rotationSin * halfY
const py = -rotationCos * halfY + rotationSin * halfX
const rxSquare = radiusX * radiusX
const rySquare = radiusY * radiusY
const pySquare = py * py
const pxSquare = px * px
const a = rxSquare * rySquare - rxSquare * pySquare - rySquare * pxSquare
let s = 0
if (a < 0) {
const t = sqrt(1 - a / (rxSquare * rySquare))
radiusX *= t
radiusY *= t
} else {
s = (largeFlag === sweepFlag ? -1 : 1) * sqrt(a / (rxSquare * pySquare + rySquare * pxSquare))
}
const cx = s * radiusX * py / radiusY
const cy = -s * radiusY * px / radiusX
const startRadian = atan2((py - cy) / radiusY, (px - cx) / radiusX)
const endRadian = atan2((-py - cy) / radiusY, (-px - cx) / radiusX)
let totalRadian = endRadian - startRadian
if (sweepFlag === 0 && totalRadian > 0) {
totalRadian -= PI2
} else if (sweepFlag === 1 && totalRadian < 0) {
totalRadian += PI2
}
const centerX = fromX + halfX + rotationCos * cx - rotationSin * cy
const centerY = fromY + halfY + rotationSin * cx + rotationCos * cy
const anticlockwise = totalRadian < 0 ? 1 : 0
if (curveMode) {
ellipse(data, centerX, centerY, radiusX, radiusY, rotation, startRadian / OneRadian, endRadian / OneRadian, anticlockwise as unknown as boolean)
} else {
if (radiusX === radiusY && !rotation) {
data.push(PathCommandMap.O, centerX, centerY, radiusX, startRadian / OneRadian, endRadian / OneRadian, anticlockwise)
} else {
data.push(PathCommandMap.G, centerX, centerY, radiusX, radiusY, rotation, startRadian / OneRadian, endRadian / OneRadian, anticlockwise)
}
}
}
}
import { ITwoPointBoundsData, IPathCommandData, IBoundsData, IPointData } from '@leafer/interface'
import { TwoPointBoundsHelper } from '@leafer/math'
import { Debug } from '@leafer/debug'
import { BezierHelper } from './BezierHelper'
import { PathCommandMap as Command } from './PathCommandMap'
const { M, L, C, Q, Z, N, D, X, G, F, O, P, U } = Command
const { toTwoPointBounds, toTwoPointBoundsByQuadraticCurve, arcTo, arc, ellipse } = BezierHelper
const { add, copy, addPoint, setPoint, addBounds, toBounds } = TwoPointBoundsHelper
const debug = Debug.get('PathBounds')
let radius: number, radiusX: number, radiusY: number
const tempPointBounds = {} as ITwoPointBoundsData
const setPointBounds = {} as ITwoPointBoundsData
const setEndPoint = {} as IPointData
export const PathBounds = {
toBounds(data: IPathCommandData, setBounds: IBoundsData): void {
PathBounds.toTwoPointBounds(data, setPointBounds)
toBounds(setPointBounds, setBounds)
},
toTwoPointBounds(data: IPathCommandData, setPointBounds: ITwoPointBoundsData): void {
if (!data || !data.length) return setPoint(setPointBounds, 0, 0)
let command: number
let i: number = 0, x: number = 0, y: number = 0, x1: number, y1: number, toX: number, toY: number
const len = data.length
while (i < len) {
command = data[i]
if (i === 0) {
if (command === Z || command === C || command === Q) {
setPoint(setPointBounds, x, y)
} else {
setPoint(setPointBounds, data[i + 1], data[i + 2])
}
}
switch (command) {
case M: //moveto(x, y)
case L: //lineto(x, y)
x = data[i + 1]
y = data[i + 2]
addPoint(setPointBounds, x, y)
i += 3
break
case C: //bezierCurveTo(x1, y1, x2, y2, x,y)
toX = data[i + 5]
toY = data[i + 6]
toTwoPointBounds(x, y, data[i + 1], data[i + 2], data[i + 3], data[i + 4], toX, toY, tempPointBounds)
add(setPointBounds, tempPointBounds)
x = toX
y = toY
i += 7
break
case Q: //quadraticCurveTo(x1, y1, x, y)
x1 = data[i + 1]
y1 = data[i + 2]
toX = data[i + 3]
toY = data[i + 4]
toTwoPointBoundsByQuadraticCurve(x, y, x1, y1, toX, toY, tempPointBounds)
add(setPointBounds, tempPointBounds)
x = toX
y = toY
i += 5
break
case Z: //closepath()
i += 1
break
// canvas command
case N: // rect(x, y, width, height)
x = data[i + 1]
y = data[i + 2]
addBounds(setPointBounds, x, y, data[i + 3], data[i + 4])
i += 5
break
case D: // roundRect(x, y, width, height, radius1, radius2, radius3, radius4)
case X: // simple roundRect(x, y, width, height, radius)
x = data[i + 1]
y = data[i + 2]
addBounds(setPointBounds, x, y, data[i + 3], data[i + 4])
i += (command === D ? 9 : 6)
break
case G: // ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)
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)
i === 0 ? copy(setPointBounds, tempPointBounds) : add(setPointBounds, tempPointBounds)
x = setEndPoint.x
y = setEndPoint.y
i += 9
break
case F: // simple ellipse(x, y, radiusX, radiusY)
x = data[i + 1]
y = data[i + 2]
radiusX = data[i + 3]
radiusY = data[i + 4]
addBounds(setPointBounds, x - radiusX, y - radiusY, radiusX * 2, radiusY * 2)
x += radiusX
i += 5
break
case O: // arc(x, y, radius, startAngle, endAngle, anticlockwise)
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)
i === 0 ? copy(setPointBounds, tempPointBounds) : add(setPointBounds, tempPointBounds)
x = setEndPoint.x
y = setEndPoint.y
i += 7
break
case P: // simple arc(x, y, radius)
x = data[i + 1]
y = data[i + 2]
radius = data[i + 3]
addBounds(setPointBounds, x - radius, y - radius, radius * 2, radius * 2)
x += radius
i += 4
break
case U: // arcTo(x1, y1, x2, y2, radius)
arcTo(null, x, y, data[i + 1], data[i + 2], data[i + 3], data[i + 4], data[i + 5], tempPointBounds, setEndPoint)
i === 0 ? copy(setPointBounds, tempPointBounds) : add(setPointBounds, tempPointBounds)
x = setEndPoint.x
y = setEndPoint.y
i += 6
break
default:
debug.error(`command: ${command} [index:${i}]`, data)
return
}
}
}
}
import { IPathCommandData, IPointData } from '@leafer/interface'
import { PathCommandMap } from './PathCommandMap'
import { BezierHelper } from './BezierHelper'
import { MathHelper } from '@leafer/math'
const { M, L, C, Q, Z, N, D, X, G, F, O, P, U } = PathCommandMap
const startPoint = {} as IPointData
export const PathCommandDataHelper = {
beginPath(data: IPathCommandData): void {
data.length = 0
},
// svg and canvas
moveTo(data: IPathCommandData, x: number, y: number): void {
data.push(M, x, y)
},
lineTo(data: IPathCommandData, x: number, y: number): void {
data.push(L, x, y)
},
bezierCurveTo(data: IPathCommandData, x1: number, y1: number, x2: number, y2: number, x: number, y: number): void {
data.push(C, x1, y1, x2, y2, x, y)
},
quadraticCurveTo(data: IPathCommandData, x1: number, y1: number, x: number, y: number): void {
data.push(Q, x1, y1, x, y)
},
closePath(data: IPathCommandData): void {
data.push(Z)
},
// canvas
rect(data: IPathCommandData, x: number, y: number, width: number, height: number): void {
data.push(N, x, y, width, height)
},
roundRect(data: IPathCommandData, x: number, y: number, width: number, height: number, cornerRadius: number | number[]): void {
if (typeof cornerRadius === 'number') {
data.push(X, x, y, width, height, cornerRadius)
} else {
const fourCorners = MathHelper.fourNumber(cornerRadius)
if (fourCorners) {
data.push(D, x, y, width, height, ...fourCorners)
} else {
data.push(N, x, y, width, height)
}
}
},
ellipse(data: IPathCommandData, x: number, y: number, radiusX: number, radiusY: number, rotation?: number, startAngle?: number, endAngle?: number, anticlockwise?: boolean): void {
if (rotation === undefined) {
data.push(F, x, y, radiusX, radiusY)
} else {
if (startAngle === undefined) startAngle = 0
if (endAngle === undefined) endAngle = 360
data.push(G, x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise ? 1 : 0)
}
},
arc(data: IPathCommandData, x: number, y: number, radius: number, startAngle?: number, endAngle?: number, anticlockwise?: boolean): void {
if (startAngle === undefined) {
data.push(P, x, y, radius)
} else {
if (endAngle === undefined) endAngle = 360
data.push(O, x, y, radius, startAngle, endAngle, anticlockwise ? 1 : 0)
}
},
arcTo(data: IPathCommandData, x1: number, y1: number, x2: number, y2: number, radius: number): void {
data.push(U, x1, y1, x2, y2, radius)
},
// new
drawEllipse(data: IPathCommandData, x: number, y: number, radiusX: number, radiusY: number, rotation?: number, startAngle?: number, endAngle?: number, anticlockwise?: boolean): void {
if (rotation === undefined) rotation = 0
if (startAngle === undefined) startAngle = 0
if (endAngle === undefined) endAngle = 360
BezierHelper.ellipse(null, x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise, null, null, startPoint)
data.push(M, startPoint.x, startPoint.y)
ellipse(data, x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)
},
drawArc(data: IPathCommandData, x: number, y: number, radius: number, startAngle?: number, endAngle?: number, anticlockwise?: boolean): void {
if (startAngle === undefined) startAngle = 0
if (endAngle === undefined) endAngle = 360
BezierHelper.arc(null, x, y, radius, startAngle, endAngle, anticlockwise, null, null, startPoint)
data.push(M, startPoint.x, startPoint.y)
arc(data, x, y, radius, startAngle, endAngle, anticlockwise)
},
drawPoints(data: IPathCommandData, points: number[], curve?: boolean | number, close?: boolean): void {
BezierHelper.points(data, points, curve, close)
}
}
const { ellipse, arc } = PathCommandDataHelper
import { INumberMap, IStringMap } from '@leafer/interface'
export const CanvasCommandOnlyMap: INumberMap = {
N: 21, // rect
D: 22, // roundRect
X: 23, // simple roundRect
G: 24, // ellipse
F: 25, // simple ellipse
O: 26, // arc
P: 27, // simple arc
U: 28 // arcTo
}
export const PathCommandMap: INumberMap = {
// svg and canvas
M: 1, // moveto
m: 10,
L: 2, // lineto
l: 20,
H: 3, // horizontal lineto
h: 30,
V: 4, // vertical lineto
v: 40,
C: 5, // curveto
c: 50,
S: 6, // smooth curveto
s: 60,
Q: 7, // quadratic Belzier curve
q: 70,
T: 8, // smooth quadratic Belzier curveto
t: 80,
A: 9, //e lliptical Arc
a: 90,
Z: 11, // closepath
z: 11,
R: 12, // Catmull Rom
// canvas
...CanvasCommandOnlyMap
}
export const PathCommandLengthMap: INumberMap = {
M: 3, //moveto
m: 3,
L: 3, //lineto
l: 3,
H: 2, //horizontal lineto
h: 2,
V: 2, //vertical lineto
v: 2,
C: 7, //curveto
c: 7,
S: 5, //smooth curveto
s: 5,
Q: 5, //quadratic Belzier curve
q: 5,
T: 3, //smooth quadratic Belzier curveto
t: 3,
A: 8, //elliptical Arc
a: 8,
Z: 1, //closepath
z: 1,
// canvas
N: 5, // rect
D: 9, // roundRect
X: 6, // simple roundRect
G: 9, // ellipse
F: 5, // simple ellipse
O: 7, // arc
P: 4, // simple arc
U: 6 // arcTo
}
export const NeedConvertToCanvasCommandMap: INumberMap = { // convert to: M L C Q Z
// M: 1, //moveto
m: 10,
// L: 2, //lineto
l: 20,
H: 3, //horizontal lineto
h: 30,
V: 4, //vertical lineto
v: 40,
// C: 5, //curveto
c: 50,
S: 6, //smooth curveto
s: 60,
// Q: 7, //quadratic Belzier curve
q: 70,
T: 8, //smooth quadratic Belzier curveto
t: 80,
A: 9, //elliptical Arc
a: 90,
// Z: 11, //closepath
// z: 11
}
export const NeedConvertToCurveCommandMap: INumberMap = {
...NeedConvertToCanvasCommandMap,
...CanvasCommandOnlyMap
}
const P = PathCommandMap
export const PathNumberCommandMap: IStringMap = {}
for (let key in P) {
PathNumberCommandMap[P[key]] = key
}
// {1: 'M'}
export const PathNumberCommandLengthMap: INumberMap = {}
for (let key in P) {
PathNumberCommandLengthMap[P[key]] = PathCommandLengthMap[key]
}
// {1: 3}
import { IPathCommandData, IPointData } from '@leafer/interface'
import { StringNumberMap } from '@leafer/math'
import { Debug } from '@leafer/debug'
import { PathCommandMap as Command, NeedConvertToCanvasCommandMap, NeedConvertToCurveCommandMap, PathCommandLengthMap, PathNumberCommandMap, PathNumberCommandLengthMap } from './PathCommandMap'
import { BezierHelper } from './BezierHelper'
import { EllipseHelper } from './EllipseHelper'
interface ICurrentCommand {
name?: number
length?: number
index?: number
dot?: number
}
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
const { rect, roundRect, arcTo, arc, ellipse, quadraticCurveTo } = BezierHelper
const { ellipticalArc } = EllipseHelper
const debug = Debug.get('PathConvert')
const setEndPoint = {} as IPointData
export const PathConvert = {
current: { dot: 0 } as ICurrentCommand,
stringify(data: IPathCommandData): string {
let i = 0, len = data.length, count: number, str: string = '', command: number, lastCommand: number
while (i < len) {
command = data[i]
count = PathNumberCommandLengthMap[command]
if (command === lastCommand) {
str += ' ' // 重复的命令可以省略
} else {
str += PathNumberCommandMap[command]
}
for (let j = 1; j < count; j++) {
str += data[i + j];
(j === count - 1) || (str += ' ')
}
lastCommand = command
i += count
}
return str
},
parse(pathString: string, curveMode?: boolean): IPathCommandData {
let needConvert: boolean, char: string, lastChar: string, num = ''
const data: IPathCommandData = []
const convertCommand = curveMode ? NeedConvertToCurveCommandMap : NeedConvertToCanvasCommandMap
for (let i = 0, len = pathString.length; i < len; i++) {
char = pathString[i]
if (StringNumberMap[char]) {
if (char === '.') {
current.dot++
if (current.dot > 1) {
pushData(data, num); num = '' // .375.375
}
}
num += char
} else if (Command[char]) {
if (num) { pushData(data, num); num = '' }
current.name = Command[char]
current.length = PathCommandLengthMap[char]
current.index = 0
pushData(data, current.name)
if (!needConvert && convertCommand[char]) needConvert = true
} else {
if (char === '-' || char === '+') {
if (lastChar === 'e' || lastChar === 'E') { // L45e-12 21e+22
num += char
} else {
if (num) pushData(data, num) // L-34-35 L+12+28
num = char
}
} else {
if (num) { pushData(data, num); num = '' }
}
}
lastChar = char
}
if (num) pushData(data, num)
return needConvert ? PathConvert.toCanvasData(data, curveMode) : data
},
toCanvasData(old: IPathCommandData, curveMode?: boolean): IPathCommandData {
let x = 0, y = 0, x1 = 0, y1 = 0, i = 0, len = old.length, controlX: number, controlY: number, command: number, lastCommand: number, smooth: boolean
const data: IPathCommandData = []
while (i < len) {
command = old[i]
switch (command) {
//moveto(x, y)
case m:
old[i + 1] += x
old[i + 2] += y
case M:
x = old[i + 1]
y = old[i + 2]
data.push(M, x, y)
i += 3
break
//horizontal lineto(x)
case h:
old[i + 1] += x
case H:
x = old[i + 1]
data.push(L, x, y)
i += 2
break
//vertical lineto(y)
case v:
old[i + 1] += y
case V:
y = old[i + 1]
data.push(L, x, y)
i += 2
break
//lineto(x,y)
case l:
old[i + 1] += x
old[i + 2] += y
case L:
x = old[i + 1]
y = old[i + 2]
data.push(L, x, y)
i += 3
break
//smooth bezierCurveTo(x2, y2, x, y)
case s: //smooth
old[i + 1] += x
old[i + 2] += y
old[i + 3] += x
old[i + 4] += y
command = S
case S:
smooth = (lastCommand === C) || (lastCommand === S)
x1 = smooth ? (x * 2 - controlX) : old[i + 1]
y1 = smooth ? (y * 2 - controlY) : old[i + 2]
controlX = old[i + 1]
controlY = old[i + 2]
x = old[i + 3]
y = old[i + 4]
data.push(C, x1, y1, controlX, controlY, x, y)
i += 5
break
//bezierCurveTo(x1, y1, x2, y2, x, y)
case c:
old[i + 1] += x
old[i + 2] += y
old[i + 3] += x
old[i + 4] += y
old[i + 5] += x
old[i + 6] += y
command = C
case C:
controlX = old[i + 3]
controlY = old[i + 4]
x = old[i + 5]
y = old[i + 6]
data.push(C, old[i + 1], old[i + 2], controlX, controlY, x, y)
i += 7
break
//smooth quadraticCurveTo(x, y)
case t:
old[i + 1] += x
old[i + 2] += y
command = T
case T: //smooth
smooth = (lastCommand === Q) || (lastCommand === T)
controlX = smooth ? (x * 2 - controlX) : old[i + 1]
controlY = smooth ? (y * 2 - controlY) : old[i + 2]
curveMode ? quadraticCurveTo(data, x, y, controlX, controlY, old[i + 1], old[i + 2]) : data.push(Q, controlX, controlY, old[i + 1], old[i + 2])
x = old[i + 1]
y = old[i + 2]
i += 3
break
//quadraticCurveTo(x1, y1, x, y)
case q:
old[i + 1] += x
old[i + 2] += y
old[i + 3] += x
old[i + 4] += y
command = Q
case Q:
controlX = old[i + 1]
controlY = old[i + 2]
curveMode ? quadraticCurveTo(data, x, y, controlX, controlY, old[i + 3], old[i + 4]) : data.push(Q, controlX, controlY, old[i + 3], old[i + 4])
x = old[i + 3]
y = old[i + 4]
i += 5
break
//ellipticalArc(rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y)
case a:
old[i + 6] += x
old[i + 7] += y
case A:
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
x = old[i + 6]
y = old[i + 7]
i += 8
break
case z:
case Z:
data.push(Z)
i++
break
// canvas command
case N: // rect(x, y, width, height)
x = old[i + 1]
y = old[i + 2]
curveMode ? rect(data, x, y, old[i + 3], old[i + 4]) : copyData(data, old, i, 5)
i += 5
break
case D: // roundRect(x, y, width, height, radius1, radius2, radius3, radius4)
x = old[i + 1]
y = old[i + 2]
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)
i += 9
break
case X: // simple roundRect(x, y, width, height, radius)
x = old[i + 1]
y = old[i + 2]
curveMode ? roundRect(data, x, y, old[i + 3], old[i + 4], old[i + 5]) : copyData(data, old, i, 6)
i += 6
break
case G: // ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)
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)
x = setEndPoint.x
y = setEndPoint.y
i += 9
break
case F: // simple ellipse(x, y, radiusX, radiusY)
curveMode ? ellipse(data, old[i + 1], old[i + 2], old[i + 3], old[i + 4], 0, 0, 360, false) : copyData(data, old, i, 5)
x = old[i + 1] + old[i + 3]
y = old[i + 2]
i += 5
break
case O: // arc(x, y, radius, startAngle, endAngle, anticlockwise)
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)
x = setEndPoint.x
y = setEndPoint.y
i += 7
break
case P: // simple arc(x, y, radius)
curveMode ? arc(data, old[i + 1], old[i + 2], old[i + 3], 0, 360, false) : copyData(data, old, i, 4)
x = old[i + 1] + old[i + 3]
y = old[i + 2]
i += 4
break
case U: // arcTo(x1, y1, x2, y2, radius)
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)
x = setEndPoint.x
y = setEndPoint.y
i += 6
break
default:
debug.error(`command: ${command} [index:${i}]`, old)
return data
}
lastCommand = command
}
return data
},
copyData(data: IPathCommandData, old: IPathCommandData, index: number, count: number): void {
for (let i = index, end = index + count; i < end; i++) {
data.push(old[i])
}
},
pushData(data: IPathCommandData, strNum: string | number) {
if (current.index === current.length) { // 单个命令,多个数据的情况
current.index = 1
data.push(current.name)
}
data.push(Number(strNum))
current.index++
current.dot = 0
}
}
const { current, pushData, copyData } = PathConvert
import { IPathCommandData } from '@leafer/interface'
export const PathCorner = {
smooth(data: IPathCommandData, _cornerRadius: number, _cornerSmoothing?: number): IPathCommandData {
return data
}
}
import { IPathCommandData, IPathDrawer, IPathString } from '@leafer/interface'
import { PathCommandDataHelper } from './PathCommandDataHelper'
import { PathHelper } from './PathHelper'
const { moveTo, lineTo, quadraticCurveTo, bezierCurveTo, closePath, beginPath, rect, roundRect, ellipse, arc, arcTo, drawEllipse, drawArc, drawPoints } = PathCommandDataHelper
export class PathCreator implements IPathDrawer {
public path: IPathCommandData
constructor(path?: IPathCommandData | IPathString) {
if (path) {
this.path = typeof path === 'string' ? PathHelper.parse(path) : path
} else {
this.path = []
}
}
public beginPath(): PathCreator {
beginPath(this.path)
return this
}
// svg and canvas
public moveTo(x: number, y: number): PathCreator {
moveTo(this.path, x, y)
return this
}
public lineTo(x: number, y: number): PathCreator {
lineTo(this.path, x, y)
return this
}
public bezierCurveTo(x1: number, y1: number, x2: number, y2: number, x: number, y: number): PathCreator {
bezierCurveTo(this.path, x1, y1, x2, y2, x, y)
return this
}
public quadraticCurveTo(x1: number, y1: number, x: number, y: number): PathCreator {
quadraticCurveTo(this.path, x1, y1, x, y)
return this
}
public closePath(): PathCreator {
closePath(this.path)
return this
}
// canvas
public rect(x: number, y: number, width: number, height: number): PathCreator {
rect(this.path, x, y, width, height)
return this
}
public roundRect(x: number, y: number, width: number, height: number, cornerRadius: number | number[]): PathCreator {
roundRect(this.path, x, y, width, height, cornerRadius)
return this
}
public ellipse(x: number, y: number, radiusX: number, radiusY: number, rotation?: number, startAngle?: number, endAngle?: number, anticlockwise?: boolean): PathCreator {
ellipse(this.path, x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)
return this
}
public arc(x: number, y: number, radius: number, startAngle?: number, endAngle?: number, anticlockwise?: boolean): PathCreator {
arc(this.path, x, y, radius, startAngle, endAngle, anticlockwise)
return this
}
public arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): PathCreator {
arcTo(this.path, x1, y1, x2, y2, radius)
return this
}
// moveTo, then draw
public drawEllipse(x: number, y: number, radiusX: number, radiusY: number, rotation?: number, startAngle?: number, endAngle?: number, anticlockwise?: boolean): PathCreator {
drawEllipse(this.path, x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)
return this
}
public drawArc(x: number, y: number, radius: number, startAngle?: number, endAngle?: number, anticlockwise?: boolean): PathCreator {
drawArc(this.path, x, y, radius, startAngle, endAngle, anticlockwise)
return this
}
public drawPoints(points: number[], curve?: boolean | number, close?: boolean): PathCreator {
drawPoints(this.path, points, curve, close)
return this
}
}
import { IPathDrawer, IPathCommandData } from '@leafer/interface'
import { OneRadian, PI2 } from '@leafer/math'
import { Debug } from '@leafer/debug'
import { PathCommandMap as Command } from './PathCommandMap'
const { M, L, C, Q, Z, N, D, X, G, F, O, P, U } = Command
const debug = Debug.get('PathDrawer')
export const PathDrawer = {
drawPathByData(drawer: IPathDrawer, data: IPathCommandData): void {
if (!data) return
let command: number
let i = 0, len = data.length
while (i < len) {
command = data[i]
switch (command) {
case M: //moveto(x, y)
drawer.moveTo(data[i + 1], data[i + 2])
i += 3
break
case L: //lineto(x, y)
drawer.lineTo(data[i + 1], data[i + 2])
i += 3
break
case C: //bezierCurveTo(x1, y1, x2, y2, x, y)
drawer.bezierCurveTo(data[i + 1], data[i + 2], data[i + 3], data[i + 4], data[i + 5], data[i + 6])
i += 7
break
case Q: //quadraticCurveTo(x1, y1, x, y)
drawer.quadraticCurveTo(data[i + 1], data[i + 2], data[i + 3], data[i + 4])
i += 5
break
case Z: //closepath()
drawer.closePath()
i += 1
break
// canvas command
case N: // rect(x, y, width, height)
drawer.rect(data[i + 1], data[i + 2], data[i + 3], data[i + 4])
i += 5
break
case D: // roundRect(x, y, width, height, radius1, radius2, radius3, radius4)
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]])
i += 9
break
case X: // simple roundRect(x, y, width, height, radius)
drawer.roundRect(data[i + 1], data[i + 2], data[i + 3], data[i + 4], data[i + 5])
i += 6
break
case G: // ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)
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)
i += 9
break
case F: // simple ellipse(x, y, radiusX, radiusY)
drawer.ellipse(data[i + 1], data[i + 2], data[i + 3], data[i + 4], 0, 0, PI2, false)
i += 5
break
case O: // arc(x, y, radius, startAngle, endAngle, anticlockwise)
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)
i += 7
break
case P: // simple arc(x, y, radius)
drawer.arc(data[i + 1], data[i + 2], data[i + 3], 0, PI2, false)
i += 4
break
case U: // arcTo(x1, y1, x2, y2, radius)
drawer.arcTo(data[i + 1], data[i + 2], data[i + 3], data[i + 4], data[i + 5])
i += 6
break
default:
debug.error(`command: ${command} [index:${i}]`, data)
return
}
}
}
}
import { IPathCommandData, IPathCreator } from '@leafer/interface'
export const PathHelper = {
// index.ts rewrite
creator: {} as IPathCreator,
parse(_pathString: string, _curveMode?: boolean): IPathCommandData { return undefined },
convertToCanvasData(_old: IPathCommandData, _curveMode?: boolean): IPathCommandData { return undefined }
}
import { IPathDrawer } from '@leafer/interface'
import { MathHelper } from '@leafer/math'
export const RectHelper = {
drawRoundRect(drawer: IPathDrawer, x: number, y: number, width: number, height: number, cornerRadius: number | number[]): void {
let [topLeft, topRight, bottomRight, bottomLeft] = MathHelper.fourNumber(cornerRadius)
const max = Math.min(width / 2, height / 2)
if (topLeft > max) topLeft = max
if (topRight > max) topRight = max
if (bottomRight > max) bottomRight = max
if (bottomLeft > max) bottomLeft = max
topLeft ? drawer.moveTo(x + topLeft, y) : drawer.moveTo(x, y)
topRight ? drawer.arcTo(x + width, y, x + width, y + height, topRight) : drawer.lineTo(x + width, y)
bottomRight ? drawer.arcTo(x + width, y + height, x, y + height, bottomRight) : drawer.lineTo(x + width, y + height)
bottomLeft ? drawer.arcTo(x, y + height, x, y, bottomLeft) : drawer.lineTo(x, y + height)
topLeft ? drawer.arcTo(x, y, x + width, y, topLeft) : drawer.lineTo(x, y)
}
}
+5
-4
{
"name": "@leafer/path",
"version": "1.0.0-beta.15",
"version": "1.0.0-beta.16",
"description": "@leafer/path",

@@ -10,2 +10,3 @@ "author": "Chao (Leafer) Wan",

"files": [
"src",
"types",

@@ -25,8 +26,8 @@ "dist"

"dependencies": {
"@leafer/math": "1.0.0-beta.15",
"@leafer/debug": "1.0.0-beta.15"
"@leafer/math": "1.0.0-beta.16",
"@leafer/debug": "1.0.0-beta.16"
},
"devDependencies": {
"@leafer/interface": "1.0.0-beta.15"
"@leafer/interface": "1.0.0-beta.16"
}
}