perfect-freehand
Advanced tools
Comparing version 1.0.6 to 1.0.7
@@ -1,1 +0,1 @@ | ||
var ft=Object.defineProperty;var dt=t=>ft(t,"__esModule",{value:!0});var xt=(t,e)=>{dt(t);for(var o in e)ft(t,o,{get:e[o],enumerable:!0})};xt(exports,{default:()=>vt,getStroke:()=>st,getStrokeOutlinePoints:()=>rt,getStrokePoints:()=>ot});function Z(t,e,o,M=g=>g){return t*M(.5-e*(.5-o))}function E(t,e){return[t[0]+e[0],t[1]+e[1]]}function x(t,e){return[t[0]-e[0],t[1]-e[1]]}function z(t,e){return[t[0]*e,t[1]*e]}function St(t,e){return[t[0]/e,t[1]/e]}function G(t){return[t[1],-t[0]]}function bt(t,e){return t[0]*e[0]+t[1]*e[1]}function T(t,e){return t[0]===e[0]&&t[1]===e[1]}function kt(t){return Math.hypot(t[0],t[1])}function Pt(t){return t[0]*t[0]+t[1]*t[1]}function nt(t,e){return Pt(x(t,e))}function _(t){return St(t,kt(t))}function I(t,e){return Math.hypot(t[1]-e[1],t[0]-e[0])}function ht(t,e){return z(E(t,e),.5)}function K(t,e,o){let M=Math.sin(o),g=Math.cos(o),R=t[0]-e[0],s=t[1]-e[1],y=R*g-s*M,j=R*M+s*g;return[y+e[0],j+e[1]]}function H(t,e,o){return E(t,z(x(e,t),o))}function h(t,e,o){return E(t,z(e,o))}var{min:V,PI:yt}=Math,gt=.275,N=yt+1e-4;function rt(t,e={}){var mt;let{size:o=16,smoothing:M=.5,thinning:g=.5,simulatePressure:R=!0,easing:s=r=>r,start:y={},end:j={},last:w=!1}=e,{cap:L=!0,taper:v=0,easing:U=r=>r*(2-r)}=y,{cap:d=!0,taper:l=0,easing:q=r=>--r*r*r+1}=j;if(t.length===0)return[];let ut=t[t.length-1].runningLength,it=Math.pow(o*M,2),f=[],S=[],$=t.slice(0,10).reduce((r,P)=>{let a=P.pressure;if(R){let u=V(1,P.distance/o),p=V(1,1-u);a=V(1,r+(p-r)*(u*gt))}return(r+a)/2},t[0].pressure),k=Z(o,g,t[t.length-1].pressure,s),pt,ct=t[0].vector,C=t[0].point,X=C,c=C,m=X,J=!0;for(let r=0;r<t.length-1;r++){let{pressure:P}=t[r],{point:a,vector:u,distance:p,runningLength:n}=t[r];if(r>0&&J&&n<o/2)continue;if(J&&(J=!1),g){if(R){let F=V(1,p/o),et=V(1,1-F);P=V(1,$+(et-$)*(F*gt))}k=Z(o,g,P,s)}else k=o/2;pt===void 0&&(pt=k);let i=n<v?U(n/v):1,O=ut-n<l?q((ut-n)/l):1;k=Math.max(.01,k*Math.min(i,O));let D=((mt=t[r+1])==null?void 0:mt.vector)||u,tt=bt(u,D);if(tt<0){let F=z(G(ct),k);for(let et=1/13,Y=0;Y<=1;Y+=et)m=K(E(a,F),a,N*-Y),c=K(x(a,F),a,N*Y),S.push(m),f.push(c);C=c,X=m;continue}let at=z(G(H(D,u,tt)),k);c=x(a,at),m=E(a,at);let lt=r<2||tt<.25;(lt||nt(C,c)>it)&&(f.push(c),C=c),(lt||nt(X,m)>it)&&(S.push(m),X=m),$=P,ct=u}let b=t[0],B=t[t.length-1],A=J||S.length<2||f.length<2;if(A&&(!(v||l)||w)){let r=0,P=T(b.point,B.point)?E(b.point,[1,1]):B.point;for(let p=0;p<t.length;p++){let{pressure:n,runningLength:i}=t[p];if(i>o){r=Z(o,g,n,s);break}}let a=h(b.point,G(_(x(b.point,P))),-(r||k)),u=[];for(let p=1/13,n=p;n<=1;n+=p)u.push(K(a,b.point,N*2*n));return u}let Q=[],W=[];if(f.length>1&&S.length>1){m=S[1];for(let n=1;n<f.length;n++)if(!T(m,f[n])){c=f[n];break}if(L||v)if(!v&&!(l&&A)){if(!T(m,c)){let n=h(b.point,_(x(c,m)),-I(m,c)/2);for(let i=1/13,O=i;O<=1;O+=i){let D=K(n,b.point,N*O);if(I(D,c)<1)break;Q.push(D)}f.shift(),S.shift()}}else Q.push(b.point.slice(0,2));else if(!T(m,c)){let n=_(x(c,m)),i=I(m,c)/2;Q.push(h(b.point,n,-i),h(b.point,n,-i*.95),h(b.point,n,i*.95),h(b.point,n,i)),f.shift(),S.shift()}let r=f[f.length-1],P=S[S.length-1],a=ht(r,P),u=B.point,p=G(_(x(u,a)));if(d||l)if(!l&&!(v&&A)){let n=h(u,p,k);for(let i=1/29,O=0;O<=1;O+=i){let D=K(n,u,N*3*O);W.push(D)}}else W.push(u.slice(0,2));else{let n=H(a,u,.95),i=k*.95;W.push(h(n,p,i),h(u,p,i),h(u,p,-i),h(n,p,-i))}}return f.concat(W,S.reverse(),Q)}function ot(t,e={}){var U;let{streamline:o=.5,size:M=16,last:g=!1}=e;if(t.length===0)return[];let R=.15+(1-o)*.85,s=Array.isArray(t[0])?t:t.map(({x:d,y:l,pressure:q=.5})=>[d,l,q]);s.length===1&&s.push([...E(s[0],[1,1]),s[0][2]||.5]);let y=[{point:[s[0][0],s[0][1]],pressure:s[0][2]||.25,vector:[1,1],distance:0,runningLength:0}],j=!1,w=0,L=y[0],v=s.length-1;for(let d=1;d<s.length;d++){let l=g&&d===v?s[d]:H(L.point,s[d],R);if(T(L.point,l))continue;let q=I(l,L.point);if(w+=q,d<v&&!j){if(w<M)continue;j=!0}L={point:l,pressure:s[d][2]||.5,vector:_(x(L.point,l)),distance:q,runningLength:w},y.push(L)}return y[0].vector=((U=y[1])==null?void 0:U.vector)||[0,0],y}function st(t,e={}){return rt(ot(t,e),e)}var vt=st; | ||
var pe=Object.defineProperty;var he=e=>pe(e,"__esModule",{value:!0});var xe=(e,t)=>{he(e);for(var o in t)pe(e,o,{get:t[o],enumerable:!0})};xe(exports,{default:()=>ve,getStroke:()=>ne,getStrokeOutlinePoints:()=>ee,getStrokePoints:()=>te});function $(e,t,o,x=b=>b){return e*x(.5-t*(.5-o))}function i(e,t){return[e[0]+t[0],e[1]+t[1]]}function p(e,t){return[e[0]-t[0],e[1]-t[1]]}function l(e,t){return[e[0]*t,e[1]*t]}function Se(e,t){return[e[0]/t,e[1]/t]}function I(e){return[e[1],-e[0]]}function me(e,t){return e[0]*t[0]+e[1]*t[1]}function ce(e,t){return e[0]===t[0]&&e[1]===t[1]}function ke(e){return Math.hypot(e[0],e[1])}function Pe(e){return e[0]*e[0]+e[1]*e[1]}function B(e,t){return Pe(p(e,t))}function K(e){return Se(e,ke(e))}function ae(e,t){return Math.hypot(e[1]-t[1],e[0]-t[0])}function le(e,t){return l(i(e,t),.5)}function L(e,t,o){let x=Math.sin(o),b=Math.cos(o),y=e[0]-t[0],s=e[1]-t[1],d=y*b-s*x,M=y*x+s*b;return[d+t[0],M+t[1]]}function N(e,t,o){return i(e,l(p(t,e),o))}function A(e,t,o){return i(e,l(t,o))}var{min:T,PI:ye}=Math,fe=.275,j=ye+1e-4;function ee(e,t={}){let{size:o=16,smoothing:x=.5,thinning:b=.5,simulatePressure:y=!0,easing:s=n=>n,start:d={},end:M={},last:V=!1}=t,{cap:S=!0,taper:k=0,easing:w=n=>n*(2-n)}=d,{cap:f=!0,taper:c=0,easing:R=n=>--n*n*n+1}=M;if(e.length===0)return[];let U=e[e.length-1].runningLength,re=Math.pow(o*x,2),v=[],P=[],X=e.slice(0,10).reduce((n,u)=>{let r=u.pressure;if(y){let a=T(1,u.distance/o),W=T(1,1-a);r=T(1,n+(W-n)*(a*fe))}return(n+r)/2},e[0].pressure),m=$(o,b,e[e.length-1].pressure,s),J,oe=e[0].vector,_=e[0].point,C=_,O=_,E=C;for(let n=0;n<e.length-1;n++){let{pressure:u}=e[n],{point:r,vector:a,distance:W,runningLength:z}=e[n];if(U-z<3)continue;if(b){if(y){let D=T(1,W/o),Z=T(1,1-D);u=T(1,X+(Z-X)*(D*fe))}m=$(o,b,u,s)}else m=o/2;J===void 0&&(J=m);let de=z<k?w(z/k):1,ge=U-z<c?R((U-z)/c):1;m=Math.max(.01,m*Math.min(de,ge));let se=e[n+1].vector,Y=me(a,se);if(Y<0){let D=l(I(oe),m);for(let Z=1/13,H=0;H<=1;H+=Z)O=L(p(r,D),r,j*H),v.push(O),E=L(i(r,D),r,j*-H),P.push(E);_=O,C=E;continue}let ue=l(I(N(se,a,Y)),m);O=p(r,ue),E=i(r,ue);let ie=n<2||Y<.25;(ie||B(_,O)>re)&&(v.push(O),_=O),(ie||B(C,E)>re)&&(P.push(E),C=E),X=u,oe=a}let g=e[0].point.slice(0,2),h=e.length>1?e[e.length-1].point.slice(0,2):i(e[0].point,[1,1]),Q=v.length<=1||P.length<=1;if(Q&&(!(k||c)||V)){let n=A(g,K(I(p(g,h))),-(J||m)),u=[];for(let r=1/13,a=r;a<=1;a+=r)u.push(L(n,g,j*2*a));return u}let F=[];if(k||c&&Q)F.push(i(g,[.1,0]));else if(S)for(let n=1/13,u=n;u<=1;u+=n){let r=L(P[0],g,j*u);F.push(r)}else{let n=p(v[0],P[0]),u=l(n,.5),r=l(n,.51);F.push(p(g,u),p(g,r),i(g,r),i(g,u))}let G=[],be=le(v[v.length-1],P[P.length-1]),q=I(K(p(h,be)));if(c||k&&Q)G.push(h);else if(f){let n=A(h,q,m);for(let u=1/29,r=0;r<=1;r+=u){let a=L(n,h,j*3*r);G.push(a)}}else G.push(i(h,l(q,m)),i(h,l(q,m*.99)),p(h,l(q,m*.99)),p(h,l(q,m)));return v.concat(G,P.reverse(),F)}function te(e,t={}){var w;let{streamline:o=.5,size:x=16,last:b=!1}=t;if(e.length===0)return[];let y=.15+(1-o)*.85,s=Array.isArray(e[0])?e:e.map(({x:f,y:c,pressure:R=.5})=>[f,c,R]);s.length===1&&s.push([...i(s[0],[1,1]),s[0][2]||.5]);let d=[{point:[s[0][0],s[0][1]],pressure:s[0][2]||.25,vector:[1,1],distance:0,runningLength:0}],M=!1,V=0,S=d[0],k=s.length-1;for(let f=1;f<s.length;f++){let c=b&&f===k?s[f]:N(S.point,s[f],y);if(ce(S.point,c))continue;let R=ae(c,S.point);if(V+=R,f<k&&!M){if(V<x)continue;M=!0}S={point:c,pressure:s[f][2]||.5,vector:K(p(S.point,c)),distance:R,runningLength:V},d.push(S)}return d[0].vector=((w=d[1])==null?void 0:w.vector)||[0,0],d}function ne(e,t={}){return ee(te(e,t),t)}var ve=ne; |
@@ -1,1 +0,259 @@ | ||
function Z(t,e,o,M=g=>g){return t*M(.5-e*(.5-o))}function E(t,e){return[t[0]+e[0],t[1]+e[1]]}function x(t,e){return[t[0]-e[0],t[1]-e[1]]}function z(t,e){return[t[0]*e,t[1]*e]}function gt(t,e){return[t[0]/e,t[1]/e]}function G(t){return[t[1],-t[0]]}function mt(t,e){return t[0]*e[0]+t[1]*e[1]}function T(t,e){return t[0]===e[0]&&t[1]===e[1]}function dt(t){return Math.hypot(t[0],t[1])}function xt(t){return t[0]*t[0]+t[1]*t[1]}function nt(t,e){return xt(x(t,e))}function _(t){return gt(t,dt(t))}function I(t,e){return Math.hypot(t[1]-e[1],t[0]-e[0])}function at(t,e){return z(E(t,e),.5)}function K(t,e,o){let M=Math.sin(o),g=Math.cos(o),R=t[0]-e[0],s=t[1]-e[1],y=R*g-s*M,j=R*M+s*g;return[y+e[0],j+e[1]]}function H(t,e,o){return E(t,z(x(e,t),o))}function h(t,e,o){return E(t,z(e,o))}var{min:V,PI:St}=Math,lt=.275,N=St+1e-4;function ft(t,e={}){var it;let{size:o=16,smoothing:M=.5,thinning:g=.5,simulatePressure:R=!0,easing:s=r=>r,start:y={},end:j={},last:w=!1}=e,{cap:L=!0,taper:v=0,easing:U=r=>r*(2-r)}=y,{cap:d=!0,taper:l=0,easing:q=r=>--r*r*r+1}=j;if(t.length===0)return[];let rt=t[t.length-1].runningLength,ot=Math.pow(o*M,2),f=[],S=[],$=t.slice(0,10).reduce((r,P)=>{let a=P.pressure;if(R){let u=V(1,P.distance/o),p=V(1,1-u);a=V(1,r+(p-r)*(u*lt))}return(r+a)/2},t[0].pressure),k=Z(o,g,t[t.length-1].pressure,s),st,ut=t[0].vector,C=t[0].point,X=C,c=C,m=X,J=!0;for(let r=0;r<t.length-1;r++){let{pressure:P}=t[r],{point:a,vector:u,distance:p,runningLength:n}=t[r];if(r>0&&J&&n<o/2)continue;if(J&&(J=!1),g){if(R){let F=V(1,p/o),et=V(1,1-F);P=V(1,$+(et-$)*(F*lt))}k=Z(o,g,P,s)}else k=o/2;st===void 0&&(st=k);let i=n<v?U(n/v):1,O=rt-n<l?q((rt-n)/l):1;k=Math.max(.01,k*Math.min(i,O));let D=((it=t[r+1])==null?void 0:it.vector)||u,tt=mt(u,D);if(tt<0){let F=z(G(ut),k);for(let et=1/13,Y=0;Y<=1;Y+=et)m=K(E(a,F),a,N*-Y),c=K(x(a,F),a,N*Y),S.push(m),f.push(c);C=c,X=m;continue}let pt=z(G(H(D,u,tt)),k);c=x(a,pt),m=E(a,pt);let ct=r<2||tt<.25;(ct||nt(C,c)>ot)&&(f.push(c),C=c),(ct||nt(X,m)>ot)&&(S.push(m),X=m),$=P,ut=u}let b=t[0],B=t[t.length-1],A=J||S.length<2||f.length<2;if(A&&(!(v||l)||w)){let r=0,P=T(b.point,B.point)?E(b.point,[1,1]):B.point;for(let p=0;p<t.length;p++){let{pressure:n,runningLength:i}=t[p];if(i>o){r=Z(o,g,n,s);break}}let a=h(b.point,G(_(x(b.point,P))),-(r||k)),u=[];for(let p=1/13,n=p;n<=1;n+=p)u.push(K(a,b.point,N*2*n));return u}let Q=[],W=[];if(f.length>1&&S.length>1){m=S[1];for(let n=1;n<f.length;n++)if(!T(m,f[n])){c=f[n];break}if(L||v)if(!v&&!(l&&A)){if(!T(m,c)){let n=h(b.point,_(x(c,m)),-I(m,c)/2);for(let i=1/13,O=i;O<=1;O+=i){let D=K(n,b.point,N*O);if(I(D,c)<1)break;Q.push(D)}f.shift(),S.shift()}}else Q.push(b.point.slice(0,2));else if(!T(m,c)){let n=_(x(c,m)),i=I(m,c)/2;Q.push(h(b.point,n,-i),h(b.point,n,-i*.95),h(b.point,n,i*.95),h(b.point,n,i)),f.shift(),S.shift()}let r=f[f.length-1],P=S[S.length-1],a=at(r,P),u=B.point,p=G(_(x(u,a)));if(d||l)if(!l&&!(v&&A)){let n=h(u,p,k);for(let i=1/29,O=0;O<=1;O+=i){let D=K(n,u,N*3*O);W.push(D)}}else W.push(u.slice(0,2));else{let n=H(a,u,.95),i=k*.95;W.push(h(n,p,i),h(u,p,i),h(u,p,-i),h(n,p,-i))}}return f.concat(W,S.reverse(),Q)}function bt(t,e={}){var U;let{streamline:o=.5,size:M=16,last:g=!1}=e;if(t.length===0)return[];let R=.15+(1-o)*.85,s=Array.isArray(t[0])?t:t.map(({x:d,y:l,pressure:q=.5})=>[d,l,q]);s.length===1&&s.push([...E(s[0],[1,1]),s[0][2]||.5]);let y=[{point:[s[0][0],s[0][1]],pressure:s[0][2]||.25,vector:[1,1],distance:0,runningLength:0}],j=!1,w=0,L=y[0],v=s.length-1;for(let d=1;d<s.length;d++){let l=g&&d===v?s[d]:H(L.point,s[d],R);if(T(L.point,l))continue;let q=I(l,L.point);if(w+=q,d<v&&!j){if(w<M)continue;j=!0}L={point:l,pressure:s[d][2]||.5,vector:_(x(L.point,l)),distance:q,runningLength:w},y.push(L)}return y[0].vector=((U=y[1])==null?void 0:U.vector)||[0,0],y}function ht(t,e={}){return ft(bt(t,e),e)}var jt=ht;export{jt as default,ht as getStroke,ft as getStrokeOutlinePoints,bt as getStrokePoints}; | ||
// src/getStrokeRadius.ts | ||
function getStrokeRadius(size, thinning, pressure, easing = (t) => t) { | ||
return size * easing(0.5 - thinning * (0.5 - pressure)); | ||
} | ||
// 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 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 isEqual(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(sub(B, A), t)); | ||
} | ||
function prj(A, B, c) { | ||
return add(A, mul(B, c)); | ||
} | ||
// src/getStrokeOutlinePoints.ts | ||
var { min, PI } = Math; | ||
var RATE_OF_PRESSURE_CHANGE = 0.275; | ||
var FIXED_PI = PI + 1e-4; | ||
function getStrokeOutlinePoints(points, options = {}) { | ||
const { | ||
size = 16, | ||
smoothing = 0.5, | ||
thinning = 0.5, | ||
simulatePressure = true, | ||
easing = (t) => t, | ||
start = {}, | ||
end = {}, | ||
last: isComplete = false | ||
} = options; | ||
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; | ||
if (points.length === 0) | ||
return []; | ||
const totalLength = points[points.length - 1].runningLength; | ||
const minDistance = Math.pow(size * smoothing, 2); | ||
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 * RATE_OF_PRESSURE_CHANGE)); | ||
} | ||
return (acc + pressure) / 2; | ||
}, points[0].pressure); | ||
let radius = getStrokeRadius(size, thinning, points[points.length - 1].pressure, easing); | ||
let firstRadius = void 0; | ||
let prevVector = points[0].vector; | ||
let pl = points[0].point; | ||
let pr = pl; | ||
let tl = pl; | ||
let tr = pr; | ||
for (let i = 0; i < points.length - 1; i++) { | ||
let { pressure } = points[i]; | ||
const { point, vector, distance, runningLength } = points[i]; | ||
if (totalLength - runningLength < 3) | ||
continue; | ||
if (thinning) { | ||
if (simulatePressure) { | ||
const sp = min(1, distance / size); | ||
const rp = min(1, 1 - sp); | ||
pressure = min(1, prevPressure + (rp - prevPressure) * (sp * RATE_OF_PRESSURE_CHANGE)); | ||
} | ||
radius = getStrokeRadius(size, thinning, pressure, easing); | ||
} 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 nextDpr = dpr(vector, nextVector); | ||
if (nextDpr < 0) { | ||
const offset2 = mul(per(prevVector), radius); | ||
for (let step = 1 / 13, t = 0; t <= 1; t += step) { | ||
tl = rotAround(sub(point, offset2), point, FIXED_PI * t); | ||
leftPts.push(tl); | ||
tr = rotAround(add(point, offset2), point, FIXED_PI * -t); | ||
rightPts.push(tr); | ||
} | ||
pl = tl; | ||
pr = tr; | ||
continue; | ||
} | ||
const offset = mul(per(lrp(nextVector, vector, nextDpr)), radius); | ||
tl = sub(point, offset); | ||
tr = add(point, offset); | ||
const alwaysAdd = i < 2 || nextDpr < 0.25; | ||
if (alwaysAdd || dist2(pl, tl) > minDistance) { | ||
leftPts.push(tl); | ||
pl = tl; | ||
} | ||
if (alwaysAdd || dist2(pr, tr) > minDistance) { | ||
rightPts.push(tr); | ||
pr = tr; | ||
} | ||
prevPressure = pressure; | ||
prevVector = vector; | ||
} | ||
const firstPoint = points[0].point.slice(0, 2); | ||
const lastPoint = points.length > 1 ? points[points.length - 1].point.slice(0, 2) : add(points[0].point, [1, 1]); | ||
const isVeryShort = leftPts.length <= 1 || rightPts.length <= 1; | ||
if (isVeryShort && (!(taperStart || taperEnd) || isComplete)) { | ||
const start2 = prj(firstPoint, uni(per(sub(firstPoint, lastPoint))), -(firstRadius || radius)); | ||
const dotPts = []; | ||
for (let step = 1 / 13, t = step; t <= 1; t += step) { | ||
dotPts.push(rotAround(start2, firstPoint, FIXED_PI * 2 * t)); | ||
} | ||
return dotPts; | ||
} | ||
const startCap = []; | ||
if (taperStart || taperEnd && isVeryShort) { | ||
startCap.push(add(firstPoint, [0.1, 0])); | ||
} else if (capStart) { | ||
for (let step = 1 / 13, t = step; t <= 1; t += step) { | ||
const pt = rotAround(rightPts[0], firstPoint, FIXED_PI * t); | ||
startCap.push(pt); | ||
} | ||
} else { | ||
const cornersVector = sub(leftPts[0], rightPts[0]); | ||
const offsetA = mul(cornersVector, 0.5); | ||
const offsetB = mul(cornersVector, 0.51); | ||
startCap.push(sub(firstPoint, offsetA), sub(firstPoint, offsetB), add(firstPoint, offsetB), add(firstPoint, offsetA)); | ||
} | ||
const endCap = []; | ||
const mid = med(leftPts[leftPts.length - 1], rightPts[rightPts.length - 1]); | ||
const direction = per(uni(sub(lastPoint, mid))); | ||
if (taperEnd || taperStart && isVeryShort) { | ||
endCap.push(lastPoint); | ||
} else if (capEnd) { | ||
const start2 = prj(lastPoint, direction, radius); | ||
for (let step = 1 / 29, t = 0; t <= 1; t += step) { | ||
const pt = rotAround(start2, lastPoint, FIXED_PI * 3 * t); | ||
endCap.push(pt); | ||
} | ||
} else { | ||
endCap.push(add(lastPoint, mul(direction, radius)), add(lastPoint, mul(direction, radius * 0.99)), sub(lastPoint, mul(direction, radius * 0.99)), sub(lastPoint, mul(direction, radius))); | ||
} | ||
return leftPts.concat(endCap, rightPts.reverse(), startCap); | ||
} | ||
// src/getStrokePoints.ts | ||
function getStrokePoints(points, options = {}) { | ||
const { streamline = 0.5, size = 16, last: isComplete = false } = options; | ||
if (points.length === 0) | ||
return []; | ||
const t = 0.15 + (1 - streamline) * 0.85; | ||
const pts = Array.isArray(points[0]) ? points : points.map(({ x, y, pressure = 0.5 }) => [x, y, pressure]); | ||
if (pts.length === 1) | ||
pts.push([...add(pts[0], [1, 1]), pts[0][2] || 0.5]); | ||
const strokePoints = [ | ||
{ | ||
point: [pts[0][0], pts[0][1]], | ||
pressure: pts[0][2] || 0.25, | ||
vector: [1, 1], | ||
distance: 0, | ||
runningLength: 0 | ||
} | ||
]; | ||
let hasReachedMinimumLength = false; | ||
let runningLength = 0; | ||
let prev = strokePoints[0]; | ||
const max = pts.length - 1; | ||
for (let i = 1; i < pts.length; i++) { | ||
const point = isComplete && i === max ? pts[i] : lrp(prev.point, pts[i], t); | ||
if (isEqual(prev.point, point)) | ||
continue; | ||
const distance = dist(point, prev.point); | ||
runningLength += distance; | ||
if (i < max && !hasReachedMinimumLength) { | ||
if (runningLength < size) | ||
continue; | ||
hasReachedMinimumLength = true; | ||
} | ||
prev = { | ||
point, | ||
pressure: pts[i][2] || 0.5, | ||
vector: uni(sub(prev.point, point)), | ||
distance, | ||
runningLength | ||
}; | ||
strokePoints.push(prev); | ||
} | ||
strokePoints[0].vector = strokePoints[1]?.vector || [0, 0]; | ||
return strokePoints; | ||
} | ||
// src/getStroke.ts | ||
function getStroke(points, options = {}) { | ||
return getStrokeOutlinePoints(getStrokePoints(points, options), options); | ||
} | ||
// src/index.ts | ||
var src_default = getStroke; | ||
export { | ||
src_default as default, | ||
getStroke, | ||
getStrokeOutlinePoints, | ||
getStrokePoints | ||
}; |
{ | ||
"version": "1.0.6", | ||
"version": "1.0.7", | ||
"name": "perfect-freehand", | ||
@@ -60,3 +60,3 @@ "private": false, | ||
}, | ||
"gitHead": "6df6712f75139129bd9916220814d707354fbabc" | ||
"gitHead": "0b1fd1bfc560cb15e34724b0b9b56d6a2b750f00" | ||
} |
@@ -7,2 +7,3 @@ import { getStrokeRadius } from './getStrokeRadius' | ||
dist2, | ||
div, | ||
dpr, | ||
@@ -122,3 +123,3 @@ isEqual, | ||
let short = true | ||
// let short = true | ||
@@ -136,7 +137,4 @@ /* | ||
if (i > 0 && short && runningLength < size / 2) { | ||
continue | ||
} else if (short) { | ||
short = false | ||
} | ||
// Removes noise from the end of the line | ||
if (totalLength - runningLength < 3) continue | ||
@@ -193,4 +191,6 @@ /* | ||
const nextVector = points[i + 1]?.vector || vector | ||
/* Add points to left and right */ | ||
const nextVector = points[i + 1].vector | ||
const nextDpr = dpr(vector, nextVector) | ||
@@ -207,11 +207,14 @@ | ||
if (nextDpr < 0) { | ||
// It's a sharp corner. Draw a rounded cap. | ||
// It's a sharp corner. Draw a rounded cap and move on to the next point | ||
// Considering saving these and drawing them later? So that we can avoid | ||
// crossing future points. | ||
const offset = mul(per(prevVector), radius) | ||
for (let step = 1 / 13, t = 0; t <= 1; t += step) { | ||
tr = rotAround(add(point, offset), point, FIXED_PI * -t) | ||
tl = rotAround(sub(point, offset), point, FIXED_PI * t) | ||
leftPts.push(tl) | ||
tr = rotAround(add(point, offset), point, FIXED_PI * -t) | ||
rightPts.push(tr) | ||
leftPts.push(tl) | ||
} | ||
@@ -222,3 +225,2 @@ | ||
// Once we've drawn the cap, don't do any more work for this point. | ||
continue | ||
@@ -255,3 +257,2 @@ } | ||
// Set variables for next iteration | ||
prevPressure = pressure | ||
@@ -269,6 +270,11 @@ prevVector = vector | ||
const firstPoint = points[0] | ||
const lastPoint = points[points.length - 1] | ||
const isVeryShort = short || rightPts.length < 2 || leftPts.length < 2 | ||
const firstPoint = points[0].point.slice(0, 2) | ||
const lastPoint = | ||
points.length > 1 | ||
? points[points.length - 1].point.slice(0, 2) | ||
: add(points[0].point, [1, 1]) | ||
const isVeryShort = leftPts.length <= 1 || rightPts.length <= 1 | ||
/* | ||
@@ -284,20 +290,6 @@ Draw a dot for very short or completed strokes | ||
if (isVeryShort && (!(taperStart || taperEnd) || isComplete)) { | ||
let ir = 0 | ||
const lastPt = isEqual(firstPoint.point, lastPoint.point) | ||
? add(firstPoint.point, [1, 1]) | ||
: lastPoint.point | ||
for (let i = 0; i < points.length; i++) { | ||
const { pressure, runningLength } = points[i] | ||
if (runningLength > size) { | ||
ir = getStrokeRadius(size, thinning, pressure, easing) | ||
break | ||
} | ||
} | ||
const start = prj( | ||
firstPoint.point, | ||
per(uni(sub(firstPoint.point, lastPt))), | ||
-(ir || radius) | ||
firstPoint, | ||
uni(per(sub(firstPoint, lastPoint))), | ||
-(firstRadius || radius) | ||
) | ||
@@ -308,3 +300,3 @@ | ||
for (let step = 1 / 13, t = step; t <= 1; t += step) { | ||
dotPts.push(rotAround(start, firstPoint.point, FIXED_PI * 2 * t)) | ||
dotPts.push(rotAround(start, firstPoint, FIXED_PI * 2 * t)) | ||
} | ||
@@ -325,52 +317,27 @@ | ||
const startCap: number[][] = [] | ||
const endCap: number[][] = [] | ||
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 (taperStart || (taperEnd && isVeryShort)) { | ||
// The start point is tapered, noop | ||
startCap.push(add(firstPoint, [0.1, 0])) | ||
} else if (capStart) { | ||
// Draw the round cap - add thirteen points rotating the right point around the start point to the left point | ||
for (let step = 1 / 13, t = step; t <= 1; t += step) { | ||
const pt = rotAround(rightPts[0], firstPoint, FIXED_PI * t) | ||
startCap.push(pt) | ||
} | ||
} else { | ||
// Draw the flat cap - add a point to the left and right of the start point | ||
const cornersVector = sub(leftPts[0], rightPts[0]) | ||
const offsetA = mul(cornersVector, 0.5) | ||
const offsetB = mul(cornersVector, 0.51) | ||
if (capStart || taperStart) { | ||
if (!taperStart && !(taperEnd && isVeryShort)) { | ||
if (!isEqual(tr, tl)) { | ||
const start = prj( | ||
firstPoint.point, | ||
uni(sub(tl, tr)), | ||
-dist(tr, tl) / 2 | ||
) | ||
for (let step = 1 / 13, t = step; t <= 1; t += step) { | ||
const pt = rotAround(start, firstPoint.point, FIXED_PI * t) | ||
if (dist(pt, tl) < 1) break | ||
startCap.push(pt) | ||
} | ||
leftPts.shift() | ||
rightPts.shift() | ||
} | ||
} else { | ||
startCap.push(firstPoint.point.slice(0, 2)) | ||
} | ||
} else { | ||
// Flat cap | ||
if (!isEqual(tr, tl)) { | ||
const vector = uni(sub(tl, tr)) | ||
const ptDist = dist(tr, tl) / 2 | ||
startCap.push( | ||
sub(firstPoint, offsetA), | ||
sub(firstPoint, offsetB), | ||
add(firstPoint, offsetB), | ||
add(firstPoint, offsetA) | ||
) | ||
} | ||
startCap.push( | ||
prj(firstPoint.point, vector, -ptDist), | ||
prj(firstPoint.point, vector, -ptDist * 0.95), | ||
prj(firstPoint.point, vector, ptDist * 0.95), | ||
prj(firstPoint.point, vector, ptDist) | ||
) | ||
leftPts.shift() | ||
rightPts.shift() | ||
} | ||
} | ||
/* | ||
/* | ||
Draw an end cap | ||
@@ -385,39 +352,28 @@ | ||
// The last left point | ||
const ll = leftPts[leftPts.length - 1] | ||
const endCap: number[][] = [] | ||
// The last right point | ||
const lr = rightPts[rightPts.length - 1] | ||
// The mid point between the last left and right points | ||
const mid = med(leftPts[leftPts.length - 1], rightPts[rightPts.length - 1]) | ||
// The point between the two | ||
const mid = med(ll, lr) | ||
// The direction vector from the mid point to the last point | ||
const direction = per(uni(sub(lastPoint, mid))) | ||
// The last provided point | ||
const last = lastPoint.point | ||
const direction = per(uni(sub(last, mid))) | ||
if (capEnd || taperEnd) { | ||
if (!taperEnd && !(taperStart && isVeryShort)) { | ||
// Draw the end cap | ||
const start = prj(last, direction, radius) | ||
for (let step = 1 / 29, t = 0; t <= 1; t += step) { | ||
const pt = rotAround(start, last, FIXED_PI * 3 * t) | ||
endCap.push(pt) | ||
} | ||
} else { | ||
// Just push the last point to the line | ||
endCap.push(last.slice(0, 2)) | ||
} | ||
} else { | ||
// Add a few more points almost at the last point | ||
const justBefore = lrp(mid, last, 0.95) | ||
const r = radius * 0.95 | ||
endCap.push( | ||
prj(justBefore, direction, r), | ||
prj(last, direction, r), | ||
prj(last, direction, -r), | ||
prj(justBefore, direction, -r) | ||
) | ||
if (taperEnd || (taperStart && isVeryShort)) { | ||
// Tapered end - push the last point to the line | ||
endCap.push(lastPoint) | ||
} else if (capEnd) { | ||
// Draw the round end cap | ||
const start = prj(lastPoint, direction, radius) | ||
for (let step = 1 / 29, t = 0; t <= 1; t += step) { | ||
const pt = rotAround(start, lastPoint, FIXED_PI * 3 * t) | ||
endCap.push(pt) | ||
} | ||
} else { | ||
// Draw the flat end cap | ||
endCap.push( | ||
add(lastPoint, mul(direction, radius)), | ||
add(lastPoint, mul(direction, radius * 0.99)), | ||
sub(lastPoint, mul(direction, radius * 0.99)), | ||
sub(lastPoint, mul(direction, radius)) | ||
) | ||
} | ||
@@ -424,0 +380,0 @@ |
@@ -79,4 +79,4 @@ import { add, dist, isEqual, lrp, sub, uni } from './vec' | ||
hasReachedMinimumLength = true | ||
// TODO: Backfill the missing points so that tapering works correctly. | ||
} | ||
// Create a new strokepoint (it will be the new "previous" one). | ||
@@ -83,0 +83,0 @@ prev = { |
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
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
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
320097
3044