Socket
Socket
Sign inDemoInstall

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.5.2 to 0.5.3

2

dist/cjs/index.js

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

var me=Object.defineProperty;var ge=e=>me(e,"__esModule",{value:!0});var ke=(e,t)=>{ge(e);for(var n in t)me(e,n,{get:t[n],enumerable:!0})};ke(exports,{default:()=>he,getStrokeOutlinePoints:()=>de,getStrokePoints:()=>be});function b(e,t){return[e[0]+t[0],e[1]+t[1]]}function d(e,t){return[e[0]-t[0],e[1]-t[1]]}function J(e,t){return[t[0]-e[0],t[1]-e[1]]}function p(e,t){return[e[0]*t,e[1]*t]}function Pe(e,t){return[e[0]/t,e[1]/t]}function E(e){return[e[1],-e[0]]}function ae(e,t){return e[0]*t[0]+e[1]*t[1]}function Se(e){return Math.hypot(e[0],e[1])}function ye(e){return e[0]*e[0]+e[1]*e[1]}function ee(e,t){return ye(d(e,t))}function I(e){return Pe(e,Se(e))}function W(e,t){return Math.hypot(e[1]-t[1],e[0]-t[0])}function le(e,t){return p(b(e,t),.5)}function C(e,t,n){let c=Math.sin(n),i=Math.cos(n),P=e[0]-t[0],h=e[1]-t[1],O=P*i-h*c,q=P*c+h*i;return[O+t[0],q+t[1]]}function w(e,t,n){return b(e,p(J(e,t),n))}function F(e,t){return e[0]===t[0]&&e[1]===t[1]}function ve(e,t,n){return e*(1-n)+t*n}function te(e,t,n){return Math.max(t,Math.min(n,e))}function fe(e){return Array.isArray(e[0])?e.map(([t,n,c=.5])=>[t,n,c]):e.map(({x:t,y:n,pressure:c=.5})=>[t,n,c])}function X(e,t,n,c=.5){return t?(c=te(n(c),0,1),(t<0?ve(e,e+e*te(t,-.95,-.05),c):ve(e-e*te(t,.05,.95),e,c))/2):e/2}var{min:ne,PI:N}=Math;function be(e,t={}){let{streamline:n=.5}=t,{simulatePressure:c=!0}=t;n=n/(c?4:2);let i=fe(e);if(i.length===0)return[];i.length===1&&i.push([...b(i[0],[1,1]),i[0][2]]);let P=[{point:[i[0][0],i[0][1]],pressure:i[0][2],vector:[0,0],distance:0,runningLength:0}];for(let h=1,O=0,q=i[h],M=P[O];h<i.length;h++,q=i[h],M=P[O]){let L=w(M.point,q,1-n);if(F(M.point,L))continue;let Y=I(d(M.point,L)),S=W(L,M.point),Z=M.runningLength+S,_={point:L,pressure:q[2],vector:Y,distance:S,runningLength:Z};P.push(_),O+=1}return P}function de(e,t={}){let{size:n=8,thinning:c=.5,smoothing:i=.5,simulatePressure:P=!0,easing:h=u=>u,start:O={},end:q={},last:M=!1}=t,{streamline:L=.5}=t;L/=2;let{cap:Y=!0,taper:S=0,easing:Z=u=>u*(2-u)}=O,{cap:_=!0,taper:R=0,easing:xe=u=>--u*u*u+1}=q,V=e.length;if(V===0)return[];let re=e[V-1].runningLength,v=[],g=[],$=e.slice(0,5).reduce((u,y)=>(u+y.pressure)/2,e[0].pressure),k=X(n,c,h,e[V-1].pressure),ue,oe=e[0].vector,j=e[0].point,G=j,a=j,m=G,Q=!0;for(let u=0;u<V-1;u++){let{pressure:y}=e[u],{point:l,vector:o,distance:f,runningLength:r}=e[u];if(u>0&&Q&&r<n/2)continue;if(Q&&(Q=!1),c){if(P){let U=ne(1,1-f/n),D=ne(1,f/n);y=ne(1,$+(U-$)*(D/2))}k=X(n,c,h,y)}else k=n/2;ue===void 0&&(ue=k);let s=r<S?Z(r/S):1,H=re-r<R?xe((re-r)/R):1;k=Math.max(.01,k*Math.min(s,H));let se=e[u+1].vector,A=ae(o,se);if(A<0){let U=p(E(oe),k);for(let D=0;D<1;D+=.2)m=C(b(l,U),l,N*-D),a=C(d(l,U),l,N*D),g.push(m),v.push(a);j=a,G=m;continue}let ce=p(E(w(se,o,A)),k);a=d(l,ce),m=b(l,ce);let ie=u<2||A<.25,pe=Math.pow(Math.max((r>n?n:n/2)*i,1),2);(ie||ee(j,a)>pe)&&(v.push(w(j,a,L)),j=a),(ie||ee(G,m)>pe)&&(g.push(w(G,m,L)),G=m),$=y,oe=o}let x=e[0],B=e[V-1],z=Q||g.length<2||v.length<2;if(z&&(!(S||R)||M)){let u=0;for(let o=0;o<V;o++){let{pressure:f,runningLength:r}=e[o];if(r>n){u=X(n,c,h,f);break}}let y=d(x.point,p(E(I(J(B.point,x.point))),u||k)),l=[];for(let o=0,f=.1;o<=1;o+=f)l.push(C(y,x.point,N*2*o));return l}let K=[],T=[];if(v.length>1&&g.length>1){m=g[1];for(let r=1;r<v.length;r++)if(!F(m,v[r])){a=v[r];break}if(Y||S)if(!S&&!(R&&z)){if(!F(m,a)){let r=d(x.point,p(I(J(m,a)),W(m,a)/2));for(let s=0,H=.1;s<=1;s+=H)K.push(C(r,x.point,N*s));v.shift(),g.shift()}}else K.push(x.point,b(x.point,[.1,.1]));else if(!F(m,a)){let r=I(J(m,a)),s=W(m,a)/2;K.push(d(x.point,p(r,s*.95))),K.push(d(x.point,p(r,s))),K.push(b(x.point,p(r,s))),K.push(b(x.point,p(r,s*.95))),v.shift(),g.shift()}let u=v[v.length-1],y=g[g.length-1],l=le(u,y),o=t.last?B.point:w(l,B.point,.618),f=I(d(o,l));if(_||R)if(!R&&!(S&&z)){let r=b(o,p(E(f),k));for(let s=0,H=.15;s<=1;s+=H)T.push(C(r,o,N*3*s))}else T.push(o);else{let r=w(l,o,.95),s=k*.95;T.push(b(r,p(E(f),s))),T.push(b(o,p(E(f),s))),T.push(d(o,p(E(f),s))),T.push(d(r,p(E(f),s)))}}return v.concat(T,g.reverse(),K)}function he(e,t={}){return de(be(e,t),t)}
var me=Object.defineProperty;var xe=e=>me(e,"__esModule",{value:!0});var ge=(e,t)=>{xe(e);for(var n in t)me(e,n,{get:t[n],enumerable:!0})};ge(exports,{default:()=>Ee,getStrokeOutlinePoints:()=>de,getStrokePoints:()=>fe});function d(e,t){return[e[0]+t[0],e[1]+t[1]]}function h(e,t){return[e[0]-t[0],e[1]-t[1]]}function W(e,t){return[t[0]-e[0],t[1]-e[1]]}function i(e,t){return[e[0]*t,e[1]*t]}function ke(e,t){return[e[0]/t,e[1]/t]}function y(e){return[e[1],-e[0]]}function le(e,t){return e[0]*t[0]+e[1]*t[1]}function Se(e){return Math.hypot(e[0],e[1])}function Pe(e){return e[0]*e[0]+e[1]*e[1]}function te(e,t){return Pe(h(e,t))}function I(e){return ke(e,Se(e))}function F(e,t){return Math.hypot(e[1]-t[1],e[0]-t[0])}function ae(e,t){return i(d(e,t),.5)}function G(e,t,n){let c=Math.sin(n),w=Math.cos(n),p=e[0]-t[0],k=e[1]-t[1],v=p*w-k*c,R=p*c+k*w;return[v+t[0],R+t[1]]}function H(e,t,n){return d(e,i(W(e,t),n))}function X(e,t){return e[0]===t[0]&&e[1]===t[1]}function be(e,t,n){return e*(1-n)+t*n}function ne(e,t,n){return Math.max(t,Math.min(n,e))}function ve(e){return Array.isArray(e[0])?e.map(([t,n,c=.5])=>[t,n,c]):e.map(({x:t,y:n,pressure:c=.5})=>[t,n,c])}function $(e,t,n,c=.5){return t?(c=ne(n(c),0,1),(t<0?be(e,e+e*ne(t,-.95,-.05),c):be(e-e*ne(t,.05,.95),e,c))/2):e/2}var{min:J,PI:Y}=Math;function fe(e,t={}){let{streamline:n=.5,last:c=!1}=t,{simulatePressure:w=!0}=t;if(e.length===0)return[];n=n/(w?3:2);let p=ve(e);p.length===1&&p.push([...d(p[0],[1,1]),p[0][2]]);let k=[],v={point:[p[0][0],p[0][1]],pressure:p[0][2],vector:[0,0],distance:0,runningLength:0};k.push(v);let R=p.length,V;for(let E=0;E<R;E++){V=p[E];let K=c&&E===R-1?V:H(v.point,V,1-n);if(X(v.point,K))continue;let M=I(h(v.point,K)),Z=F(K,v.point),B=v.runningLength+Z;v={point:K,pressure:V[2],vector:M,distance:Z,runningLength:B},k.push(v)}return k}function de(e,t={}){let{size:n=8,thinning:c=.5,smoothing:w=.5,simulatePressure:p=!0,easing:k=r=>r,start:v={},end:R={},last:V=!1}=t,{streamline:E=.5}=t;E/=2;let{cap:K=!0,taper:M=0,easing:Z=r=>r*(2-r)}=v,{cap:B=!0,taper:T=0,easing:he=r=>--r*r*r+1}=R,j=e.length;if(j===0)return[];let re=e[j-1].runningLength,f=[],S=[],z=e.slice(0,10).reduce((r,g)=>{let a=g.pressure;if(p){let o=J(1,g.distance/n),b=J(1,1-o);a=J(1,r+(b-r)*(o/4))}return(r+a)/2},e[0].pressure),P=$(n,c,k,e[j-1].pressure),ue,oe=e[0].vector,C=e[0].point,N=C,m=C,l=N,_=!0;for(let r=0;r<j-1;r++){let{pressure:g}=e[r],{point:a,vector:o,distance:b,runningLength:u}=e[r];if(r>0&&_&&u<n/2)continue;if(_&&(_=!1),c){if(p){let U=J(1,b/n),D=J(1,1-U);g=J(1,z+(D-z)*(U/4))}P=$(n,c,k,g)}else P=n/2;ue===void 0&&(ue=P);let s=u<M?Z(u/M):1,Q=re-u<T?he((re-u)/T):1;P=Math.max(.01,P*Math.min(s,Q));let O=e[r+1].vector,ee=le(o,O);if(ee<0){let U=i(y(oe),P);for(let D=0;D<1;D+=.2)l=G(d(a,U),a,Y*-D),m=G(h(a,U),a,Y*D),S.push(l),f.push(m);C=m,N=l;continue}let ce=i(y(H(O,o,ee)),P);m=h(a,ce),l=d(a,ce);let ie=r<2||ee<.25,pe=Math.pow(Math.max((u>n?n:n/2)*w,1),2);(ie||te(C,m)>pe)&&(f.push(H(C,m,E)),C=m),(ie||te(N,l)>pe)&&(S.push(H(N,l,E)),N=l),z=g,oe=o}let x=e[0],se=e[j-1],A=_||S.length<2||f.length<2;if(A&&(!(M||T)||V)){let r=0;for(let o=0;o<j;o++){let{pressure:b,runningLength:u}=e[o];if(u>n){r=$(n,c,k,b);break}}let g=h(x.point,i(y(I(W(se.point,x.point))),r||P)),a=[];for(let o=0,b=.1;o<=1;o+=b)a.push(G(g,x.point,Y*2*o));return a}let L=[],q=[];if(f.length>1&&S.length>1){l=S[1];for(let u=1;u<f.length;u++)if(!X(l,f[u])){m=f[u];break}if(K||M)if(!M&&!(T&&A)){if(!X(l,m)){let u=h(x.point,i(I(W(l,m)),F(l,m)/2));for(let s=0,Q=.1;s<=1;s+=Q){let O=G(u,x.point,Y*s);if(F(O,m)<1)break;L.push(O)}f.shift(),S.shift()}}else L.push(x.point,d(x.point,[.1,.1]));else if(!X(l,m)){let u=I(W(l,m)),s=F(l,m)/2;L.push(h(x.point,i(u,s*.95))),L.push(h(x.point,i(u,s))),L.push(d(x.point,i(u,s))),L.push(d(x.point,i(u,s*.95))),f.shift(),S.shift()}let r=f[f.length-1],g=S[S.length-1],a=ae(r,g),o=se.point,b=I(h(o,a));if(B||T)if(!T&&!(M&&A)){let u=d(o,i(y(b),P));for(let s=0,Q=.1;s<=1;s+=Q){let O=G(u,o,Y*3*s);if(F(O,g)<1)break;q.push(O)}}else q.push(o);else{let u=H(a,o,.95),s=P*.95;q.push(d(u,i(y(b),s))),q.push(d(o,i(y(b),s))),q.push(h(o,i(y(b),s))),q.push(h(u,i(y(b),s)))}}return f.concat(q,S.reverse(),L)}function ye(e,t={}){return de(fe(e,t),t)}var Ee=ye;

