@leafer/path
Advanced tools
| 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) | ||
| } | ||
| }, | ||
| moveToEllipse(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) | ||
| }, | ||
| moveToArc(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) | ||
| }, | ||
| arcTo(data: IPathCommandData, x1: number, y1: number, x2: number, y2: number, radius: number): void { | ||
| data.push(U, x1, y1, x2, y2, radius) | ||
| } | ||
| } | ||
| const { ellipse, arc } = PathCommandDataHelper |
| import { IPathCommandData } from '@leafer/interface' | ||
| export const PathCorner = { | ||
| smooth(data: IPathCommandData, _cornerRadius: number, _cornerSmoothing?: number): IPathCommandData { | ||
| return data | ||
| } | ||
| } |
| 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 { 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) | ||
| } | ||
| } |
+4
-3
| { | ||
| "name": "@leafer/path", | ||
| "version": "1.0.0-alpha.23", | ||
| "version": "1.0.0-alpha.30", | ||
| "description": "@leafer/path", | ||
@@ -22,7 +22,8 @@ "author": "Chao (Leafer) Wan", | ||
| "dependencies": { | ||
| "@leafer/math": "1.0.0-alpha.23" | ||
| "@leafer/math": "1.0.0-alpha.30", | ||
| "@leafer/debug": "1.0.0-alpha.30" | ||
| }, | ||
| "devDependencies": { | ||
| "@leafer/interface": "1.0.0-alpha.23" | ||
| "@leafer/interface": "1.0.0-alpha.30" | ||
| } | ||
| } |
+112
-74
| import { IPointData, ITwoPointBoundsData, IPathCommandData } from '@leafer/interface' | ||
| import { TwoPointBoundsHelper } from '@leafer/math' | ||
| 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 } = Math | ||
| const { setPoint, addPoint } = TwoPointBoundsHelper | ||
| const { set } = PointHelper | ||
| const tempPoint = {} as IPointData | ||
| const { sin, cos, sqrt, atan2, ceil, abs, PI } = Math | ||
| const { setPoint, addPoint } = TwoPointBoundsHelper | ||
| export const BezierHelper = { | ||
| getFromACommand(fromX: number, fromY: number, rx: number, ry: number, rotateAngle: number, largeFlag: number, sweepFlag: number, toX: number, toY: number): IPathCommandData { // [...CCommandData, ...CCommandData] | ||
| 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) | ||
| }, | ||
| const localToX = toX - fromX | ||
| const localToY = toY - fromY | ||
| 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)) | ||
| }, | ||
| const rotation = rotateAngle * PI / 180 | ||
| const sinRotation = sin(rotation) | ||
| const cosRotation = cos(rotation) | ||
| 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 | ||
| const ax = -cosRotation * localToX * 0.5 - sinRotation * localToY * 0.5 | ||
| const ay = -cosRotation * localToY * 0.5 + sinRotation * localToX * 0.5 | ||
| const rxSquare = rx * rx | ||
| const rySquare = ry * ry | ||
| const pySquare = ay * ay | ||
| const pxSquare = ax * ax | ||
| const a = rxSquare * rySquare - rxSquare * pySquare - rySquare * pxSquare | ||
| let sr = 0 | ||
| let startRadian = atan2(BAy, BAx) | ||
| let endRadian = atan2(CBy, CBx) | ||
| let totalRadian = endRadian - startRadian | ||
| if (totalRadian < 0) totalRadian += PI2 | ||
| if (a < 0) { | ||
| const scale = sqrt(1 - a / (rxSquare * rySquare)) | ||
| rx *= scale | ||
| ry *= scale | ||
| } else { | ||
| const sign = largeFlag === sweepFlag ? -1 : 1 | ||
| sr = sign * sqrt(a / (rxSquare * pySquare + rySquare * pxSquare)) | ||
| if (totalRadian === PI || (abs(BAx + BAy) < 1.e-12) || (abs(CBx + CBy) < 1.e-12)) { // invalid | ||
| if (data) data.push(PathCommandMap.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 cx = sr * rx * ay / ry | ||
| const cy = -sr * ry * ax / rx | ||
| const cx1 = cosRotation * cx - sinRotation * cy + localToX * 0.5 | ||
| const cy1 = sinRotation * cx + cosRotation * cy + localToY * 0.5 | ||
| const anticlockwise = BAx * CBy - CBx * BAy < 0 | ||
| const sign = anticlockwise ? -1 : 1 | ||
| const c = radius / cos(totalRadian / 2) | ||
| let r1 = atan2((ay - cy) / ry, (ax - cx) / rx) | ||
| let r2 = atan2((-ay - cy) / ry, (-ax - cx) / rx) | ||
| let totalRadian = r2 - r1 | ||
| 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 | ||
| if (sweepFlag === 0 && totalRadian > 0) { | ||
| totalRadian -= 2 * PI | ||
| } else if (sweepFlag === 1 && totalRadian < 0) { | ||
| totalRadian += 2 * PI | ||
| } | ||
| return ellipse(data, centerX, centerY, radius, radius, 0, startRadian / OneRadian, endRadian / OneRadian, anticlockwise, setPointBounds, setEndPoint, setStartPoint) | ||
| }, | ||
| // segments arc | ||
| const data: IPathCommandData = [] | ||
| const segments = ceil(abs(totalRadian / PI * 2)) | ||
| const segmentRadian = totalRadian / segments | ||
| const sinSegmentRadian2 = sin(segmentRadian / 2) | ||
| const sinSegmentRadian4 = sin(segmentRadian / 4) | ||
| const controlRadian = 8 / 3 * sinSegmentRadian4 * sinSegmentRadian4 / sinSegmentRadian2 | ||
| 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) | ||
| }, | ||
| r1 = 0 | ||
| r2 = atan2((ay - cy) / ry, (ax - cx) / rx) | ||
| let startRadian = r2 - r1 | ||
| let endRadian = startRadian + segmentRadian | ||
| let cosStart = cos(startRadian) | ||
| let sinStart = sin(startRadian) | ||
| let cosEnd: number, sinEnd: number | ||
| let startX = 0, startY = 0 | ||
| 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 | ||
| for (let i = 0; i < segments; i++) { | ||
| cosEnd = cos(endRadian) | ||
| sinEnd = sin(endRadian) | ||
| let startX = x = rotationCos * radiusX * startCos - rotationSin * radiusY * startSin | ||
| let startY = y = rotationSin * radiusX * startCos + rotationCos * radiusY * startSin | ||
| // segment bezier | ||
| x = cosRotation * rx * cosEnd - sinRotation * ry * sinEnd + cx1 | ||
| y = sinRotation * rx * cosEnd + cosRotation * ry * sinEnd + cy1 | ||
| x1 = startX + controlRadian * (-cosRotation * rx * sinStart - sinRotation * ry * cosStart) | ||
| y1 = startY + controlRadian * (-sinRotation * rx * sinStart + cosRotation * ry * cosStart) | ||
| x2 = x + controlRadian * (cosRotation * rx * sinEnd + sinRotation * ry * cosEnd) | ||
| y2 = y + controlRadian * (sinRotation * rx * sinEnd - cosRotation * ry * cosEnd) | ||
| let fromX = cx + x, fromY = cy + y | ||
| data.push(PathCommandMap.C, x1 + fromX, y1 + fromY, x2 + fromX, y2 + fromY, x + fromX, y + fromY) | ||
| if (data) data.push(PathCommandMap.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(PathCommandMap.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 | ||
| cosStart = cosEnd | ||
| sinStart = sinEnd | ||
| endRadian += segmentRadian | ||
| endRadian += partRadian | ||
| } | ||
| return data | ||
| if (setEndPoint) set(setEndPoint, cx + x, cy + y) | ||
| }, | ||
| toTwoPointBounds(fromX: number, fromY: number, x1: number, y1: number, x2: number, y2: number, toX: number, toY: number, pointBounds: ITwoPointBoundsData): void { | ||
| quadraticCurveTo(data: IPathCommandData, fromX: number, fromY: number, x1: number, y1: number, toX: number, toY: number): void { | ||
| data.push(PathCommandMap.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 = [] | ||
@@ -134,9 +172,10 @@ let a, b, c, t, t1, t2, v, sqrtV | ||
| setPoint(pointBounds, fromX, fromY) | ||
| addMode ? addPoint(pointBounds, fromX, fromY) : setPoint(pointBounds, fromX, fromY) | ||
| addPoint(pointBounds, toX, toY) | ||
| for (let i = 0, len = tList.length; i < len; i++) { | ||
| B.getPointAndSet(tList[i], fromX, fromY, x1, y1, x2, y2, toX, toY, tempPoint) | ||
| getPointAndSet(tList[i], fromX, fromY, x1, y1, x2, y2, toX, toY, tempPoint) | ||
| addPoint(pointBounds, tempPoint.x, tempPoint.y) | ||
| } | ||
| }, | ||
@@ -152,8 +191,7 @@ | ||
| const point = {} as IPointData | ||
| B.getPointAndSet(t, fromX, fromY, x1, y1, x2, y2, toX, toY, point) | ||
| getPointAndSet(t, fromX, fromY, x1, y1, x2, y2, toX, toY, point) | ||
| return point | ||
| } | ||
| } | ||
| const B = BezierHelper | ||
| const { getPointAndSet, toTwoPointBounds, ellipse } = BezierHelper |
+18
-2
@@ -0,5 +1,21 @@ | ||
| export { PathHelper } from './PathHelper' | ||
| export { PathConvert } from './PathConvert' | ||
| export { PathHelper } from './PathHelper' | ||
| export { PathCreator } from './PathCreator' | ||
| export { PathCommandDataHelper } from './PathCommandDataHelper' | ||
| export { PathDrawer } from './PathDrawer' | ||
| export { PathBounds } from './PathBounds' | ||
| export { PathCorner } from './PathCorner' | ||
| export { BezierHelper } from './BezierHelper' | ||
| export { PathCommandMap, PathCommandNeedConvertMap, NumberPathCommandMap, NumberPathCommandLengthMap } from './PathCommandMap' | ||
| export { EllipseHelper } from './EllipseHelper' | ||
| export { RectHelper } from './RectHelper' | ||
| export { PathCommandMap, NeedConvertToCanvasCommandMap, PathNumberCommandMap, PathNumberCommandLengthMap } from './PathCommandMap' | ||
| // rewrite, prevent circular references | ||
| import { PathConvert } from './PathConvert' | ||
| import { PathCreator } from './PathCreator' | ||
| import { PathHelper } from './PathHelper' | ||
| PathHelper.creator = new PathCreator() | ||
| PathHelper.parse = PathConvert.parse | ||
| PathHelper.convertToCanvasData = PathConvert.toCanvasData |
+52
-33
| 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 = { | ||
| M: 1, //moveto | ||
| // svg and canvas | ||
| M: 1, // moveto | ||
| m: 10, | ||
| L: 2, //lineto | ||
| L: 2, // lineto | ||
| l: 20, | ||
| H: 3, //horizontal lineto | ||
| H: 3, // horizontal lineto | ||
| h: 30, | ||
| V: 4, //vertical lineto | ||
| V: 4, // vertical lineto | ||
| v: 40, | ||
| C: 5, //curveto | ||
| C: 5, // curveto | ||
| c: 50, | ||
| S: 6, //smooth curveto | ||
| S: 6, // smooth curveto | ||
| s: 60, | ||
| Q: 7, //quadratic Belzier curve | ||
| Q: 7, // quadratic Belzier curve | ||
| q: 70, | ||
| T: 8, //smooth quadratic Belzier curveto | ||
| T: 8, // smooth quadratic Belzier curveto | ||
| t: 80, | ||
| A: 9, //elliptical Arc | ||
| A: 9, //e lliptical Arc | ||
| a: 90, | ||
| Z: 11, //closepath | ||
| Z: 11, // closepath | ||
| z: 11, | ||
| // 非svg标准的canvas绘图命令 | ||
| rect: 100, | ||
| roundRect: 101, | ||
| ellipse: 102, | ||
| arc: 103, | ||
| arcTo: 104 | ||
| R: 12, // Catmull Rom | ||
| // canvas | ||
| ...CanvasCommandOnlyMap | ||
| } | ||
| export const PathCommandLengthMap: INumberMap = { | ||
| M: 3, //moveto | ||
@@ -56,11 +70,17 @@ m: 3, | ||
| // 非svg标准的canvas绘图命令 | ||
| rect: 5, | ||
| roundRect: 6, | ||
| ellipse: 9, | ||
| arc: 7, | ||
| arcTo: 6 | ||
| // 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 PathCommandNeedConvertMap: INumberMap = { // convert to: M L C Q Z | ||
| export const NeedConvertToCanvasCommandMap: INumberMap = { // convert to: M L C Q Z | ||
| // M: 1, //moveto | ||
@@ -87,23 +107,22 @@ m: 10, | ||
| // 非svg标准的canvas绘图命令 | ||
| rect: 100, | ||
| roundRect: 101, | ||
| ellipse: 102, | ||
| arc: 103, | ||
| arcTo: 104 | ||
| } | ||
| export const NeedConvertToCurveCommandMap: INumberMap = { | ||
| ...NeedConvertToCanvasCommandMap, | ||
| ...CanvasCommandOnlyMap | ||
| } | ||
| const P = PathCommandMap | ||
| export const NumberPathCommandMap: IStringMap = {} | ||
| export const PathNumberCommandMap: IStringMap = {} | ||
| for (let key in P) { | ||
| NumberPathCommandMap[P[key]] = key | ||
| PathNumberCommandMap[P[key]] = key | ||
| } | ||
| // {1: 'M'} | ||
| export const NumberPathCommandLengthMap: INumberMap = {} | ||
| export const PathNumberCommandLengthMap: INumberMap = {} | ||
| for (let key in P) { | ||
| NumberPathCommandLengthMap[P[key]] = PathCommandLengthMap[key] | ||
| PathNumberCommandLengthMap[P[key]] = PathCommandLengthMap[key] | ||
| } | ||
| // {1: 3} |
+104
-28
@@ -1,6 +0,8 @@ | ||
| import { IPathCommandData } from '@leafer/interface' | ||
| import { IPathCommandData, IPointData } from '@leafer/interface' | ||
| import { StringNumberMap } from '@leafer/math' | ||
| import { PathCommandMap as Command, PathCommandNeedConvertMap as NeedConvertCommand, PathCommandLengthMap as CommandLength, NumberPathCommandMap as CommandName, NumberPathCommandLengthMap as NumberCommandLength } from './PathCommandMap' | ||
| import { Debug } from '@leafer/debug' | ||
| import { PathCommandMap as Command, NeedConvertToCanvasCommandMap, NeedConvertToCurveCommandMap, PathCommandLengthMap, PathNumberCommandMap, PathNumberCommandLengthMap } from './PathCommandMap' | ||
| import { BezierHelper } from './BezierHelper' | ||
| import { EllipseHelper } from './EllipseHelper' | ||
@@ -15,5 +17,9 @@ | ||
| const { M, m, L, l, H, h, V, v, C, c, S, s, Q, q, T, t, A, a, Z, z } = Command | ||
| const { getFromACommand } = BezierHelper | ||
| 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 = { | ||
@@ -27,7 +33,7 @@ | ||
| command = data[i] | ||
| count = NumberCommandLength[command] | ||
| count = PathNumberCommandLengthMap[command] | ||
| if (command === lastCommand) { | ||
| str += ' ' // 重复的命令可以省略 | ||
| } else { | ||
| str += CommandName[command] | ||
| str += PathNumberCommandMap[command] | ||
| } | ||
@@ -46,6 +52,7 @@ | ||
| parse(pathString: string, convert: boolean = true): IPathCommandData { | ||
| parse(pathString: string, curveMode?: boolean): IPathCommandData { | ||
| let needConvert: boolean, char: string, num = '' | ||
| let needConvert: boolean, char: string, lastChar: string, num = '' | ||
| const data: IPathCommandData = [] | ||
| const convertCommand = curveMode ? NeedConvertToCurveCommandMap : NeedConvertToCanvasCommandMap | ||
@@ -65,13 +72,19 @@ for (let i = 0, len = pathString.length; i < len; i++) { | ||
| current.name = Command[char] | ||
| current.length = CommandLength[char] | ||
| current.length = PathCommandLengthMap[char] | ||
| current.index = 0 | ||
| pushData(data, current.name) | ||
| if (!needConvert && NeedConvertCommand[char]) needConvert = true | ||
| if (!needConvert && convertCommand[char]) needConvert = true | ||
| } else { | ||
| if (char === '-') { // L-34-35 | ||
| if (num) { pushData(data, Number(num)) } | ||
| num = char | ||
| if (char === '-' || char === '+') { | ||
| if (lastChar === 'e' || lastChar === 'E') { // L45e-12 21e+22 | ||
| num += char | ||
| } else { | ||
| if (num) pushData(data, Number(num)) // L-34-35 L+12+28 | ||
| num = char | ||
| } | ||
| } else { | ||
@@ -83,2 +96,4 @@ if (num) { pushData(data, Number(num)); num = '' } | ||
| lastChar = char | ||
| } | ||
@@ -88,6 +103,6 @@ | ||
| return (convert && needConvert) ? PathConvert.convertToSimple(data) : data | ||
| return needConvert ? PathConvert.toCanvasData(data, curveMode) : data | ||
| }, | ||
| convertToSimple(old: IPathCommandData): IPathCommandData { | ||
| toCanvasData(old: IPathCommandData, curveMode?: boolean): IPathCommandData { | ||
@@ -102,3 +117,3 @@ let x = 0, y = 0, x1 = 0, y1 = 0, i = 0, len = old.length, controlX: number, controlY: number, command: number, lastCommand: number, smooth: boolean | ||
| switch (command) { | ||
| //moveto x,y | ||
| //moveto(x, y) | ||
| case m: | ||
@@ -114,3 +129,3 @@ old[i + 1] += x | ||
| //horizontal lineto x | ||
| //horizontal lineto(x) | ||
| case h: | ||
@@ -124,3 +139,3 @@ old[i + 1] += x | ||
| //vertical lineto y | ||
| //vertical lineto(y) | ||
| case v: | ||
@@ -134,3 +149,3 @@ old[i + 1] += y | ||
| //lineto x,y | ||
| //lineto(x,y) | ||
| case l: | ||
@@ -146,3 +161,3 @@ old[i + 1] += x | ||
| //smooth bezierCurveTo x2,y2,x,y | ||
| //smooth bezierCurveTo(x2, y2, x, y) | ||
| case s: //smooth | ||
@@ -166,3 +181,3 @@ old[i + 1] += x | ||
| //bezierCurveTo x1,y1,x2,y2,x,y | ||
| //bezierCurveTo(x1, y1, x2, y2, x, y) | ||
| case c: | ||
@@ -185,3 +200,3 @@ old[i + 1] += x | ||
| //smooth quadraticCurveTo x,y | ||
| //smooth quadraticCurveTo(x, y) | ||
| case t: | ||
@@ -195,9 +210,9 @@ old[i + 1] += x | ||
| 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] | ||
| data.push(Q, controlX, controlY, x, y) | ||
| i += 3 | ||
| break | ||
| //quadraticCurveTo x1,y1,x,y | ||
| //quadraticCurveTo(x1, y1, x, y) | ||
| case q: | ||
@@ -212,9 +227,9 @@ old[i + 1] += x | ||
| 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] | ||
| data.push(Q, controlX, controlY, x, y) | ||
| i += 5 | ||
| break | ||
| //ellipticalArc rx ry x-axis-rotation large-arc-flag sweep-flag x y | ||
| //ellipticalArc(rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y) | ||
| case a: | ||
@@ -224,3 +239,3 @@ old[i + 6] += x | ||
| case A: | ||
| 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 | ||
| 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] | ||
@@ -235,2 +250,57 @@ y = old[i + 7] | ||
| 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 | ||
| } | ||
@@ -245,2 +315,8 @@ | ||
| 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, num: number) { | ||
@@ -258,2 +334,2 @@ if (current.index === current.length) { // 单个命令,多个数据的情况 | ||
| const { current, pushData } = PathConvert | ||
| const { current, pushData, copyData } = PathConvert |
+71
-43
@@ -1,64 +0,92 @@ | ||
| import { IPathCommandData } from '@leafer/interface' | ||
| import { PathCommandMap } from './PathCommandMap' | ||
| import { IPathCommandData, IPathDrawer } from '@leafer/interface' | ||
| import { PathCommandDataHelper } from './PathCommandDataHelper' | ||
| import { IPathString } from '@leafer-ui/interface' | ||
| import { PathHelper } from './PathHelper' | ||
| let data: IPathCommandData | ||
| const { M, L, C, Q, Z, rect, roundRect, ellipse, arc, arcTo } = PathCommandMap | ||
| const { moveTo, lineTo, quadraticCurveTo, bezierCurveTo, closePath, beginPath, rect, roundRect, ellipse, arc, arcTo, moveToEllipse, moveToArc } = PathCommandDataHelper | ||
| export const PathCreator = { | ||
| export class PathCreator implements IPathDrawer { | ||
| begin(commandData: IPathCommandData): void { | ||
| data = commandData | ||
| }, | ||
| public path: IPathCommandData | ||
| end(): void { | ||
| data = null | ||
| }, | ||
| constructor(path?: IPathCommandData | IPathString) { | ||
| if (path) { | ||
| this.path = typeof path === 'string' ? PathHelper.parse(path) : path | ||
| } else { | ||
| this.path = [] | ||
| } | ||
| } | ||
| // draw | ||
| public beginPath(): PathCreator { | ||
| beginPath(this.path) | ||
| return this | ||
| } | ||
| moveTo(x: number, y: number): void { | ||
| data.push(M, x, y) | ||
| }, | ||
| // svg and canvas | ||
| lineTo(x: number, y: number): void { | ||
| data.push(L, x, y) | ||
| }, | ||
| public moveTo(x: number, y: number): PathCreator { | ||
| moveTo(this.path, x, y) | ||
| return this | ||
| } | ||
| bezierCurveTo(x1: number, y1: number, x2: number, y2: number, x: number, y: number): void { | ||
| data.push(C, x1, y1, x2, y2, x, y) | ||
| }, | ||
| public lineTo(x: number, y: number): PathCreator { | ||
| lineTo(this.path, x, y) | ||
| return this | ||
| } | ||
| quadraticCurveTo(x1: number, y1: number, x: number, y: number): void { | ||
| data.push(Q, x1, y1, x, y) | ||
| }, | ||
| 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 | ||
| } | ||
| close(end?: boolean): void { | ||
| data.push(Z) | ||
| if (end) data = null | ||
| }, | ||
| 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 | ||
| } | ||
| // 非svg标准的canvas绘图命令 | ||
| // canvas | ||
| rect(x: number, y: number, width: number, height: number): void { | ||
| data.push(rect, x, y, width, height) | ||
| }, | ||
| public rect(x: number, y: number, width: number, height: number): PathCreator { | ||
| rect(this.path, x, y, width, height) | ||
| return this | ||
| } | ||
| roundRect(x: number, y: number, width: number, height: number, cornerRadius?: number | number[]): void { | ||
| data.push(roundRect, x, y, width, height, cornerRadius as unknown as number) | ||
| }, | ||
| public roundRect(x: number, y: number, width: number, height: number, cornerRadius: number | number[]): PathCreator { | ||
| roundRect(this.path, x, y, width, height, cornerRadius) | ||
| return this | ||
| } | ||
| ellipse(x: number, y: number, radiusX: number, radiusY: number, rotation: number, startAngle: number, endAngle: number, counterclockwise?: boolean): void { | ||
| data.push(ellipse, x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise as unknown as number) | ||
| }, | ||
| 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 | ||
| } | ||
| arc(x: number, y: number, radius: number, startAngle: number, endAngle: number, counterclockwise?: boolean): void { | ||
| data.push(arc, x, y, radius, startAngle, endAngle, counterclockwise as unknown as number) | ||
| }, | ||
| 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 | ||
| } | ||
| arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): void { | ||
| data.push(arcTo, x1, y1, x2, y2, radius) | ||
| 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 moveToEllipse(x: number, y: number, radiusX: number, radiusY: number, rotation?: number, startAngle?: number, endAngle?: number, anticlockwise?: boolean): PathCreator { | ||
| moveToEllipse(this.path, x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise) | ||
| return this | ||
| } | ||
| public moveToArc(x: number, y: number, radius: number, startAngle?: number, endAngle?: number, anticlockwise?: boolean): PathCreator { | ||
| moveToArc(this.path, x, y, radius, startAngle, endAngle, anticlockwise) | ||
| return this | ||
| } | ||
| } |
+6
-121
@@ -1,123 +0,8 @@ | ||
| import { IPathDrawer, ITwoPointBoundsData, IPathCommandData, IBoundsData } from '@leafer/interface' | ||
| import { TwoPointBoundsHelper } from '@leafer/math' | ||
| import { IPathCommandData, IPathCreator } from '@leafer/interface' | ||
| import { BezierHelper } from './BezierHelper' | ||
| import { PathCommandMap as Command } from './PathCommandMap' | ||
| const { M, L, C, Q, Z, ellipse: E } = Command | ||
| const { toTwoPointBounds } = BezierHelper | ||
| const { add, addPoint, setPoint, toBounds } = TwoPointBoundsHelper | ||
| const tempPointBounds = {} as ITwoPointBoundsData | ||
| const setPointBounds = {} as ITwoPointBoundsData | ||
| export const PathHelper = { | ||
| smoothCorner(data: IPathCommandData, _cornerRadius: number, _cornerSmoothing?: number): IPathCommandData { | ||
| return data | ||
| }, | ||
| toBounds(data: IPathCommandData, setBounds: IBoundsData): void { | ||
| P.toTwoPointBounds(data, setPointBounds) | ||
| toBounds(setPointBounds, setBounds) | ||
| }, | ||
| toTwoPointBounds(data: IPathCommandData, setPointBounds: ITwoPointBoundsData): void { | ||
| let command: number | ||
| let i: number = 0, x: number, y: number, x1: number, y1: number, toX: number, toY: number | ||
| const len = data.length | ||
| while (i < len) { | ||
| command = data[i] | ||
| if (i === 0) { | ||
| (command === M) ? setPoint(setPointBounds, data[1], data[2]) : setPoint(setPointBounds, 0, 0) | ||
| } | ||
| 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] | ||
| toTwoPointBounds(x, y, x1, y1, x1, y1, toX, toY, tempPointBounds) | ||
| add(setPointBounds, tempPointBounds) | ||
| x = toX | ||
| y = toY | ||
| i += 5 | ||
| break | ||
| case Z: //closepath | ||
| i += 1 | ||
| break | ||
| } | ||
| } | ||
| // 增加1px的扩展,否则会有问题 | ||
| setPointBounds.minX-- | ||
| setPointBounds.minY-- | ||
| setPointBounds.maxX++ | ||
| setPointBounds.maxY++ | ||
| }, | ||
| drawData(drawer: IPathDrawer, data: IPathCommandData): void { | ||
| if (data) { | ||
| 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 | ||
| // 非svg标准的canvas绘图命令 | ||
| case E: | ||
| 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) | ||
| i += 9 | ||
| break | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| const P = PathHelper | ||
| // index.ts rewrite | ||
| creator: {} as IPathCreator, | ||
| parse(_pathString: string, _curveMode?: boolean): IPathCommandData { return undefined }, | ||
| convertToCanvasData(_old: IPathCommandData, _curveMode?: boolean): IPathCommandData { return undefined } | ||
| } |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
46390
95.12%15
66.67%952
70%2
100%1
Infinity%+ Added
+ Added
+ Added
- Removed
Updated