perfect-freehand
Advanced tools
Comparing version 0.4.91 to 0.5.0
{ | ||
"version": "0.4.91", | ||
"version": "0.5.0", | ||
"name": "perfect-freehand", | ||
"private": false, | ||
"description": "Draw perfect pressure-sensitive freehand strokes.", | ||
"author": { | ||
@@ -20,52 +22,28 @@ "name": "Steve Ruiz", | ||
"license": "MIT", | ||
"module": "dist/perfect-freehand.esm.js", | ||
"main": "dist/index.js", | ||
"typings": "dist/index.d.ts", | ||
"files": [ | ||
"dist", | ||
"src" | ||
], | ||
"engines": { | ||
"node": ">=10" | ||
}, | ||
"main": "./dist/cjs/index.js", | ||
"module": "./dist/esm/index.js", | ||
"types": "./dist/types/index.d.ts", | ||
"scripts": { | ||
"start": "tsdx watch", | ||
"build": "tsdx build", | ||
"test": "tsdx test", | ||
"lint": "tsdx lint", | ||
"prepare": "tsdx build", | ||
"size": "size-limit", | ||
"analyze": "size-limit --why" | ||
"start": "node scripts/dev & tsc --watch --incremental --emitDeclarationOnly --declarationMap --outDir dist/types", | ||
"build": "yarn clean && node scripts/build && tsc --project tsconfig.build.json --emitDeclarationOnly --outDir dist/types", | ||
"lint": "eslint src/ --ext .ts,.tsx", | ||
"clean": "rm -rf dist", | ||
"ts-node": "ts-node", | ||
"docs": "typedoc --entryPoints src/index.ts" | ||
}, | ||
"husky": { | ||
"hooks": { | ||
"pre-commit": "tsdx lint" | ||
} | ||
}, | ||
"prettier": { | ||
"printWidth": 80, | ||
"semi": false, | ||
"singleQuote": true, | ||
"trailingComma": "es5" | ||
}, | ||
"size-limit": [ | ||
{ | ||
"path": "dist/perfect-freehand.cjs.production.min.js", | ||
"limit": "10 KB" | ||
}, | ||
{ | ||
"path": "dist/perfect-freehand.esm.js", | ||
"limit": "10 KB" | ||
} | ||
], | ||
"devDependencies": { | ||
"@size-limit/preset-small-lib": "^4.9.2", | ||
"husky": "^5.0.9", | ||
"size-limit": "^4.9.2", | ||
"tsdx": "^0.14.1", | ||
"tslib": "^2.1.0", | ||
"typescript": "^4.1.5" | ||
"@types/jest": "^27.0.1", | ||
"@types/node": "^15.0.1", | ||
"@types/react": "^17.0.16", | ||
"@types/react-dom": "^17.0.9", | ||
"esbuild": "^0.12.21", | ||
"eslint": "^7.22.0", | ||
"react": "^17.0.2", | ||
"react-dom": "^17.0.2", | ||
"ts-node": "^9.1.1", | ||
"tslib": "^2.3.0", | ||
"typedoc": "^0.20.35", | ||
"typescript": "^4.3.5" | ||
}, | ||
"peerDependencies": {}, | ||
"dependencies": {} | ||
"gitHead": "285dece1681b7a21bffb9309fee476c5f44495f5" | ||
} |
@@ -68,6 +68,8 @@ # ![Screenshot](screenshot.svg 'Perfect Freehand') | ||
| `easing` | function | t => t | An easing function to apply to each point's pressure. | | ||
| `start` | function | t => t | Tapering options for the start of the line. | | ||
| `start` | { } | | Tapering options for the start of the line. | | ||
| `end` | { } | | Tapering options for the end of the line. | | ||
| `last` | boolean | true | Whether the stroke is complete. | | ||
| `last` | boolean | true | Whether the stroke is complete. | | ||
**Note:** When the `last` property is `true`, the line's end will be drawn at the last input point, rather than slightly behind it. | ||
The `start` and `end` options accept an object: | ||
@@ -77,6 +79,7 @@ | ||
| -------- | -------- | ------- | ------------------------------------------- | | ||
| `taper` | boolean | 0 | The distance to taper. | | ||
| `easing` | function | t => | An easing function for the tapering effect. | | ||
| `cap` | boolean | true | Whether to draw a cap. | | ||
| `taper` | number | 0 | The distance to taper. | | ||
| `easing` | function | t => t | An easing function for the tapering effect. | | ||
When `taper` is zero for either start or end, the library will add a rounded cap at that end of the line. | ||
**Note:** The `cap` property has no effect when `taper` is more than zero. | ||
@@ -89,3 +92,3 @@ ```js | ||
streamline: 0.5, | ||
easing: t => t * t * t, | ||
easing: (t) => t * t * t, | ||
simulatePressure: true, | ||
@@ -95,7 +98,7 @@ last: true, | ||
taper: 20, | ||
easing: t => t * t * t, | ||
easing: (t) => t * t * t, | ||
}, | ||
end: { | ||
taper: 20, | ||
easing: t => t * t * t, | ||
easing: (t) => t * t * t, | ||
}, | ||
@@ -117,3 +120,3 @@ }) | ||
function getSvgPathFromStroke(stroke) { | ||
if (!stroke.length) return "" | ||
if (!stroke.length) return '' | ||
@@ -126,7 +129,7 @@ const d = stroke.reduce( | ||
}, | ||
["M", ...stroke[0], "Q"] | ||
['M', ...stroke[0], 'Q'] | ||
) | ||
d.push("Z") | ||
return d.join(" ") | ||
d.push('Z') | ||
return d.join(' ') | ||
} | ||
@@ -138,3 +141,3 @@ ``` | ||
```js | ||
import getStroke from "perfect-freehand" | ||
import getStroke from 'perfect-freehand' | ||
@@ -149,3 +152,3 @@ const myStroke = getStroke(myInputPoints) | ||
```jsx | ||
<path d={pathData}/> | ||
<path d={pathData} /> | ||
``` | ||
@@ -187,5 +190,5 @@ | ||
```jsx | ||
import * as React from "react" | ||
import getStroke from "perfect-freehand" | ||
import { getSvgPathFromStroke } from "./utils" | ||
import * as React from 'react' | ||
import getStroke from 'perfect-freehand' | ||
import { getSvgPathFromStroke } from './utils' | ||
@@ -211,3 +214,3 @@ export default function Example() { | ||
onPointerMove={handlePointerMove} | ||
style={{ touchAction: "none" }} | ||
style={{ touchAction: 'none' }} | ||
> | ||
@@ -221,3 +224,3 @@ {points && ( | ||
smoothing: 0.5, | ||
streamline: 0.5 | ||
streamline: 0.5, | ||
}) | ||
@@ -224,0 +227,0 @@ )} |
168
src/index.ts
import { toPointsArray, getStrokeRadius } from './utils' | ||
import { StrokeOptions, StrokePoint } from './types' | ||
import type { StrokeOptions, StrokePoint } from './types' | ||
import * as vec from './vec' | ||
@@ -18,18 +18,13 @@ | ||
>(points: (T | K)[], options = {} as StrokeOptions): StrokePoint[] { | ||
let { simulatePressure = true, streamline = 0.5, size = 8 } = options | ||
let { streamline = 0.5 } = options | ||
const { simulatePressure = true } = options | ||
streamline /= 2 | ||
streamline = streamline / (simulatePressure ? 4 : 2) | ||
if (!simulatePressure) { | ||
streamline /= 2 | ||
} | ||
const pts = toPointsArray(points) | ||
let len = pts.length | ||
if (pts.length === 0) return [] | ||
if (len === 0) return [] | ||
if (pts.length === 1) pts.push([...vec.add(pts[0], [1, 1]), pts[0][2]]) | ||
if (len === 1) pts.push(vec.add(pts[0], [1, 0])) | ||
const strokePoints: StrokePoint[] = [ | ||
@@ -47,3 +42,3 @@ { | ||
let i = 1, j = 0, curr = pts[i], prev = strokePoints[j]; | ||
i < len; | ||
i < pts.length; | ||
i++, curr = pts[i], prev = strokePoints[j] | ||
@@ -55,15 +50,16 @@ ) { | ||
const pressure = curr[2] | ||
const vector = vec.uni(vec.vec(point, prev.point)) | ||
const vector = vec.uni(vec.sub(prev.point, point)) | ||
const distance = vec.dist(point, prev.point) | ||
const runningLength = prev.runningLength + distance | ||
strokePoints.push({ | ||
const strokePoint = { | ||
point, | ||
pressure, | ||
pressure: curr[2], | ||
vector, | ||
distance, | ||
runningLength, | ||
}) | ||
} | ||
strokePoints.push(strokePoint) | ||
j += 1 // only increment j if we add an item to strokePoints | ||
@@ -83,16 +79,16 @@ } | ||
// Update the length to the length of the strokePoints array. | ||
len = strokePoints.length | ||
// const len = strokePoints.length | ||
const totalLength = strokePoints[len - 1].runningLength | ||
// const totalLength = strokePoints[len - 1].runningLength | ||
for (let i = len - 2; i > 1; i--) { | ||
const { runningLength, vector } = strokePoints[i] | ||
const dpr = vec.dpr(strokePoints[i - 1].vector, strokePoints[i].vector) | ||
if (totalLength - runningLength > size / 2 || dpr < 0.8) { | ||
for (let j = i; j < len; j++) { | ||
strokePoints[j].vector = vector | ||
} | ||
break | ||
} | ||
} | ||
// for (let i = len - 2; i > 1; i--) { | ||
// const { runningLength, vector } = strokePoints[i] | ||
// const dpr = vec.dpr(strokePoints[i - 1].vector, strokePoints[i].vector) | ||
// if (totalLength - runningLength > size / 2 || dpr < 0.8) { | ||
// for (let j = i; j < len; j++) { | ||
// strokePoints[j].vector = vector | ||
// } | ||
// break | ||
// } | ||
// } | ||
@@ -125,3 +121,3 @@ return strokePoints | ||
simulatePressure = true, | ||
easing = t => t, | ||
easing = (t) => t, | ||
start = {}, | ||
@@ -137,9 +133,11 @@ end = {}, | ||
const { | ||
cap: capStart = true, | ||
taper: taperStart = 0, | ||
easing: taperStartEase = t => t * (2 - t), | ||
easing: taperStartEase = (t) => t * (2 - t), | ||
} = start | ||
const { | ||
cap: capEnd = true, | ||
taper: taperEnd = 0, | ||
easing: taperEndEase = t => --t * t * t + 1, | ||
easing: taperEndEase = (t) => --t * t * t + 1, | ||
} = end | ||
@@ -168,2 +166,4 @@ | ||
let firstRadius: number | undefined = undefined | ||
// Previous vector | ||
@@ -180,2 +180,4 @@ let prevVector = points[0].vector | ||
let short = true | ||
/* | ||
@@ -188,5 +190,12 @@ Find the outline's left and right points | ||
for (let i = 1; i < len - 1; i++) { | ||
let { point, pressure, vector, distance, runningLength } = points[i] | ||
for (let i = 0; i < len - 1; i++) { | ||
let { pressure } = points[i] | ||
const { point, vector, distance, runningLength } = points[i] | ||
if (i > 0 && short && runningLength < size / 2) { | ||
continue | ||
} else if (short) { | ||
short = false | ||
} | ||
/* | ||
@@ -212,2 +221,6 @@ Calculate the radius | ||
if (firstRadius === undefined) { | ||
firstRadius = radius | ||
} | ||
/* | ||
@@ -231,3 +244,3 @@ Apply tapering | ||
radius *= Math.min(ts, te) | ||
radius = Math.max(0.01, radius * Math.min(ts, te)) | ||
@@ -262,2 +275,3 @@ /* | ||
} | ||
/* | ||
@@ -278,5 +292,6 @@ Add regular points | ||
const alwaysAdd = i === 1 || dpr < 0.25 | ||
const alwaysAdd = i < 2 || dpr < 0.25 | ||
const minDistance = Math.pow( | ||
(runningLength > size ? size : size / 2) * smoothing, | ||
Math.max((runningLength > size ? size : size / 2) * smoothing, 1), | ||
2 | ||
@@ -311,3 +326,3 @@ ) | ||
const lastPoint = points[len - 1] | ||
const isVeryShort = rightPts.length < 2 || leftPts.length < 2 | ||
const isVeryShort = short || rightPts.length < 2 || leftPts.length < 2 | ||
@@ -361,4 +376,5 @@ /* | ||
const startCap: number[][] = [] | ||
const endCap: number[][] = [] | ||
if (!taperStart && !(taperEnd && isVeryShort)) { | ||
if (leftPts.length > 1 && rightPts.length > 1) { | ||
tr = rightPts[1] | ||
@@ -373,18 +389,36 @@ | ||
if (!vec.isEqual(tr, tl)) { | ||
const start = vec.sub( | ||
firstPoint.point, | ||
vec.mul(vec.uni(vec.vec(tr, tl)), vec.dist(tr, tl) / 2) | ||
) | ||
if (capStart || taperStart) { | ||
if (!taperStart && !(taperEnd && isVeryShort)) { | ||
if (!vec.isEqual(tr, tl)) { | ||
const start = vec.sub( | ||
firstPoint.point, | ||
vec.mul(vec.uni(vec.vec(tr, tl)), vec.dist(tr, tl) / 2) | ||
) | ||
for (let t = 0, step = 0.2; t <= 1; t += step) { | ||
startCap.push(vec.rotAround(start, firstPoint.point, PI * t)) | ||
for (let t = 0, step = 0.1; t <= 1; t += step) { | ||
startCap.push(vec.rotAround(start, firstPoint.point, PI * t)) | ||
} | ||
leftPts.shift() | ||
rightPts.shift() | ||
} | ||
} else { | ||
startCap.push(firstPoint.point) | ||
} | ||
} else { | ||
if (!vec.isEqual(tr, tl)) { | ||
const vector = vec.uni(vec.vec(tr, tl)) | ||
const dist = vec.dist(tr, tl) / 2 | ||
leftPts.shift() | ||
rightPts.shift() | ||
startCap.push(vec.sub(firstPoint.point, vec.mul(vector, dist * 0.95))) | ||
startCap.push(vec.sub(firstPoint.point, vec.mul(vector, dist))) | ||
startCap.push(vec.add(firstPoint.point, vec.mul(vector, dist))) | ||
startCap.push(vec.add(firstPoint.point, vec.mul(vector, dist * 0.95))) | ||
leftPts.shift() | ||
rightPts.shift() | ||
} | ||
} | ||
} | ||
/* | ||
/* | ||
Draw an end cap | ||
@@ -399,15 +433,27 @@ | ||
const endCap: number[][] = [] | ||
const lastLeft = leftPts[leftPts.length - 1] | ||
const lastRight = rightPts[rightPts.length - 1] | ||
const mid = vec.med(lastLeft, lastRight) | ||
const last = options.last | ||
? lastPoint.point | ||
: vec.lrp(mid, lastPoint.point, 0.618) | ||
const vector = vec.uni(vec.sub(last, mid)) | ||
if (!taperEnd && !(taperStart && isVeryShort)) { | ||
const start = vec.sub( | ||
lastPoint.point, | ||
vec.mul(vec.per(lastPoint.vector), radius) | ||
) | ||
for (let t = 0, step = 0.1; t <= 1; t += step) { | ||
endCap.push(vec.rotAround(start, lastPoint.point, PI * 3 * t)) | ||
if (capEnd || taperEnd) { | ||
if (!taperEnd && !(taperStart && isVeryShort)) { | ||
const start = vec.add(last, vec.mul(vec.per(vector), radius)) | ||
for (let t = 0, step = 0.15; t <= 1; t += step) { | ||
endCap.push(vec.rotAround(start, last, PI * 3 * t)) | ||
} | ||
} else { | ||
endCap.push(last) | ||
} | ||
} else { | ||
const justBefore = vec.lrp(mid, last, 0.95) | ||
const r = radius * 0.95 | ||
endCap.push(vec.add(justBefore, vec.mul(vec.per(vector), r))) | ||
endCap.push(vec.add(last, vec.mul(vec.per(vector), r))) | ||
endCap.push(vec.sub(last, vec.mul(vec.per(vector), r))) | ||
endCap.push(vec.sub(justBefore, vec.mul(vec.per(vector), r))) | ||
} | ||
} else { | ||
endCap.push(lastPoint.point) | ||
} | ||
@@ -414,0 +460,0 @@ |
@@ -9,2 +9,3 @@ export interface StrokeOptions { | ||
start?: { | ||
cap?: boolean | ||
taper?: number | ||
@@ -14,2 +15,3 @@ easing?: (distance: number) => number | ||
end?: { | ||
cap?: boolean | ||
taper?: number | ||
@@ -16,0 +18,0 @@ easing?: (distance: number) => number |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
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
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
21
267
84184
12
1105
4
1