@@ -1,1 +0,316 @@

function b(e,t){return[e[0]+t[0],e[1]+t[1]]}function d(e,t){return[e[0]-t[0],e[1]-t[1]]}function J(e,t){return[t[0]-e[0],t[1]-e[1]]}function p(e,t){return[e[0]*t,e[1]*t]}function be(e,t){return[e[0]/t,e[1]/t]}function E(e){return[e[1],-e[0]]}function me(e,t){return e[0]*t[0]+e[1]*t[1]}function de(e){return Math.hypot(e[0],e[1])}function he(e){return e[0]*e[0]+e[1]*e[1]}function ee(e,t){return he(d(e,t))}function I(e){return be(e,de(e))}function W(e,t){return Math.hypot(e[1]-t[1],e[0]-t[0])}function ae(e,t){return p(b(e,t),.5)}function C(e,t,n){let c=Math.sin(n),i=Math.cos(n),P=e[0]-t[0],h=e[1]-t[1],O=P*i-h*c,q=P*c+h*i;return[O+t[0],q+t[1]]}function w(e,t,n){return b(e,p(J(e,t),n))}function F(e,t){return e[0]===t[0]&&e[1]===t[1]}function le(e,t,n){return e*(1-n)+t*n}function te(e,t,n){return Math.max(t,Math.min(n,e))}function ve(e){return Array.isArray(e[0])?e.map(([t,n,c=.5])=>[t,n,c]):e.map(({x:t,y:n,pressure:c=.5})=>[t,n,c])}function X(e,t,n,c=.5){return t?(c=te(n(c),0,1),(t<0?le(e,e+e*te(t,-.95,-.05),c):le(e-e*te(t,.05,.95),e,c))/2):e/2}var{min:ne,PI:N}=Math;function xe(e,t={}){let{streamline:n=.5}=t,{simulatePressure:c=!0}=t;n=n/(c?4:2);let i=ve(e);if(i.length===0)return[];i.length===1&&i.push([...b(i[0],[1,1]),i[0][2]]);let P=[{point:[i[0][0],i[0][1]],pressure:i[0][2],vector:[0,0],distance:0,runningLength:0}];for(let h=1,O=0,q=i[h],M=P[O];h<i.length;h++,q=i[h],M=P[O]){let L=w(M.point,q,1-n);if(F(M.point,L))continue;let Y=I(d(M.point,L)),S=W(L,M.point),Z=M.runningLength+S,_={point:L,pressure:q[2],vector:Y,distance:S,runningLength:Z};P.push(_),O+=1}return P}function ge(e,t={}){let{size:n=8,thinning:c=.5,smoothing:i=.5,simulatePressure:P=!0,easing:h=u=>u,start:O={},end:q={},last:M=!1}=t,{streamline:L=.5}=t;L/=2;let{cap:Y=!0,taper:S=0,easing:Z=u=>u*(2-u)}=O,{cap:_=!0,taper:R=0,easing:fe=u=>--u*u*u+1}=q,V=e.length;if(V===0)return[];let re=e[V-1].runningLength,v=[],g=[],$=e.slice(0,5).reduce((u,y)=>(u+y.pressure)/2,e[0].pressure),k=X(n,c,h,e[V-1].pressure),ue,oe=e[0].vector,j=e[0].point,G=j,a=j,m=G,Q=!0;for(let u=0;u<V-1;u++){let{pressure:y}=e[u],{point:l,vector:o,distance:f,runningLength:r}=e[u];if(u>0&&Q&&r<n/2)continue;if(Q&&(Q=!1),c){if(P){let U=ne(1,1-f/n),D=ne(1,f/n);y=ne(1,$+(U-$)*(D/2))}k=X(n,c,h,y)}else k=n/2;ue===void 0&&(ue=k);let s=r<S?Z(r/S):1,H=re-r<R?fe((re-r)/R):1;k=Math.max(.01,k*Math.min(s,H));let se=e[u+1].vector,A=me(o,se);if(A<0){let U=p(E(oe),k);for(let D=0;D<1;D+=.2)m=C(b(l,U),l,N*-D),a=C(d(l,U),l,N*D),g.push(m),v.push(a);j=a,G=m;continue}let ce=p(E(w(se,o,A)),k);a=d(l,ce),m=b(l,ce);let ie=u<2||A<.25,pe=Math.pow(Math.max((r>n?n:n/2)*i,1),2);(ie||ee(j,a)>pe)&&(v.push(w(j,a,L)),j=a),(ie||ee(G,m)>pe)&&(g.push(w(G,m,L)),G=m),$=y,oe=o}let x=e[0],B=e[V-1],z=Q||g.length<2||v.length<2;if(z&&(!(S||R)||M)){let u=0;for(let o=0;o<V;o++){let{pressure:f,runningLength:r}=e[o];if(r>n){u=X(n,c,h,f);break}}let y=d(x.point,p(E(I(J(B.point,x.point))),u||k)),l=[];for(let o=0,f=.1;o<=1;o+=f)l.push(C(y,x.point,N*2*o));return l}let K=[],T=[];if(v.length>1&&g.length>1){m=g[1];for(let r=1;r<v.length;r++)if(!F(m,v[r])){a=v[r];break}if(Y||S)if(!S&&!(R&&z)){if(!F(m,a)){let r=d(x.point,p(I(J(m,a)),W(m,a)/2));for(let s=0,H=.1;s<=1;s+=H)K.push(C(r,x.point,N*s));v.shift(),g.shift()}}else K.push(x.point,b(x.point,[.1,.1]));else if(!F(m,a)){let r=I(J(m,a)),s=W(m,a)/2;K.push(d(x.point,p(r,s*.95))),K.push(d(x.point,p(r,s))),K.push(b(x.point,p(r,s))),K.push(b(x.point,p(r,s*.95))),v.shift(),g.shift()}let u=v[v.length-1],y=g[g.length-1],l=ae(u,y),o=t.last?B.point:w(l,B.point,.618),f=I(d(o,l));if(_||R)if(!R&&!(S&&z)){let r=b(o,p(E(f),k));for(let s=0,H=.15;s<=1;s+=H)T.push(C(r,o,N*3*s))}else T.push(o);else{let r=w(l,o,.95),s=k*.95;T.push(b(r,p(E(f),s))),T.push(b(o,p(E(f),s))),T.push(d(o,p(E(f),s))),T.push(d(r,p(E(f),s)))}}return v.concat(T,g.reverse(),K)}function ke(e,t={}){return ge(xe(e,t),t)}export{ke as default,ge as getStrokeOutlinePoints,xe as getStrokePoints};
// src/vec.ts
function add(A, B) {
return [A[0] + B[0], A[1] + B[1]];
}
function sub(A, B) {
return [A[0] - B[0], A[1] - B[1]];
}
function vec(A, B) {
return [B[0] - A[0], B[1] - A[1]];
}
function mul(A, n) {
return [A[0] * n, A[1] * n];
}
function div(A, n) {
return [A[0] / n, A[1] / n];
}
function per(A) {
return [A[1], -A[0]];
}
function dpr(A, B) {
return A[0] * B[0] + A[1] * B[1];
}
function len(A) {
return Math.hypot(A[0], A[1]);
}
function len2(A) {
return A[0] * A[0] + A[1] * A[1];
}
function dist2(A, B) {
return len2(sub(A, B));
}
function uni(A) {
return div(A, len(A));
}
function dist(A, B) {
return Math.hypot(A[1] - B[1], A[0] - B[0]);
}
function med(A, B) {
return mul(add(A, B), 0.5);
}
function rotAround(A, C, r) {
const s = Math.sin(r);
const c = Math.cos(r);
const px = A[0] - C[0];
const py = A[1] - C[1];
const nx = px * c - py * s;
const ny = px * s + py * c;
return [nx + C[0], ny + C[1]];
}
function lrp(A, B, t) {
return add(A, mul(vec(A, B), t));
}
function isEqual(a, b) {
return a[0] === b[0] && a[1] === b[1];
}
// src/utils.ts
function lerp(y1, y2, mu) {
return y1 * (1 - mu) + y2 * mu;
}
function clamp(n, a, b) {
return Math.max(a, Math.min(b, n));
}
function toPointsArray(points) {
if (Array.isArray(points[0])) {
return points.map(([x, y, pressure = 0.5]) => [
x,
y,
pressure
]);
} else {
return points.map(({ x, y, pressure = 0.5 }) => [x, y, pressure]);
}
}
function getStrokeRadius(size, thinning, easing, pressure = 0.5) {
if (!thinning)
return size / 2;
pressure = clamp(easing(pressure), 0, 1);
return (thinning < 0 ? lerp(size, size + size * clamp(thinning, -0.95, -0.05), pressure) : lerp(size - size * clamp(thinning, 0.05, 0.95), size, pressure)) / 2;
}
// src/index.ts
var { min, PI } = Math;
function getStrokePoints(points, options = {}) {
let { streamline = 0.5, last: isComplete = false } = options;
const { simulatePressure = true } = options;
if (points.length === 0)
return [];
streamline = streamline / (simulatePressure ? 3 : 2);
const pts = toPointsArray(points);
if (pts.length === 1)
pts.push([...add(pts[0], [1, 1]), pts[0][2]]);
const strokePoints = [];
let prev = {
point: [pts[0][0], pts[0][1]],
pressure: pts[0][2],
vector: [0, 0],
distance: 0,
runningLength: 0
};
strokePoints.push(prev);
const len3 = pts.length;
let curr;
for (let i = 0; i < len3; i++) {
curr = pts[i];
const point = isComplete && i === len3 - 1 ? curr : lrp(prev.point, curr, 1 - streamline);
if (isEqual(prev.point, point))
continue;
const vector = uni(sub(prev.point, point));
const distance = dist(point, prev.point);
const runningLength = prev.runningLength + distance;
prev = {
point,
pressure: curr[2],
vector,
distance,
runningLength
};
strokePoints.push(prev);
}
return strokePoints;
}
function getStrokeOutlinePoints(points, options = {}) {
const {
size = 8,
thinning = 0.5,
smoothing = 0.5,
simulatePressure = true,
easing = (t) => t,
start = {},
end = {},
last: isComplete = false
} = options;
let { streamline = 0.5 } = options;
streamline /= 2;
const {
cap: capStart = true,
taper: taperStart = 0,
easing: taperStartEase = (t) => t * (2 - t)
} = start;
const {
cap: capEnd = true,
taper: taperEnd = 0,
easing: taperEndEase = (t) => --t * t * t + 1
} = end;
const len3 = points.length;
if (len3 === 0)
return [];
const totalLength = points[len3 - 1].runningLength;
const leftPts = [];
const rightPts = [];
let prevPressure = points.slice(0, 10).reduce((acc, curr) => {
let pressure = curr.pressure;
if (simulatePressure) {
const sp = min(1, curr.distance / size);
const rp = min(1, 1 - sp);
pressure = min(1, acc + (rp - acc) * (sp / 4));
}
return (acc + pressure) / 2;
}, points[0].pressure);
let radius = getStrokeRadius(size, thinning, easing, points[len3 - 1].pressure);
let firstRadius = void 0;
let prevVector = points[0].vector;
let pl = points[0].point;
let pr = pl;
let tl = pl;
let tr = pr;
let short = true;
for (let i = 0; i < len3 - 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;
}
if (thinning) {
if (simulatePressure) {
const sp = min(1, distance / size);
const rp = min(1, 1 - sp);
pressure = min(1, prevPressure + (rp - prevPressure) * (sp / 4));
}
radius = getStrokeRadius(size, thinning, easing, pressure);
} else {
radius = size / 2;
}
if (firstRadius === void 0) {
firstRadius = radius;
}
const ts = runningLength < taperStart ? taperStartEase(runningLength / taperStart) : 1;
const te = totalLength - runningLength < taperEnd ? taperEndEase((totalLength - runningLength) / taperEnd) : 1;
radius = Math.max(0.01, radius * Math.min(ts, te));
const nextVector = points[i + 1].vector;
const dpr2 = dpr(vector, nextVector);
if (dpr2 < 0) {
const offset2 = mul(per(prevVector), radius);
for (let t = 0; t < 1; t += 0.2) {
tr = rotAround(add(point, offset2), point, PI * -t);
tl = rotAround(sub(point, offset2), point, PI * t);
rightPts.push(tr);
leftPts.push(tl);
}
pl = tl;
pr = tr;
continue;
}
const offset = mul(per(lrp(nextVector, vector, dpr2)), radius);
tl = sub(point, offset);
tr = add(point, offset);
const alwaysAdd = i < 2 || dpr2 < 0.25;
const minDistance = Math.pow(Math.max((runningLength > size ? size : size / 2) * smoothing, 1), 2);
if (alwaysAdd || dist2(pl, tl) > minDistance) {
leftPts.push(lrp(pl, tl, streamline));
pl = tl;
}
if (alwaysAdd || dist2(pr, tr) > minDistance) {
rightPts.push(lrp(pr, tr, streamline));
pr = tr;
}
prevPressure = pressure;
prevVector = vector;
}
const firstPoint = points[0];
const lastPoint = points[len3 - 1];
const isVeryShort = short || rightPts.length < 2 || leftPts.length < 2;
if (isVeryShort && (!(taperStart || taperEnd) || isComplete)) {
let ir = 0;
for (let i = 0; i < len3; i++) {
const { pressure, runningLength } = points[i];
if (runningLength > size) {
ir = getStrokeRadius(size, thinning, easing, pressure);
break;
}
}
const start2 = sub(firstPoint.point, mul(per(uni(vec(lastPoint.point, firstPoint.point))), ir || radius));
const dotPts = [];
for (let t = 0, step = 0.1; t <= 1; t += step) {
dotPts.push(rotAround(start2, firstPoint.point, PI * 2 * t));
}
return dotPts;
}
const startCap = [];
const endCap = [];
if (leftPts.length > 1 && rightPts.length > 1) {
tr = rightPts[1];
for (let i = 1; i < leftPts.length; i++) {
if (!isEqual(tr, leftPts[i])) {
tl = leftPts[i];
break;
}
}
if (capStart || taperStart) {
if (!taperStart && !(taperEnd && isVeryShort)) {
if (!isEqual(tr, tl)) {
const start2 = sub(firstPoint.point, mul(uni(vec(tr, tl)), dist(tr, tl) / 2));
for (let t = 0, step = 0.1; t <= 1; t += step) {
const pt = rotAround(start2, firstPoint.point, PI * t);
if (dist(pt, tl) < 1)
break;
startCap.push(pt);
}
leftPts.shift();
rightPts.shift();
}
} else {
startCap.push(firstPoint.point, add(firstPoint.point, [0.1, 0.1]));
}
} else {
if (!isEqual(tr, tl)) {
const vector2 = uni(vec(tr, tl));
const dist3 = dist(tr, tl) / 2;
startCap.push(sub(firstPoint.point, mul(vector2, dist3 * 0.95)));
startCap.push(sub(firstPoint.point, mul(vector2, dist3)));
startCap.push(add(firstPoint.point, mul(vector2, dist3)));
startCap.push(add(firstPoint.point, mul(vector2, dist3 * 0.95)));
leftPts.shift();
rightPts.shift();
}
}
const ll = leftPts[leftPts.length - 1];
const lr = rightPts[rightPts.length - 1];
const mid = med(ll, lr);
const last = lastPoint.point;
const vector = uni(sub(last, mid));
if (capEnd || taperEnd) {
if (!taperEnd && !(taperStart && isVeryShort)) {
const start2 = add(last, mul(per(vector), radius));
for (let t = 0, step = 0.1; t <= 1; t += step) {
const pt = rotAround(start2, last, PI * 3 * t);
if (dist(pt, lr) < 1)
break;
endCap.push(pt);
}
} else {
endCap.push(last);
}
} else {
const justBefore = lrp(mid, last, 0.95);
const r = radius * 0.95;
endCap.push(add(justBefore, mul(per(vector), r)));
endCap.push(add(last, mul(per(vector), r)));
endCap.push(sub(last, mul(per(vector), r)));
endCap.push(sub(justBefore, mul(per(vector), r)));
}
}
return leftPts.concat(endCap, rightPts.reverse(), startCap);
}
function getStroke(points, options = {}) {
return getStrokeOutlinePoints(getStrokePoints(points, options), options);
}
var src_default = getStroke;
export {
src_default as default,
getStrokeOutlinePoints,
getStrokePoints
};
import type { StrokeOptions, StrokePoint } from './types';
/**
* ## getStrokePoints
* @description Get points for a stroke.
* @description Get points for a stroke. Returns an array of objects with an adjusted point, pressure, vector, distance, and runningLength.
* @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional.

@@ -9,7 +9,7 @@ * @param streamline How much to streamline the stroke.

*/
export declare function getStrokePoints<T extends number[], K extends {
export declare function getStrokePoints(points: number[][] | {
x: number;
y: number;
pressure?: number;
}>(points: (T | K)[], options?: StrokeOptions): StrokePoint[];
}[], options?: StrokeOptions): StrokePoint[];
/**

@@ -33,4 +33,4 @@ * ## getStrokeOutlinePoints

* @description Returns a stroke as an array of outline points.
* @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional.
* @param options An (optional) object with options.
* @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional in both cases.
* @param options (optional) An object with options.
* @param options.size The base size (diameter) of the stroke.

@@ -45,7 +45,9 @@ * @param options.thinning The effect of pressure on the stroke's size.

*/
export default function getStroke<T extends number[], K extends {
declare function getStroke(points: number[][], options?: StrokeOptions): number[][];
declare function getStroke(points: {
x: number;
y: number;
pressure?: number;
}>(points: (T | K)[], options?: StrokeOptions): number[][];
}[], options?: StrokeOptions): number[][];
export default getStroke;
export { StrokeOptions };

