Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

perfect-freehand

Package Overview
Dependencies
Maintainers
1
Versions
56
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

perfect-freehand - npm Package Compare versions

Comparing version 0.4.6 to 0.4.7

8

CHANGELOG.md

@@ -0,1 +1,9 @@

## 0.4.6
- Reduce the effect of streamline by half.
- If using actual pressure, reduce the effect by half again.
- Lerp between previous and next left/right point using streamline.
- Adds check for hard turns to line end alignment.
- Removes line start alignment. This still needs to be improved, but the previous solution is not the right one.
## 0.4.5

@@ -2,0 +10,0 @@

26

dist/perfect-freehand.cjs.development.js

@@ -131,2 +131,19 @@ 'use strict';

/**
* Length of the vector squared
* @param A
*/
function len2(A) {
return A[0] * A[0] + A[1] * A[1];
}
/**
* Dist length from A to B squared.
* @param A
* @param B
*/
function dist2(A, B) {
return len2(sub(A, B));
}
/**
* Get normalized / unit vector.

@@ -240,4 +257,5 @@ * @param A

_vector = _strokePoints$_i.vector;
var dpr$1 = dpr(strokePoints[_i - 1].vector, strokePoints[_i].vector);
if (totalLength - _runningLength > size / 2 || dpr(strokePoints[_i - 1].vector, strokePoints[_i].vector) < 0.8) {
if (totalLength - _runningLength > size / 2 || dpr$1 < 0.8) {
for (var j = _i; j < len; j++) {

@@ -414,5 +432,5 @@ strokePoints[j].vector = _vector;

var alwaysAdd = i === 1 || dpr$1 < 0.25;
var minDistance = (runningLength > size ? size : size / 2) * smoothing;
var minDistance = Math.pow((runningLength > size ? size : size / 2) * smoothing, 2);
if (alwaysAdd || dist(pl, tl) > minDistance) {
if (alwaysAdd || dist2(pl, tl) > minDistance) {
leftPts.push(lrp(pl, tl, streamline));

@@ -422,3 +440,3 @@ pl = tl;

if (alwaysAdd || dist(pr, tr) > minDistance) {
if (alwaysAdd || dist2(pr, tr) > minDistance) {
rightPts.push(lrp(pr, tr, streamline));

@@ -425,0 +443,0 @@ pr = tr;

2

dist/perfect-freehand.cjs.production.min.js

@@ -1,2 +0,2 @@

"use strict";function r(r,n,t){return r*(1-t)+n*t}function n(r,n,t){return Math.max(n,Math.min(t,r))}function t(t,e,i,o){return void 0===o&&(o=.5),e?(o=n(i(o),0,1),(e<0?r(t,t+t*n(e,-.95,-.05),o):r(t-t*n(e,.05,.95),t,o))/2):t/2}function e(r,n){return[r[0]+n[0],r[1]+n[1]]}function i(r,n){return[r[0]-n[0],r[1]-n[1]]}function o(r,n){return[n[0]-r[0],n[1]-r[1]]}function u(r,n){return[r[0]*n,r[1]*n]}function s(r){return[r[1],-r[0]]}function a(r,n){return r[0]*n[0]+r[1]*n[1]}function v(r){return function(r,n){return[r[0]/n,r[1]/n]}(r,function(r){return Math.hypot(r[0],r[1])}(r))}function f(r,n){return Math.hypot(r[1]-n[1],r[0]-n[0])}function c(r,n,t){var e=Math.sin(t),i=Math.cos(t),o=r[0]-n[0],u=r[1]-n[1];return[o*i-u*e+n[0],o*e+u*i+n[1]]}function p(r,n,t){return e(r,u(o(r,n),t))}Object.defineProperty(exports,"__esModule",{value:!0});var h=Math.min,d=Math.PI;function g(r,n){var t=n.simulatePressure,i=n.streamline,u=void 0===i?.5:i,s=n.size,c=void 0===s?8:s;u/=2,void 0===t||t||(u/=2);var h=function(r){return Array.isArray(r[0])?r.map((function(r){var n=r[2];return[r[0],r[1],void 0===n?.5:n]})):r.map((function(r){var n=r.pressure;return[r.x,r.y,void 0===n?.5:n]}))}(r),d=h.length;if(0===d)return[];1===d&&h.push(e(h[0],[1,0]));for(var g=[{point:[h[0][0],h[0][1]],pressure:h[0][2],vector:[0,0],distance:0,runningLength:0}],l=1,m=h[l],M=g[0];l<h.length;m=h[++l],M=g[l-1]){var L=p(M.point,m,1-u),x=m[2],y=v(o(L,M.point)),P=f(L,M.point);g.push({point:L,pressure:x,vector:y,distance:P,runningLength:M.runningLength+P})}for(var k=g[d-1].runningLength,b=d-2;b>1;b--){var z=g[b],A=z.vector;if(k-z.runningLength>c/2||a(g[b-1].vector,g[b].vector)<.8){for(var O=b;O<d;O++)g[O].vector=A;break}}return g}function l(r,n){void 0===n&&(n={});var g=n.size,l=void 0===g?8:g,m=n.thinning,M=void 0===m?.5:m,L=n.smoothing,x=void 0===L?.5:L,y=n.simulatePressure,P=void 0===y||y,k=n.easing,b=void 0===k?function(r){return r}:k,z=n.start,A=void 0===z?{}:z,O=n.end,S=void 0===O?{}:O,_=n.last,j=void 0!==_&&_,I=n.streamline,q=void 0===I?.5:I;q/=2;var w=A.taper,B=void 0===w?0:w,C=A.easing,D=void 0===C?function(r){return r*(2-r)}:C,E=S.taper,F=void 0===E?0:E,G=S.easing,H=void 0===G?function(r){return--r*r*r+1}:G,J=r.length;if(0===J)return[];for(var K=r[J-1].runningLength,N=[],Q=[],R=r.slice(0,5).reduce((function(r,n){return(r+n.pressure)/2}),r[0].pressure),T=t(l,M,b,r[J-1].pressure),U=r[0].vector,V=r[0].point,W=V,X=V,Y=W,Z=1;Z<J-1;Z++){var $=r[Z],rr=$.point,nr=$.pressure,tr=$.vector,er=$.distance,ir=$.runningLength;if(M){if(P){var or=h(1,1-er/l),ur=h(1,er/l);nr=h(1,R+ur/2*(or-R))}T=t(l,M,b,nr)}else T=l/2;var sr=ir<B?D(ir/B):1,ar=K-ir<F?H((K-ir)/F):1;T*=Math.min(sr,ar);var vr=r[Z+1].vector,fr=a(tr,vr);if(fr<0){for(var cr=u(s(U),T),pr=e(rr,cr),hr=i(rr,cr),dr=.2;dr<1;dr+=.2)Y=c(pr,rr,d*-dr),X=c(hr,rr,d*dr),Q.push(Y),N.push(X);V=X,W=Y}else{var gr=u(s(p(vr,tr,fr)),T);X=i(rr,gr),Y=e(rr,gr);var lr=1===Z||fr<.25,mr=(ir>l?l:l/2)*x;(lr||f(V,X)>mr)&&(N.push(p(V,X,q)),V=X),(lr||f(W,Y)>mr)&&(Q.push(p(W,Y,q)),W=Y),R=nr,U=tr}}var Mr=r[0],Lr=r[J-1],xr=Q.length<2||N.length<2;if(xr&&(!B&&!F||j)){for(var yr=0,Pr=0;Pr<J;Pr++){var kr=r[Pr];if(kr.runningLength>l){yr=t(l,M,b,kr.pressure);break}}for(var br=i(Mr.point,u(s(v(o(Lr.point,Mr.point))),yr||T)),zr=[],Ar=0;Ar<=1;Ar+=.1)zr.push(c(br,Mr.point,2*d*Ar));return zr}var Or=[];if(!(B||F&&xr)){for(var Sr=i(Mr.point,u(v(o(Y=Q[1],X=N[1])),f(Y,X)/2)),_r=0;_r<=1;_r+=.2)Or.push(c(Sr,Mr.point,d*_r));N.shift(),Q.shift()}var jr=[];if(F||B&&xr)jr.push(Lr.point);else for(var Ir=i(Lr.point,u(s(Lr.vector),T)),qr=0;qr<=1;qr+=.1)jr.push(c(Ir,Lr.point,3*d*qr));return N.concat(jr,Q.reverse(),Or)}exports.default=function(r,n){return void 0===n&&(n={}),l(g(r,n),n)},exports.getStrokeOutlinePoints=l,exports.getStrokePoints=g;
"use strict";function n(n,r,t){return n*(1-t)+r*t}function r(n,r,t){return Math.max(r,Math.min(t,n))}function t(t,e,i,o){return void 0===o&&(o=.5),e?(o=r(i(o),0,1),(e<0?n(t,t+t*r(e,-.95,-.05),o):n(t-t*r(e,.05,.95),t,o))/2):t/2}function e(n,r){return[n[0]+r[0],n[1]+r[1]]}function i(n,r){return[n[0]-r[0],n[1]-r[1]]}function o(n,r){return[r[0]-n[0],r[1]-n[1]]}function u(n,r){return[n[0]*r,n[1]*r]}function s(n){return[n[1],-n[0]]}function a(n,r){return n[0]*r[0]+n[1]*r[1]}function v(n,r){return function(n){return n[0]*n[0]+n[1]*n[1]}(i(n,r))}function f(n){return function(n,r){return[n[0]/r,n[1]/r]}(n,function(n){return Math.hypot(n[0],n[1])}(n))}function c(n,r){return Math.hypot(n[1]-r[1],n[0]-r[0])}function p(n,r,t){var e=Math.sin(t),i=Math.cos(t),o=n[0]-r[0],u=n[1]-r[1];return[o*i-u*e+r[0],o*e+u*i+r[1]]}function h(n,r,t){return e(n,u(o(n,r),t))}Object.defineProperty(exports,"__esModule",{value:!0});var d=Math.min,g=Math.PI;function l(n,r){var t=r.simulatePressure,i=r.streamline,u=void 0===i?.5:i,s=r.size,v=void 0===s?8:s;u/=2,void 0===t||t||(u/=2);var p=function(n){return Array.isArray(n[0])?n.map((function(n){var r=n[2];return[n[0],n[1],void 0===r?.5:r]})):n.map((function(n){var r=n.pressure;return[n.x,n.y,void 0===r?.5:r]}))}(n),d=p.length;if(0===d)return[];1===d&&p.push(e(p[0],[1,0]));for(var g=[{point:[p[0][0],p[0][1]],pressure:p[0][2],vector:[0,0],distance:0,runningLength:0}],l=1,m=p[l],M=g[0];l<p.length;m=p[++l],M=g[l-1]){var L=h(M.point,m,1-u),x=m[2],y=f(o(L,M.point)),P=c(L,M.point);g.push({point:L,pressure:x,vector:y,distance:P,runningLength:M.runningLength+P})}for(var k=g[d-1].runningLength,b=d-2;b>1;b--){var z=g[b],A=z.runningLength,O=z.vector,S=a(g[b-1].vector,g[b].vector);if(k-A>v/2||S<.8){for(var _=b;_<d;_++)g[_].vector=O;break}}return g}function m(n,r){void 0===r&&(r={});var l=r.size,m=void 0===l?8:l,M=r.thinning,L=void 0===M?.5:M,x=r.smoothing,y=void 0===x?.5:x,P=r.simulatePressure,k=void 0===P||P,b=r.easing,z=void 0===b?function(n){return n}:b,A=r.start,O=void 0===A?{}:A,S=r.end,_=void 0===S?{}:S,j=r.last,w=void 0!==j&&j,I=r.streamline,q=void 0===I?.5:I;q/=2;var B=O.taper,C=void 0===B?0:B,D=O.easing,E=void 0===D?function(n){return n*(2-n)}:D,F=_.taper,G=void 0===F?0:F,H=_.easing,J=void 0===H?function(n){return--n*n*n+1}:H,K=n.length;if(0===K)return[];for(var N=n[K-1].runningLength,Q=[],R=[],T=n.slice(0,5).reduce((function(n,r){return(n+r.pressure)/2}),n[0].pressure),U=t(m,L,z,n[K-1].pressure),V=n[0].vector,W=n[0].point,X=W,Y=W,Z=X,$=1;$<K-1;$++){var nn=n[$],rn=nn.point,tn=nn.pressure,en=nn.vector,on=nn.distance,un=nn.runningLength;if(L){if(k){var sn=d(1,1-on/m),an=d(1,on/m);tn=d(1,T+an/2*(sn-T))}U=t(m,L,z,tn)}else U=m/2;var vn=un<C?E(un/C):1,fn=N-un<G?J((N-un)/G):1;U*=Math.min(vn,fn);var cn=n[$+1].vector,pn=a(en,cn);if(pn<0){for(var hn=u(s(V),U),dn=e(rn,hn),gn=i(rn,hn),ln=.2;ln<1;ln+=.2)Z=p(dn,rn,g*-ln),Y=p(gn,rn,g*ln),R.push(Z),Q.push(Y);W=Y,X=Z}else{var mn=u(s(h(cn,en,pn)),U);Y=i(rn,mn),Z=e(rn,mn);var Mn=1===$||pn<.25,Ln=Math.pow((un>m?m:m/2)*y,2);(Mn||v(W,Y)>Ln)&&(Q.push(h(W,Y,q)),W=Y),(Mn||v(X,Z)>Ln)&&(R.push(h(X,Z,q)),X=Z),T=tn,V=en}}var xn=n[0],yn=n[K-1],Pn=R.length<2||Q.length<2;if(Pn&&(!C&&!G||w)){for(var kn=0,bn=0;bn<K;bn++){var zn=n[bn];if(zn.runningLength>m){kn=t(m,L,z,zn.pressure);break}}for(var An=i(xn.point,u(s(f(o(yn.point,xn.point))),kn||U)),On=[],Sn=0;Sn<=1;Sn+=.1)On.push(p(An,xn.point,2*g*Sn));return On}var _n=[];if(!(C||G&&Pn)){for(var jn=i(xn.point,u(f(o(Z=R[1],Y=Q[1])),c(Z,Y)/2)),wn=0;wn<=1;wn+=.2)_n.push(p(jn,xn.point,g*wn));Q.shift(),R.shift()}var In=[];if(G||C&&Pn)In.push(yn.point);else for(var qn=i(yn.point,u(s(yn.vector),U)),Bn=0;Bn<=1;Bn+=.1)In.push(p(qn,yn.point,3*g*Bn));return Q.concat(In,R.reverse(),_n)}exports.default=function(n,r){return void 0===r&&(r={}),m(l(n,r),r)},exports.getStrokeOutlinePoints=m,exports.getStrokePoints=l;
//# sourceMappingURL=perfect-freehand.cjs.production.min.js.map

@@ -127,2 +127,19 @@ function lerp(y1, y2, mu) {

/**
* Length of the vector squared
* @param A
*/
function len2(A) {
return A[0] * A[0] + A[1] * A[1];
}
/**
* Dist length from A to B squared.
* @param A
* @param B
*/
function dist2(A, B) {
return len2(sub(A, B));
}
/**
* Get normalized / unit vector.

@@ -236,4 +253,5 @@ * @param A

_vector = _strokePoints$_i.vector;
var dpr$1 = dpr(strokePoints[_i - 1].vector, strokePoints[_i].vector);
if (totalLength - _runningLength > size / 2 || dpr(strokePoints[_i - 1].vector, strokePoints[_i].vector) < 0.8) {
if (totalLength - _runningLength > size / 2 || dpr$1 < 0.8) {
for (var j = _i; j < len; j++) {

@@ -410,5 +428,5 @@ strokePoints[j].vector = _vector;

var alwaysAdd = i === 1 || dpr$1 < 0.25;
var minDistance = (runningLength > size ? size : size / 2) * smoothing;
var minDistance = Math.pow((runningLength > size ? size : size / 2) * smoothing, 2);
if (alwaysAdd || dist(pl, tl) > minDistance) {
if (alwaysAdd || dist2(pl, tl) > minDistance) {
leftPts.push(lrp(pl, tl, streamline));

@@ -418,3 +436,3 @@ pl = tl;

if (alwaysAdd || dist(pr, tr) > minDistance) {
if (alwaysAdd || dist2(pr, tr) > minDistance) {
rightPts.push(lrp(pr, tr, streamline));

@@ -421,0 +439,0 @@ pr = tr;

@@ -53,2 +53,13 @@ /**

/**
* Length of the vector squared
* @param A
*/
export declare function len2(A: number[]): number;
/**
* Dist length from A to B squared.
* @param A
* @param B
*/
export declare function dist2(A: number[], B: number[]): number;
/**
* Get normalized / unit vector.

@@ -55,0 +66,0 @@ * @param A

{
"version": "0.4.6",
"version": "0.4.7",
"name": "perfect-freehand",

@@ -4,0 +4,0 @@ "author": {

@@ -7,2 +7,4 @@ # ![Screenshot](screenshot.svg 'Perfect Freehand')

💰 Want to use this library in your commercial product? [Contact me here](steveruizok+perfectfreehand@gmail.com).
## Table of Contents

@@ -14,3 +16,3 @@

- [Discussion](#discussion)
- [Author](#Author)
- [Author](#author)

@@ -34,3 +36,3 @@ ## Installation

- accepts an array of points and an (optional) options object
- returns a stroke as an array of points formatted as `[x, y]`
- returns a stroke outline as an array of points formatted as `[x, y]`

@@ -71,3 +73,3 @@ ```js

| `end` | { } | | Tapering options for the end of the line. |
| `last` | boolean | false | Whether the stroke is complete. |
| `last` | boolean | true | Whether the stroke is complete. |

@@ -109,35 +111,52 @@ The `start` and `end` options accept an object:

While `getStroke` returns an array of points representing a stroke, it's up to you to decide how you will render the stroke. The library does not export any rendering solutions.
While `getStroke` returns an array of points representing the outline of a stroke, it's up to you to decide how you will render these points.
For example, the function below will turn a stroke into SVG path data for use with either [SVG paths](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d) or HTML Canvas (using the [`Path2D` constructor](https://developer.mozilla.org/en-US/docs/Web/API/Path2D/Path2D#using_svg_paths)).
The function below will turn the points returned by `getStroke` into SVG path data.
```js
// Create SVG path data using the points from perfect-freehand.
function getSvgPathFromStroke(points) {
const d = []
function getSvgPathFromStroke(stroke) {
if (!stroke.length) return ""
if (stroke.length < 3) {
return ''
}
const d = stroke.reduce(
(acc, [x0, y0], i, arr) => {
const [x1, y1] = arr[(i + 1) % arr.length]
acc.push(x0, y0, (x0 + x1) / 2, (y0 + y1) / 2)
return acc
},
["M", ...stroke[0], "Q"]
)
let p0 = stroke[stroke.length - 3]
let p1 = stroke[stroke.length - 2]
d.push("Z")
return d.join(" ")
}
```
d.push('M', p0[0], p0[1], 'Q')
To use this function, first use perfect-freehand to turn your input points into a stroke outline, then pass the result to `getSvgPathFromStroke`.
for (let i = 0; i < stroke.length; i++) {
d.push(p0[0], p0[1], (p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2)
p0 = p1
p1 = stroke[i]
}
```js
import getStroke from "perfect-freehand"
d.push('Z')
const myStroke = getStroke(myInputPoints)
return d.join(' ')
}
const pathData = getSvgPathFromStroke(myStroke)
```
To render a stroke as a flat polygon, add the [`polygon-clipping`](https://github.com/mfogel/polygon-clipping) package and use the following function together with the `getSvgPathFromStroke`.
You could then pass this string either to an [SVG path](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d) element:
```jsx
<path d={pathData}/>
```
Or, if you are rendering with HTML Canvas, you can pass the result to a [`Path2D` constructor](https://developer.mozilla.org/en-US/docs/Web/API/Path2D/Path2D#using_svg_paths)).
```js
const myPath = new Path2D(pathData)
ctx.fill(myPath)
```
### Flattening
To render a stroke as a "flattened" polygon, add the [`polygon-clipping`](https://github.com/mfogel/polygon-clipping) package and use the following function together with the `getSvgPathFromStroke`.
```js
import polygonClipping from 'polygon-clipping'

@@ -165,24 +184,18 @@

```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"
export default function Example() {
const [currentMark, setCurrentMark] = React.useState()
const [points, setPoints] = React.useState()
function handlePointerDown(e) {
e.preventDefault()
setCurrentMark({
type: e.pointerType,
points: [[e.pageX, e.pageY, e.pressure]],
})
setPoints([[e.pageX, e.pageY, e.pressure]])
}
function handlePointerMove(e) {
e.preventDefault()
if (e.buttons === 1) {
setCurrentMark({
...currentMark,
points: [...currentMark.points, [e.pageX, e.pageY, e.pressure]],
})
e.preventDefault()
setPoints([...points, [e.pageX, e.pageY, e.pressure]])
}

@@ -195,13 +208,12 @@ }

onPointerMove={handlePointerMove}
style={{ touchAction: 'none' }}
style={{ touchAction: "none" }}
>
{currentMark && (
{points && (
<path
d={getSvgPathFromStroke(
getStroke(currentMark.points, {
getStroke(points, {
size: 24,
thinning: 0.75,
thinning: 0.5,
smoothing: 0.5,
streamline: 0.5,
simulatePressure: currentMark.type !== 'pen',
streamline: 0.5
})

@@ -208,0 +220,0 @@ )}

@@ -27,2 +27,3 @@ import { toPointsArray, getStrokeRadius } from './utils'

const pts = toPointsArray(points)
const len = pts.length

@@ -78,6 +79,4 @@

const { runningLength, vector } = strokePoints[i]
if (
totalLength - runningLength > size / 2 ||
vec.dpr(strokePoints[i - 1].vector, strokePoints[i].vector) < 0.8
) {
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++) {

@@ -265,5 +264,8 @@ strokePoints[j].vector = vector

const alwaysAdd = i === 1 || dpr < 0.25
const minDistance = (runningLength > size ? size : size / 2) * smoothing
const minDistance = Math.pow(
(runningLength > size ? size : size / 2) * smoothing,
2
)
if (alwaysAdd || vec.dist(pl, tl) > minDistance) {
if (alwaysAdd || vec.dist2(pl, tl) > minDistance) {
leftPts.push(vec.lrp(pl, tl, streamline))

@@ -273,3 +275,3 @@ pl = tl

if (alwaysAdd || vec.dist(pr, tr) > minDistance) {
if (alwaysAdd || vec.dist2(pr, tr) > minDistance) {
rightPts.push(vec.lrp(pr, tr, streamline))

@@ -276,0 +278,0 @@ pr = tr

@@ -81,2 +81,19 @@ /**

/**
* Length of the vector squared
* @param A
*/
export function len2(A: number[]) {
return A[0] * A[0] + A[1] * A[1]
}
/**
* Dist length from A to B squared.
* @param A
* @param B
*/
export function dist2(A: number[], B: number[]) {
return len2(sub(A, B))
}
/**
* Get normalized / unit vector.

@@ -83,0 +100,0 @@ * @param A

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc