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.4 to 1.0.0

dist/types/getStroke.d.ts

2

dist/cjs/index.js

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

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

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

// 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 } = options;
const { simulatePressure = true, last: isComplete = false } = 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;
}
var RATE_OF_CHANGE = 0.3;
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 * RATE_OF_CHANGE));
}
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 * RATE_OF_CHANGE));
}
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,
getStroke,
getStrokeOutlinePoints,
getStrokePoints
};
function Y(t,e,o,E=g=>g){return t*E(.5-e*(.5-o))}function v(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 N(t){return[t[1],-t[0]]}function pt(t,e){return t[0]*e[0]+t[1]*e[1]}function C(t,e){return t[0]===e[0]&&t[1]===e[1]}function ht(t){return Math.hypot(t[0],t[1])}function dt(t){return t[0]*t[0]+t[1]*t[1]}function tt(t,e){return dt(x(t,e))}function T(t){return gt(t,ht(t))}function K(t,e){return Math.hypot(t[1]-e[1],t[0]-e[0])}function mt(t,e){return z(v(t,e),.5)}function _(t,e,o){let E=Math.sin(o),g=Math.cos(o),i=t[0]-e[0],k=t[1]-e[1],l=i*g-k*E,j=i*E+k*g;return[l+e[0],j+e[1]]}function R(t,e,o){return v(t,z(x(e,t),o))}function b(t,e,o){return v(t,z(e,o))}var ct=.3,{min:F,PI:J}=Math;function at(t,e={}){let{size:o=16,smoothing:E=.5,thinning:g=.5,simulatePressure:i=!0,easing:k=n=>n,start:l={},end:j={},last:q=!1}=e,{streamline:S=.5}=e,{cap:M=!0,taper:P=0,easing:Q=n=>n*(2-n)}=l,{cap:Z=!0,taper:D=0,easing:bt=n=>--n*n*n+1}=j;if(S/=2,t.length===0)return[];let et=t[t.length-1].runningLength,f=[],y=[],$=t.slice(0,10).reduce((n,d)=>{let c=d.pressure;if(i){let s=F(1,d.distance/o),a=F(1,1-s);c=F(1,n+(a-n)*(s*ct))}return(n+c)/2},t[0].pressure),O=Y(o,g,t[t.length-1].pressure,k),nt,rt=t[0].vector,V=t[0].point,G=V,p=V,m=G,U=!0;for(let n=0;n<t.length-1;n++){let{pressure:d}=t[n],{point:c,vector:s,distance:a,runningLength:r}=t[n];if(n>0&&U&&r<o/2)continue;if(U&&(U=!1),g){if(i){let I=F(1,a/o),w=F(1,1-I);d=F(1,$+(w-$)*(I*ct))}O=Y(o,g,d,k)}else O=o/2;nt===void 0&&(nt=O);let u=r<P?Q(r/P):1,H=et-r<D?bt((et-r)/D):1;O=Math.max(.01,O*Math.min(u,H));let L=t[n+1].vector,A=pt(s,L);if(A<0){let I=z(N(rt),O);for(let w=0;w<1;w+=.2)m=_(v(c,I),c,J*-w),p=_(x(c,I),c,J*w),y.push(m),f.push(p);V=p,G=m;continue}let st=z(N(R(L,s,A)),O);p=x(c,st),m=v(c,st);let ut=n<2||A<.25,it=Math.pow(Math.max((r>o?o:o/2)*E,1),2);(ut||tt(V,p)>it)&&(f.push(R(V,p,S)),V=p),(ut||tt(G,m)>it)&&(y.push(R(G,m,S)),G=m),$=d,rt=s}let h=t[0],ot=t[t.length-1],B=U||y.length<2||f.length<2;if(B&&(!(P||D)||q)){let n=0;for(let s=0;s<t.length;s++){let{pressure:a,runningLength:r}=t[s];if(r>o){n=Y(o,g,a,k);break}}let d=b(h.point,N(T(x(h.point,ot.point))),-(n||O)),c=[];for(let s=0,a=.1;s<=1;s+=a)c.push(_(d,h.point,J*2*s));return c}let W=[],X=[];if(f.length>1&&y.length>1){m=y[1];for(let r=1;r<f.length;r++)if(!C(m,f[r])){p=f[r];break}if(M||P)if(!P&&!(D&&B)){if(!C(m,p)){let r=b(h.point,T(x(p,m)),-K(m,p)/2);for(let u=0,H=.1;u<=1;u+=H){let L=_(r,h.point,J*u);if(K(L,p)<1)break;W.push(L)}f.shift(),y.shift()}}else W.push(h.point,v(h.point,[.1,.1]));else if(!C(m,p)){let r=T(x(p,m)),u=K(m,p)/2;W.concat(b(h.point,r,u*.95),b(h.point,r,u),b(h.point,r,-u),b(h.point,r,-u*.95)),f.shift(),y.shift()}let n=f[f.length-1],d=y[y.length-1],c=mt(n,d),s=ot.point,a=N(T(x(s,c)));if(Z||D)if(!D&&!(P&&B)){let r=b(s,a,O);for(let u=0,H=.1;u<=1;u+=H){let L=_(r,s,J*3*u);if(K(L,d)<1)break;X.push(L)}}else X.push(s);else{let r=R(c,s,.95),u=O*.95;X.concat(b(r,a,u),b(s,a,u),b(s,a,-u),b(r,a,-u))}}return f.concat(X,y.reverse(),W)}function lt(t,e={}){let{streamline:o=.5}=e,{simulatePressure:E=!0,last:g=!1}=e;if(t.length===0)return[];o=o/(E?3:2);let i=Array.isArray(t[0])?t:t.map(({x:S,y:M,pressure:P=.5})=>[S,M,P]);i.length===1&&i.push([...v(i[0],[1,1]),i[0][2]||.5]);let k=[],l={point:[i[0][0],i[0][1]],pressure:i[0][2]||.5,vector:[0,0],distance:0,runningLength:0};k.push(l);let j=i.length,q;for(let S=0;S<j;S++){q=i[S];let M=g&&S===j-1?q:R(l.point,q,1-o);if(C(l.point,M))continue;let P=T(x(l.point,M)),Q=K(M,l.point),Z=l.runningLength+Q;l={point:M,pressure:q[2]||.5,vector:P,distance:Q,runningLength:Z},k.push(l)}return k}function ft(t,e={}){return at(lt(t,e),e)}var Rt={getStroke:ft};export{Rt as default,ft as getStroke,at as getStrokeOutlinePoints,lt as getStrokePoints};

@@ -1,51 +0,10 @@

import type { StrokeOptions, StrokePoint } from './types';
/**
* ## getStrokePoints
* @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.
* @param streamline How much to streamline the stroke.
* @param size The stroke's size.
*/
export declare function getStrokePoints(points: number[][] | {
x: number;
y: number;
pressure?: number;
}[], options?: StrokeOptions): StrokePoint[];
/**
* ## getStrokeOutlinePoints
* @description Get an array of points (as `[x, y]`) representing the outline of a stroke.
* @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 options.size The base size (diameter) of the stroke.
* @param options.thinning The effect of pressure on the stroke's size.
* @param options.smoothing How much to soften the stroke's edges.
* @param options.easing An easing function to apply to each point's pressure.
* @param options.simulatePressure Whether to simulate pressure based on velocity.
* @param options.start Tapering and easing function for the start of the line.
* @param options.end Tapering and easing function for the end of the line.
* @param options.last Whether to handle the points as a completed stroke.
*/
export declare function getStrokeOutlinePoints(points: StrokePoint[], options?: Partial<StrokeOptions>): number[][];
/**
* ## getStroke
* @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 in both cases.
* @param options (optional) An object with options.
* @param options.size The base size (diameter) of the stroke.
* @param options.thinning The effect of pressure on the stroke's size.
* @param options.smoothing How much to soften the stroke's edges.
* @param options.easing An easing function to apply to each point's pressure.
* @param options.simulatePressure Whether to simulate pressure based on velocity.
* @param options.start Tapering and easing function for the start of the line.
* @param options.end Tapering and easing function for the end of the line.
* @param options.last Whether to handle the points as a completed stroke.
*/
declare function getStroke(points: number[][], options?: StrokeOptions): number[][];
declare function getStroke(points: {
x: number;
y: number;
pressure?: number;
}[], options?: StrokeOptions): number[][];
export default getStroke;
export { getStroke };
export { StrokeOptions };
import { getStroke } from './getStroke';
declare const _default: {
getStroke: typeof getStroke;
};
export default _default;
export * from './getStrokeOutlinePoints';
export * from './getStrokePoints';
export * from './getStroke';
export * from './types';
//# sourceMappingURL=index.d.ts.map

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

/**
* The options object for `getStroke` or `getStrokePoints`.
*/
export interface StrokeOptions {

@@ -20,2 +23,5 @@ size?: number;

}
/**
* The points returned by `getStrokePoints`, and the input for `getStrokeOutlinePoints`.
*/
export interface StrokePoint {

@@ -28,6 +34,2 @@ point: number[];

}
export declare type InputPoint = {
x: number;
y: number;
pressure?: number;
} | number[];
//# sourceMappingURL=types.d.ts.map

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

/**
* Negate a vector.
* @param A
*/
export declare function neg(A: number[]): number[];
/**
* Add vectors.
* @param A
* @param B
*/
export declare function add(A: number[], B: number[]): number[];
/**
* Subtract vectors.
* @param A
* @param B
*/
export declare function sub(A: number[], B: number[]): number[];
/**
* Get the vector from vectors A to B.
* @param A
* @param B
*/
export declare function vec(A: number[], B: number[]): number[];
/**
* Vector multiplication by scalar
* @param A
* @param n
*/
export declare function mul(A: number[], n: number): number[];
/**
* Vector division by scalar.
* @param A
* @param n
*/
export declare function div(A: number[], n: number): number[];
/**
* Perpendicular rotation of a vector A
* @param A
*/
export declare function per(A: number[]): number[];
/**
* Dot product
* @param A
* @param B
*/
export declare function dpr(A: number[], B: number[]): number;
/**
* Length of the vector
* @param A
*/
export declare function len(A: number[]): number;
/**
* 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.
* @param A
*/
export declare function uni(A: number[]): number[];
/**
* Dist length from A to B
* @param A
* @param B
*/
export declare function dist(A: number[], B: number[]): number;
/**
* Mean between two vectors or mid vector between two vectors
* @param A
* @param B
*/
export declare function med(A: number[], B: number[]): number[];
/**
* Rotate a vector around another vector by r (radians)
* @param A vector
* @param C center
* @param r rotation in radians
*/
export declare function rotAround(A: number[], C: number[], r: number): number[];
/**
* Interpolate vector A to B with a scalar t
* @param A
* @param B
* @param t scalar
*/
export declare function lrp(A: number[], B: number[], t: number): number[];
export declare function isLeft(p1: number[], pc: number[], p2: number[]): number;
export declare function clockwise(p1: number[], pc: number[], p2: number[]): boolean;
export declare function isEqual(a: number[], b: number[]): boolean;
export {};
//# sourceMappingURL=vec.d.ts.map
{
"version": "0.5.4",
"version": "1.0.0",
"name": "perfect-freehand",

@@ -64,3 +64,3 @@ "private": false,

},
"gitHead": "b52b4d22b7b12fbfc5cff300c7416ca7786aba15"
"gitHead": "4230a43d94d943c6329585a38370983c37bb3433"
}

