perfect-freehand
Advanced tools
Comparing version 1.0.8 to 1.0.9
# Changelog | ||
## 1.1.0 | ||
## 1.0.8 | ||
- Removes more unused | ||
- Fixes bug when size was negative | ||
- Adds a few thousand tests | ||
## 1.0.8 | ||
- Removes unused code | ||
- Improves start and end caps | ||
## 1.0.6 | ||
- Fixes appearance of start caps | ||
@@ -6,0 +17,0 @@ - Fix appearance of tapered end cap |
@@ -1,1 +0,1 @@ | ||
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; | ||
var ue=Object.defineProperty;var be=e=>ue(e,"__esModule",{value:!0});var ge=(e,t)=>{be(e);for(var r in t)ue(e,r,{get:t[r],enumerable:!0})};ge(exports,{default:()=>ke,getStroke:()=>ee,getStrokeOutlinePoints:()=>B,getStrokePoints:()=>A});function Y(e,t,r,S=g=>g){return e*S(.5-t*(.5-r))}function p(e,t){return[e[0]+t[0],e[1]+t[1]]}function m(e,t){return[e[0]-t[0],e[1]-t[1]]}function f(e,t){return[e[0]*t,e[1]*t]}function de(e,t){return[e[0]/t,e[1]/t]}function I(e){return[e[1],-e[0]]}function ie(e,t){return e[0]*t[0]+e[1]*t[1]}function pe(e,t){return e[0]===t[0]&&e[1]===t[1]}function he(e){return Math.hypot(e[0],e[1])}function xe(e){return e[0]*e[0]+e[1]*e[1]}function Z(e,t){return xe(m(e,t))}function K(e){return de(e,he(e))}function me(e,t){return Math.hypot(e[1]-t[1],e[0]-t[0])}function ce(e,t){return f(p(e,t),.5)}function V(e,t,r){let S=Math.sin(r),g=Math.cos(r),y=e[0]-t[0],s=e[1]-t[1],x=y*g-s*S,L=y*S+s*g;return[x+t[0],L+t[1]]}function N(e,t,r){return p(e,f(m(t,e),r))}function $(e,t,r){return p(e,f(t,r))}var{min:_,PI:Se}=Math,ae=.275,j=Se+1e-4;function B(e,t={}){let{size:r=16,smoothing:S=.5,thinning:g=.5,simulatePressure:y=!0,easing:s=n=>n,start:x={},end:L={},last:q=!1}=t,{cap:k=!0,taper:P=0,easing:C=n=>n*(2-n)}=x,{cap:b=!0,taper:a=0,easing:T=n=>--n*n*n+1}=L;if(e.length===0||r<=0)return[];let U=e[e.length-1].runningLength,te=Math.pow(r*S,2),O=[],v=[],X=e.slice(0,10).reduce((n,i)=>{let o=i.pressure;if(y){let u=_(1,i.distance/r),l=_(1,1-u);o=_(1,n+(l-n)*(u*ae))}return(n+o)/2},e[0].pressure),c=Y(r,g,e[e.length-1].pressure,s),J,ne=e[0].vector,z=e[0].point,F=z,E=z,M=F;for(let n=0;n<e.length-1;n++){let{pressure:i}=e[n],{point:o,vector:u,distance:l,runningLength:R}=e[n];if(U-R<3)continue;if(g){if(y){let D=_(1,l/r),W=_(1,1-D);i=_(1,X+(W-X)*(D*ae))}c=Y(r,g,i,s)}else c=r/2;J===void 0&&(J=c);let le=R<P?C(R/P):1,fe=U-R<a?T((U-R)/a):1;c=Math.max(.01,c*Math.min(le,fe));let re=e[n+1].vector,oe=ie(u,re);if(oe<0){let D=f(I(ne),c);for(let W=1/13,H=0;H<=1;H+=W)E=V(m(o,D),o,j*H),O.push(E),M=V(p(o,D),o,j*-H),v.push(M);z=E,F=M;continue}let se=f(I(N(re,u,oe)),c);E=m(o,se),(n===0||Z(z,E)>te)&&(O.push(E),z=E),M=p(o,se),(n===0||Z(F,M)>te)&&(v.push(M),F=M),X=i,ne=u}let d=e[0].point.slice(0,2),h=e.length>1?e[e.length-1].point.slice(0,2):p(e[0].point,[1,1]),Q=O.length<=1||v.length<=1,w=[],G=[];if(Q){if(!(P||a)||q){let n=$(d,K(I(m(d,h))),-(J||c)),i=[];for(let o=1/13,u=o;u<=1;u+=o)i.push(V(n,d,j*2*u));return i}}else{if(P||a&&Q)w.push(d,p(d,[.1,0]));else if(k)for(let o=1/13,u=o;u<=1;u+=o){let l=V(v[0],d,j*u);w.push(l)}else{let o=m(O[0],v[0]),u=f(o,.5),l=f(o,.51);w.push(m(d,u),m(d,l),p(d,l),p(d,u))}let n=ce(O[O.length-1],v[v.length-1]),i=I(K(m(h,n)));if(a||P&&Q)G.push(h,p(h,[1,0]));else if(b){let o=$(h,i,c);for(let u=1/29,l=0;l<=1;l+=u){let R=V(o,h,j*3*l);G.push(R)}}else G.push(p(h,f(i,c)),p(h,f(i,c*.99)),m(h,f(i,c*.99)),m(h,f(i,c)))}return O.concat(G,v.reverse(),w)}function A(e,t={}){var C;let{streamline:r=.5,size:S=16,last:g=!1}=t;if(e.length===0)return[];let y=.15+(1-r)*.85,s=Array.isArray(e[0])?e:e.map(({x:b,y:a,pressure:T=.5})=>[b,a,T]);s.length===1&&(s=[...s,[...p(s[0],[1,1]),...s[0].slice(2)]]);let x=[{point:[s[0][0],s[0][1]],pressure:s[0][2]||.25,vector:[1,1],distance:0,runningLength:0}],L=!1,q=0,k=x[0],P=s.length-1;for(let b=1;b<s.length;b++){let a=g&&b===P?s[b]:N(k.point,s[b],y);if(pe(k.point,a))continue;let T=me(a,k.point);if(q+=T,b<P&&!L){if(q<S)continue;L=!0}k={point:a,pressure:s[b][2]||.5,vector:K(m(k.point,a)),distance:T,runningLength:q},x.push(k)}return x[0].vector=((C=x[1])==null?void 0:C.vector)||[0,0],x}function ee(e,t={}){return B(A(e,t),t)}var ke=ee; |
@@ -1,259 +0,1 @@ | ||
// 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 | ||
}; | ||
function Y(e,t,u,S=g=>g){return e*S(.5-t*(.5-u))}function p(e,t){return[e[0]+t[0],e[1]+t[1]]}function m(e,t){return[e[0]-t[0],e[1]-t[1]]}function f(e,t){return[e[0]*t,e[1]*t]}function fe(e,t){return[e[0]/t,e[1]/t]}function I(e){return[e[1],-e[0]]}function re(e,t){return e[0]*t[0]+e[1]*t[1]}function oe(e,t){return e[0]===t[0]&&e[1]===t[1]}function be(e){return Math.hypot(e[0],e[1])}function ge(e){return e[0]*e[0]+e[1]*e[1]}function Z(e,t){return ge(m(e,t))}function K(e){return fe(e,be(e))}function se(e,t){return Math.hypot(e[1]-t[1],e[0]-t[0])}function ue(e,t){return f(p(e,t),.5)}function V(e,t,u){let S=Math.sin(u),g=Math.cos(u),y=e[0]-t[0],o=e[1]-t[1],x=y*g-o*S,L=y*S+o*g;return[x+t[0],L+t[1]]}function N(e,t,u){return p(e,f(m(t,e),u))}function $(e,t,u){return p(e,f(t,u))}var{min:_,PI:de}=Math,ie=.275,j=de+1e-4;function pe(e,t={}){let{size:u=16,smoothing:S=.5,thinning:g=.5,simulatePressure:y=!0,easing:o=n=>n,start:x={},end:L={},last:q=!1}=t,{cap:k=!0,taper:P=0,easing:C=n=>n*(2-n)}=x,{cap:b=!0,taper:a=0,easing:T=n=>--n*n*n+1}=L;if(e.length===0||u<=0)return[];let U=e[e.length-1].runningLength,B=Math.pow(u*S,2),O=[],v=[],X=e.slice(0,10).reduce((n,i)=>{let r=i.pressure;if(y){let s=_(1,i.distance/u),l=_(1,1-s);r=_(1,n+(l-n)*(s*ie))}return(n+r)/2},e[0].pressure),c=Y(u,g,e[e.length-1].pressure,o),J,A=e[0].vector,z=e[0].point,F=z,E=z,M=F;for(let n=0;n<e.length-1;n++){let{pressure:i}=e[n],{point:r,vector:s,distance:l,runningLength:R}=e[n];if(U-R<3)continue;if(g){if(y){let D=_(1,l/u),W=_(1,1-D);i=_(1,X+(W-X)*(D*ie))}c=Y(u,g,i,o)}else c=u/2;J===void 0&&(J=c);let ae=R<P?C(R/P):1,le=U-R<a?T((U-R)/a):1;c=Math.max(.01,c*Math.min(ae,le));let ee=e[n+1].vector,te=re(s,ee);if(te<0){let D=f(I(A),c);for(let W=1/13,H=0;H<=1;H+=W)E=V(m(r,D),r,j*H),O.push(E),M=V(p(r,D),r,j*-H),v.push(M);z=E,F=M;continue}let ne=f(I(N(ee,s,te)),c);E=m(r,ne),(n===0||Z(z,E)>B)&&(O.push(E),z=E),M=p(r,ne),(n===0||Z(F,M)>B)&&(v.push(M),F=M),X=i,A=s}let d=e[0].point.slice(0,2),h=e.length>1?e[e.length-1].point.slice(0,2):p(e[0].point,[1,1]),Q=O.length<=1||v.length<=1,w=[],G=[];if(Q){if(!(P||a)||q){let n=$(d,K(I(m(d,h))),-(J||c)),i=[];for(let r=1/13,s=r;s<=1;s+=r)i.push(V(n,d,j*2*s));return i}}else{if(P||a&&Q)w.push(d,p(d,[.1,0]));else if(k)for(let r=1/13,s=r;s<=1;s+=r){let l=V(v[0],d,j*s);w.push(l)}else{let r=m(O[0],v[0]),s=f(r,.5),l=f(r,.51);w.push(m(d,s),m(d,l),p(d,l),p(d,s))}let n=ue(O[O.length-1],v[v.length-1]),i=I(K(m(h,n)));if(a||P&&Q)G.push(h,p(h,[1,0]));else if(b){let r=$(h,i,c);for(let s=1/29,l=0;l<=1;l+=s){let R=V(r,h,j*3*l);G.push(R)}}else G.push(p(h,f(i,c)),p(h,f(i,c*.99)),m(h,f(i,c*.99)),m(h,f(i,c)))}return O.concat(G,v.reverse(),w)}function me(e,t={}){var C;let{streamline:u=.5,size:S=16,last:g=!1}=t;if(e.length===0)return[];let y=.15+(1-u)*.85,o=Array.isArray(e[0])?e:e.map(({x:b,y:a,pressure:T=.5})=>[b,a,T]);o.length===1&&(o=[...o,[...p(o[0],[1,1]),...o[0].slice(2)]]);let x=[{point:[o[0][0],o[0][1]],pressure:o[0][2]||.25,vector:[1,1],distance:0,runningLength:0}],L=!1,q=0,k=x[0],P=o.length-1;for(let b=1;b<o.length;b++){let a=g&&b===P?o[b]:N(k.point,o[b],y);if(oe(k.point,a))continue;let T=se(a,k.point);if(q+=T,b<P&&!L){if(q<S)continue;L=!0}k={point:a,pressure:o[b][2]||.5,vector:K(m(k.point,a)),distance:T,runningLength:q},x.push(k)}return x[0].vector=((C=x[1])==null?void 0:C.vector)||[0,0],x}function ce(e,t={}){return pe(me(e,t),t)}var qe=ce;export{qe as default,ce as getStroke,pe as getStrokeOutlinePoints,me as getStrokePoints}; |
{ | ||
"version": "1.0.8", | ||
"version": "1.0.9", | ||
"name": "perfect-freehand", | ||
@@ -59,3 +59,3 @@ "private": false, | ||
}, | ||
"gitHead": "d2eccfdf5fe038dfa3718b2958954451660910fb" | ||
"gitHead": "9529e0e406174f3cfe9e39d2efe4dc8f26e86a02" | ||
} |
@@ -115,16 +115,16 @@ # ![Screenshot](perfect-freehand-logo.svg 'Perfect Freehand') | ||
```js | ||
function getSvgPathFromStroke(stroke) { | ||
if (!stroke.length) return '' | ||
function getSvgPathFromStroke(points: number[][]): string { | ||
if (!points.length) return '' | ||
const d = stroke.reduce( | ||
(acc, [x0, y0], i, arr) => { | ||
const [x1, y1] = arr[(i + 1) % arr.length] | ||
acc.push(x0, y0, (x0 + x1) / 2, (y0 + y1) / 2) | ||
return acc | ||
}, | ||
['M', ...stroke[0], 'Q'] | ||
) | ||
d.push('Z') | ||
return d.join(' ') | ||
return points | ||
.reduce( | ||
(acc, point, i, arr) => { | ||
if (i === points.length - 1) | ||
acc.push(point, Vec.med(point, arr[0]), 'L', arr[0], 'Z') | ||
else acc.push(point, Vec.med(point, arr[i + 1])) | ||
return acc | ||
}, | ||
['M', points[0], 'Q'] | ||
) | ||
.join(' ') | ||
} | ||
@@ -153,2 +153,3 @@ ``` | ||
const myPath = new Path2D(pathData) | ||
ctx.fill(myPath) | ||
@@ -189,5 +190,6 @@ ``` | ||
export default function Example() { | ||
const [points, setPoints] = React.useState() | ||
const [points, setPoints] = React.useState([]) | ||
function handlePointerDown(e) { | ||
e.target.setPointerCapture(e.pointerId) | ||
setPoints([[e.pageX, e.pageY, e.pressure]]) | ||
@@ -197,7 +199,15 @@ } | ||
function handlePointerMove(e) { | ||
if (e.buttons === 1) { | ||
setPoints([...points, [e.pageX, e.pageY, e.pressure]]) | ||
} | ||
if (e.buttons !== 1) return | ||
setPoints([...points, [e.pageX, e.pageY, e.pressure]]) | ||
} | ||
const stroke = getStroke(points, { | ||
size: 16, | ||
thinning: 0.5, | ||
smoothing: 0.5, | ||
streamline: 0.5, | ||
}) | ||
const pathData = getSvgPathFromStroke(stroke) | ||
return ( | ||
@@ -209,14 +219,3 @@ <svg | ||
> | ||
{points && ( | ||
<path | ||
d={getSvgPathFromStroke( | ||
getStroke(points, { | ||
size: 16, | ||
thinning: 0.5, | ||
smoothing: 0.5, | ||
streamline: 0.5, | ||
}) | ||
)} | ||
/> | ||
)} | ||
{points && <path d={pathData} />} | ||
</svg> | ||
@@ -231,10 +230,2 @@ ) | ||
#### `StrokeOptions` | ||
A TypeScript type for the options object. | ||
```ts | ||
import { StrokeOptions } from 'perfect-freehand' | ||
``` | ||
For advanced usage, the library also exports smaller functions that `getStroke` uses to generate its SVG data. While you can use `getStroke`'s data to render strokes with an HTML canvas (via the Path2D element) or with SVG paths, these new functions will allow you to create paths in other rendering technologies. | ||
@@ -244,18 +235,37 @@ | ||
A function that accepts an array of points (formatted either as `[x, y, pressure]` or `{ x: number, y: number, pressure: number}`) and a streamline value. Returns a set of adjusted points as `{ point, pressure, vector, distance, runningLength }`. The path's total length will be the `runningLength` of the last point in the array. | ||
```js | ||
import { strokePoints } from 'perfect-freehand' | ||
const strokePoints = getStrokePoints(rawInputPoints) | ||
import { getStrokePoints } from 'perfect-freehand' | ||
import samplePoints from "./samplePoints.json' | ||
const strokePoints = getStrokePoints(samplePoints) | ||
``` | ||
Accepts an array of points (formatted either as `[x, y, pressure]` or `{ x: number, y: number, pressure: number}`) and a streamline value. Returns a set of streamlined points as `[x, y, pressure, angle, distance, lengthAtPoint]`. The path's total length will be the length of the last point in the array. | ||
#### `getOutlinePoints` | ||
Accepts an array of points (formatted as `[x, y, pressure, angle, distance, length]`, i.e. the output of `getStrokePoints`) and returns an array of points (`[x, y]`) defining the outline of a pressure-sensitive stroke. | ||
A function that accepts an array of points (formatted as `{ point, pressure, vector, distance, runningLength }`, i.e. the output of `getStrokePoints`) and returns an array of points (`[x, y]`) defining the outline of a pressure-sensitive stroke. | ||
```js | ||
import { getOutlinePoints } from 'perfect-freehand' | ||
import { getStrokePoints, getOutlinePoints } from 'perfect-freehand' | ||
import samplePoints from "./samplePoints.json' | ||
const strokePoints = getStrokePoints(samplePoints) | ||
const outlinePoints = getOutlinePoints(strokePoints) | ||
``` | ||
#### `StrokeOptions` | ||
A TypeScript type for the options object. Useful if you're defining your options outside of the `getStroke` function. | ||
```ts | ||
import { StrokeOptions, getStroke } from 'perfect-freehand' | ||
const options: StrokeOptions = { | ||
size: 16, | ||
} | ||
const stroke = getStroke(options) | ||
``` | ||
## Support | ||
@@ -262,0 +272,0 @@ |
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
275
25399
117