@@ -23,5 +23,10 @@ export interface StrokeOptions {

pressure: number;
distance: number;
vector: number[];
distance: number;
runningLength: number;
}
export declare type InputPoint = {
x: number;
y: number;
pressure?: number;
} | number[];
export declare function lerp(y1: number, y2: number, mu: number): number;
export declare function clamp(n: number, a: number, b: number): number;
/**
* Convert an array of points to the correct format ([x, y, radius])
* @param points
* @returns
* Convert an array of points to the correct format ([x, y, pressure])
* @param points The points to format, an array of arrays or objects
* @returns number[][]
*/

@@ -22,2 +22,1 @@ export declare function toPointsArray<T extends number[], K extends {

export declare function getStrokeRadius(size: number, thinning: number, easing: (t: number) => number, pressure?: number): number;
export declare function withoutDuplicates(pts: number[][]): number[][];
{
"version": "0.5.2",
"version": "0.5.3",
"name": "perfect-freehand",

@@ -35,16 +35,32 @@ "private": false,

"devDependencies": {
"@babel/core": "^7.15.0",
"@babel/plugin-syntax-import-meta": "^7.10.4",
"@babel/preset-env": "^7.15.0",
"@babel/preset-react": "^7.14.5",
"@babel/preset-typescript": "^7.15.0",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^12.0.0",
"@types/jest": "^27.0.1",
"@types/node": "^15.0.1",
"@types/react": "^17.0.16",
"@types/react": "^17.0.19",
"@types/react-dom": "^17.0.9",
"esbuild": "^0.12.21",
"eslint": "^7.22.0",
"@typescript-eslint/eslint-plugin": "^4.19.0",
"@typescript-eslint/parser": "^4.19.0",
"babel-jest": "^27.1.0",
"eslint": "^7.32.0",
"fake-indexeddb": "^3.1.3",
"jest": "^27.1.0",
"lerna": "^3.15.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"ts-node": "^9.1.1",
"ts-jest": "^27.0.5",
"tslib": "^2.3.0",
"typedoc": "^0.20.35",
"typescript": "^4.3.5"
"typedoc": "^0.21.9",
"typescript": "^4.4.2"
},
"gitHead": "c13ecc2363bcd33a72f3c49cf72496794f2cc726"
"dependencies": {
"@tldraw/core": "^0.0.53",
"rko": "^0.5.19"
},
"gitHead": "d2a334f923a58ffa869060b5ce80281f18af919d"
}

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

* ## getStrokePoints
* @description Get points for a stroke.
* @description Get points for a stroke. Returns an array of objects with an adjusted point, pressure, vector, distance, and runningLength.
* @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional.

@@ -15,41 +15,68 @@ * @param streamline How much to streamline the stroke.

*/
export function getStrokePoints<
T extends number[],
K extends { x: number; y: number; pressure?: number }
>(points: (T | K)[], options = {} as StrokeOptions): StrokePoint[] {
let { streamline = 0.5 } = options
export function getStrokePoints(
points: number[][] | { x: number; y: number; pressure?: number }[],
options = {} as StrokeOptions
): StrokePoint[] {
let { streamline = 0.5, last: isComplete = false } = options
const { simulatePressure = true } = options
streamline = streamline / (simulatePressure ? 4 : 2)
// If we don't have any points, return an empty array.
if (points.length === 0) return []
// Knock down the streamline (more if we're simulating pressure).
streamline = streamline / (simulatePressure ? 3 : 2)
// Whatever the input is, make sure that the points are in number[][].
const pts = toPointsArray(points)
if (pts.length === 0) return []
// If there's only one point, add another point at a 1pt offset.
if (pts.length === 1) pts.push([...vec.add(pts[0], [1, 1]), pts[0][2]])
const strokePoints: StrokePoint[] = [
{
point: [pts[0][0], pts[0][1]],
pressure: pts[0][2],
vector: [0, 0],
distance: 0,
runningLength: 0,
},
]
// The strokePoints array will hold the points for the stroke.
// Start it out with the first point, which needs no adjustment.
const strokePoints: StrokePoint[] = []
for (
let i = 1, j = 0, curr = pts[i], prev = strokePoints[j];
i < pts.length;
i++, curr = pts[i], prev = strokePoints[j]
) {
const point = vec.lrp(prev.point, curr, 1 - streamline)
let prev: StrokePoint = {
point: [pts[0][0], pts[0][1]],
pressure: pts[0][2],
vector: [0, 0],
distance: 0,
runningLength: 0,
}
strokePoints.push(prev)
// Iterate through all of the points.
const len = pts.length
let curr: number[]
for (let i = 0; i < len; i++) {
curr = pts[i]
// If we're at the last point, then add the actual input point.
// Otherwise, using the streamline option, interpolate a new point
// between the previous point the current point.
// More streamline = closer to the previous point;
// less streamline = closer to the current point.
// This takes a lot of the "noise" out of the input points.
const point =
isComplete && i === len - 1
? curr
: vec.lrp(prev.point, curr, 1 - streamline)
// If the new point is the same as the previous point, skip ahead.
if (vec.isEqual(prev.point, point)) continue
// What's the vector from the current point to the previous point?
const vector = vec.uni(vec.sub(prev.point, point))
// How far is the new point from the previous point?
const distance = vec.dist(point, prev.point)
// Add this distance to the total "running length" of the line.
const runningLength = prev.runningLength + distance
const strokePoint = {
// Create a new strokepoint (it will be the new "previous" one)
prev = {
point,

@@ -62,33 +89,5 @@ pressure: curr[2],

strokePoints.push(strokePoint)
j += 1 // only increment j if we add an item to strokePoints
strokePoints.push(prev)
}
/*
Align vectors at the end of the line
Starting from the last point, work back until we've traveled more than
half of the line's size (width). Take the current point's vector and then
work forward, setting all remaining points' vectors to this vector. This
removes the "noise" at the end of the line and allows for a better-facing
end cap.
*/
// Update the length to the length of the strokePoints array.
// const len = strokePoints.length
// 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
// }
// }
return strokePoints

@@ -155,10 +154,21 @@ }

// Previous pressure (start with average of first five pressures)
let prevPressure = points
.slice(0, 5)
.reduce((acc, cur) => (acc + cur.pressure) / 2, points[0].pressure)
// Previous pressure (start with average of first five pressures,
// in order to prevent fat starts for every line. Drawn lines
// almost always start slow!
let prevPressure = points.slice(0, 10).reduce((acc, curr) => {
let pressure = curr.pressure
if (simulatePressure) {
const sp = min(1, curr.distance / size)
const rp = min(1, 1 - sp)
pressure = min(1, acc + (rp - acc) * (sp / 4))
}
return (acc + pressure) / 2
}, points[0].pressure)
// The current radius
let radius = getStrokeRadius(size, thinning, easing, points[len - 1].pressure)
// The radius of the first saved point
let firstRadius: number | undefined = undefined

@@ -182,4 +192,4 @@

Iterating through the points and populate the rightPts and leftPts arrays,
skipping the first and last pointsm, which will get caps later on.
Iterating through the points and populate the rightPts and leftPts arrays,
skipping the first and last pointsm, which will get caps later on.
*/

@@ -206,6 +216,9 @@

if (thinning) {
// If we're simulating pressure, then do so based on the distance
// between the current point and the previous point, and the size
// of the stroke.
if (simulatePressure) {
const rp = min(1, 1 - distance / size)
const sp = min(1, distance / size)
pressure = min(1, prevPressure + (rp - prevPressure) * (sp / 2))
const rp = min(1, 1 - sp)
pressure = min(1, prevPressure + (rp - prevPressure) * (sp / 4))
}

@@ -388,3 +401,5 @@

for (let t = 0, step = 0.1; t <= 1; t += step) {
startCap.push(vec.rotAround(start, firstPoint.point, PI * t))
const pt = vec.rotAround(start, firstPoint.point, PI * t)
if (vec.dist(pt, tl) < 1) break
startCap.push(pt)
}

@@ -422,8 +437,14 @@ leftPts.shift()

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)
// The last left point
const ll = leftPts[leftPts.length - 1]
// The last right point
const lr = rightPts[rightPts.length - 1]
// The point between the two
const mid = vec.med(ll, lr)
// The last provided point
const last = lastPoint.point
const vector = vec.uni(vec.sub(last, mid))

@@ -433,7 +454,11 @@

if (!taperEnd && !(taperStart && isVeryShort)) {
// Draw the end cap
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))
for (let t = 0, step = 0.1; t <= 1; t += step) {
const pt = vec.rotAround(start, last, PI * 3 * t)
if (vec.dist(pt, lr) < 1) break
endCap.push(pt)
}
} else {
// Just push the last point to the line
endCap.push(last)

@@ -463,4 +488,4 @@ }

* @description Returns a stroke as an array of outline points.
* @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional.
* @param options An (optional) object with options.
* @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional in both cases.
* @param options (optional) An object with options.
* @param options.size The base size (diameter) of the stroke.

@@ -475,9 +500,15 @@ * @param options.thinning The effect of pressure on the stroke's size.

*/
export default function getStroke<
T extends number[],
K extends { x: number; y: number; pressure?: number }
>(points: (T | K)[], options: StrokeOptions = {} as StrokeOptions): number[][] {
function getStroke(points: number[][], options?: StrokeOptions): number[][]
function getStroke(
points: { x: number; y: number; pressure?: number }[],
options?: StrokeOptions
): number[][]
function getStroke(
points: number[][] | { x: number; y: number; pressure?: number }[],
options: StrokeOptions = {} as StrokeOptions
): number[][] {
return getStrokeOutlinePoints(getStrokePoints(points, options), options)
}
export default getStroke
export { StrokeOptions }

@@ -24,5 +24,7 @@ export interface StrokeOptions {

pressure: number
distance: number
vector: number[]
distance: number
runningLength: number
}
export type InputPoint = { x: number; y: number; pressure?: number } | number[]

@@ -12,5 +12,5 @@ import { isEqual } from './vec'

/**
* Convert an array of points to the correct format ([x, y, radius])
* @param points
* @returns
* Convert an array of points to the correct format ([x, y, pressure])
* @param points The points to format, an array of arrays or objects
* @returns number[][]
*/

@@ -28,7 +28,9 @@ export function toPointsArray<

} else {
return (points as {
x: number
y: number
pressure?: number
}[]).map(({ x, y, pressure = 0.5 }) => [x, y, pressure])
return (
points as {
x: number
y: number
pressure?: number
}[]
).map(({ x, y, pressure = 0.5 }) => [x, y, pressure])
}

@@ -59,15 +61,1 @@ }

}
export function withoutDuplicates(pts: number[][]) {
const unique: number[][] = []
let prev: number[] | undefined = undefined
for (let pt of pts) {
if (prev && isEqual(prev, pt)) continue
unique.push(pt)
prev = pt
}
return pts
}

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