@@ -1,512 +0,8 @@

import { toPointsArray, getStrokeRadius } from './utils'
import type { StrokeOptions, StrokePoint } from './types'
import * as vec from './vec'
import { getStroke } from './getStroke'
const { min, PI } = Math
export default { getStroke }
/**
* ## getStrokePoints
* @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.
* @param streamline How much to streamline the stroke.
* @param size The stroke's size.
*/
export function getStrokePoints(
points: number[][] | { x: number; y: number; pressure?: number }[],
options = {} as StrokeOptions
): StrokePoint[] {
let { streamline = 0.5 } = options
const { simulatePressure = true, last: isComplete = false } = options
// 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 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]])
// The strokePoints array will hold the points for the stroke.
// Start it out with the first point, which needs no adjustment.
const strokePoints: StrokePoint[] = []
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
// Create a new strokepoint (it will be the new "previous" one)
prev = {
point,
pressure: curr[2],
vector,
distance,
runningLength,
}
strokePoints.push(prev)
}
return strokePoints
}
const RATE_OF_CHANGE = 0.3
/**
* ## getStrokeOutlinePoints
* @description Get an array of points (as `[x, y]`) representing the outline of a stroke.
* @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 options.size The base size (diameter) of the stroke.
* @param options.thinning The effect of pressure on the stroke's size.
* @param options.smoothing How much to soften the stroke's edges.
* @param options.easing An easing function to apply to each point's pressure.
* @param options.simulatePressure Whether to simulate pressure based on velocity.
* @param options.start Tapering and easing function for the start of the line.
* @param options.end Tapering and easing function for the end of the line.
* @param options.last Whether to handle the points as a completed stroke.
*/
export function getStrokeOutlinePoints(
points: StrokePoint[],
options: Partial<StrokeOptions> = {} as Partial<StrokeOptions>
): number[][] {
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
// The number of points in the array
const len = points.length
// We can't do anything with an empty array.
if (len === 0) return []
// The total length of the line
const totalLength = points[len - 1].runningLength
// Our collected left and right points
const leftPts: number[][] = []
const rightPts: number[][] = []
// 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) {
// Speed of change - how fast should the the pressure changing?
const sp = min(1, curr.distance / size)
// Rate of change - how much of a change is there?
const rp = min(1, 1 - sp)
// Accelerate the pressure
pressure = min(1, acc + (rp - acc) * (sp * RATE_OF_CHANGE))
}
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
// Previous vector
let prevVector = points[0].vector
// Previous left and right points
let pl = points[0].point
let pr = pl
// Temporary left and right points
let tl = pl
let tr = pr
let short = true
/*
Find the outline's left and right points
Iterating through the points and populate the rightPts and leftPts arrays,
skipping the first and last pointsm, which will get caps later on.
*/
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
}
/*
Calculate the radius
If not thinning, the current point's radius will be half the size; or
otherwise, the size will be based on the current (real or simulated)
pressure.
*/
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 sp = min(1, distance / size)
const rp = min(1, 1 - sp)
pressure = min(
1,
prevPressure + (rp - prevPressure) * (sp * RATE_OF_CHANGE)
)
}
radius = getStrokeRadius(size, thinning, easing, pressure)
} else {
radius = size / 2
}
if (firstRadius === undefined) {
firstRadius = radius
}
/*
Apply tapering
If the current length is within the taper distance at either the
start or the end, calculate the taper strengths. Apply the smaller
of the two taper strengths to the 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))
/*
Handle sharp corners
Find the difference (dot product) between the current and next vector.
If the next vector is at more than a right angle to the current vector,
draw a cap at the current point.
*/
const nextVector = points[i + 1].vector
const dpr = vec.dpr(vector, nextVector)
if (dpr < 0) {
const offset = vec.mul(vec.per(prevVector), radius)
for (let t = 0; t < 1; t += 0.2) {
tr = vec.rotAround(vec.add(point, offset), point, PI * -t)
tl = vec.rotAround(vec.sub(point, offset), point, PI * t)
rightPts.push(tr)
leftPts.push(tl)
}
pl = tl
pr = tr
continue
}
/*
Add regular points
Project points to either side of the current point, using the
calculated size as a distance. If a point's distance to the
previous point on that side greater than the minimum distance
(or if the corner is kinda sharp), add the points to the side's
points array.
*/
const offset = vec.mul(vec.per(vec.lrp(nextVector, vector, dpr)), radius)
tl = vec.sub(point, offset)
tr = vec.add(point, offset)
const alwaysAdd = i < 2 || dpr < 0.25
const minDistance = Math.pow(
Math.max((runningLength > size ? size : size / 2) * smoothing, 1),
2
)
if (alwaysAdd || vec.dist2(pl, tl) > minDistance) {
leftPts.push(vec.lrp(pl, tl, streamline))
pl = tl
}
if (alwaysAdd || vec.dist2(pr, tr) > minDistance) {
rightPts.push(vec.lrp(pr, tr, streamline))
pr = tr
}
// Set variables for next iteration
prevPressure = pressure
prevVector = vector
}
/*
Drawing caps
Now that we have our points on either side of the line, we need to
draw caps at the start and end. Tapered lines don't have caps, but
may have dots for very short lines.
*/
const firstPoint = points[0]
const lastPoint = points[len - 1]
const isVeryShort = short || rightPts.length < 2 || leftPts.length < 2
/*
Draw a dot for very short or completed strokes
If the line is too short to gather left or right points and if the line is
not tapered on either side, draw a dot. If the line is tapered, then only
draw a dot if the line is both very short and complete. If we draw a dot,
we can just return those points.
*/
if (isVeryShort && (!(taperStart || taperEnd) || isComplete)) {
let ir = 0
for (let i = 0; i < len; i++) {
const { pressure, runningLength } = points[i]
if (runningLength > size) {
ir = getStrokeRadius(size, thinning, easing, pressure)
break
}
}
const start = vec.sub(
firstPoint.point,
vec.mul(
vec.per(vec.uni(vec.vec(lastPoint.point, firstPoint.point))),
ir || radius
)
)
const dotPts: number[][] = []
for (let t = 0, step = 0.1; t <= 1; t += step) {
dotPts.push(vec.rotAround(start, firstPoint.point, PI * 2 * t))
}
return dotPts
}
/*
Draw a start cap
Unless the line has a tapered start, or unless the line has a tapered end
and the line is very short, draw a start cap around the first point. Use
the distance between the second left and right point for the cap's radius.
Finally remove the first left and right points. :psyduck:
*/
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 (!vec.isEqual(tr, leftPts[i])) {
tl = leftPts[i]
break
}
}
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.1; t <= 1; t += step) {
const pt = vec.rotAround(start, firstPoint.point, PI * t)
if (vec.dist(pt, tl) < 1) break
startCap.push(pt)
}
leftPts.shift()
rightPts.shift()
}
} else {
startCap.push(firstPoint.point, vec.add(firstPoint.point, [0.1, 0.1]))
}
} else {
if (!vec.isEqual(tr, tl)) {
const vector = vec.uni(vec.vec(tr, tl))
const dist = vec.dist(tr, tl) / 2
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
If the line does not have a tapered end, and unless the line has a tapered
start and the line is very short, draw a cap around the last point. Finally,
remove the last left and right points. Otherwise, add the last point. Note
that This cap is a full-turn-and-a-half: this prevents incorrect caps on
sharp end turns.
*/
// 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))
if (capEnd || taperEnd) {
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.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)
}
} 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)))
}
}
/*
Return the points in the correct windind order: begin on the left side, then
continue around the end cap, then come back along the right side, and finally
complete the start cap.
*/
return leftPts.concat(endCap, rightPts.reverse(), startCap)
}
/**
* ## getStroke
* @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 in both cases.
* @param options (optional) An object with options.
* @param options.size The base size (diameter) of the stroke.
* @param options.thinning The effect of pressure on the stroke's size.
* @param options.smoothing How much to soften the stroke's edges.
* @param options.easing An easing function to apply to each point's pressure.
* @param options.simulatePressure Whether to simulate pressure based on velocity.
* @param options.start Tapering and easing function for the start of the line.
* @param options.end Tapering and easing function for the end of the line.
* @param options.last Whether to handle the points as a completed stroke.
*/
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 { getStroke }
export { StrokeOptions }
export * from './getStrokeOutlinePoints'
export * from './getStrokePoints'
export * from './getStroke'
export * from './types'

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

/**
* The options object for `getStroke` or `getStrokePoints`.
*/
export interface StrokeOptions {

@@ -21,2 +24,5 @@ size?: number

/**
* The points returned by `getStrokePoints`, and the input for `getStrokeOutlinePoints`.
*/
export interface StrokePoint {

@@ -29,3 +35,1 @@ point: number[]

}
export type InputPoint = { x: number; y: number; pressure?: number } | number[]
/**
* Negate a vector.
* @param A
* @internal
*/

@@ -13,2 +14,3 @@ export function neg(A: number[]) {

* @param B
* @internal
*/

@@ -23,2 +25,3 @@ export function add(A: number[], B: number[]) {

* @param B
* @internal
*/

@@ -30,15 +33,6 @@ export function sub(A: number[], B: number[]) {

/**
* Get the vector from vectors A to B.
* @param A
* @param B
*/
export function vec(A: number[], B: number[]) {
// A, B as vectors get the vector from A to B
return [B[0] - A[0], B[1] - A[1]]
}
/**
* Vector multiplication by scalar
* @param A
* @param n
* @internal
*/

@@ -53,2 +47,3 @@ export function mul(A: number[], n: number) {

* @param n
* @internal
*/

@@ -62,2 +57,3 @@ export function div(A: number[], n: number) {

* @param A
* @internal
*/

@@ -72,2 +68,3 @@ export function per(A: number[]) {

* @param B
* @internal
*/

@@ -79,4 +76,15 @@ export function dpr(A: number[], B: number[]) {

/**
* Get whether two vectors are equal.
* @param A
* @param B
* @internal
*/
export function isEqual(A: number[], B: number[]) {
return A[0] === B[0] && A[1] === B[1]
}
/**
* Length of the vector
* @param A
* @internal
*/

@@ -90,2 +98,3 @@ export function len(A: number[]) {

* @param A
* @internal
*/

@@ -100,2 +109,3 @@ export function len2(A: number[]) {

* @param B
* @internal
*/

@@ -109,2 +119,3 @@ export function dist2(A: number[], B: number[]) {

* @param A
* @internal
*/

@@ -119,2 +130,3 @@ export function uni(A: number[]) {

* @param B
* @internal
*/

@@ -129,2 +141,3 @@ export function dist(A: number[], B: number[]) {

* @param B
* @internal
*/

@@ -140,2 +153,3 @@ export function med(A: number[], B: number[]) {

* @param r rotation in radians
* @internal
*/

@@ -160,20 +174,17 @@ export function rotAround(A: number[], C: number[], r: number) {

* @param t scalar
* @internal
*/
export function lrp(A: number[], B: number[], t: number) {
return add(A, mul(vec(A, B), t))
return add(A, mul(sub(B, A), t))
}
// isLeft: >0 for counterclockwise
// =0 for none (degenerate)
// <0 for clockwise
export function isLeft(p1: number[], pc: number[], p2: number[]) {
return (pc[0] - p1[0]) * (p2[1] - p1[1]) - (p2[0] - p1[0]) * (pc[1] - p1[1])
/**
* Project a point A in the direction B by a scalar c
* @param A
* @param B
* @param c
* @internal
*/
export function prj(A: number[], B: number[], c: number) {
return add(A, mul(B, c))
}
export function clockwise(p1: number[], pc: number[], p2: number[]) {
return isLeft(p1, pc, p2) > 0
}
export function isEqual(a: number[], b: number[]) {
return a[0] === b[0] && a[1] === b[1]
}

@@ -11,3 +11,11 @@ {

"dist"
]
],
"compilerOptions": {
"composite": false,
"incremental": false,
"declarationMap": true,
"sourceMap": true,
"emitDeclarationOnly": true,
"stripInternal": true
}
}

@@ -8,7 +8,4 @@ {

"outDir": "./dist/types",
"baseUrl": "src",
"paths": {
"+*": ["./*"]
}
"baseUrl": "src"
}
}
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