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

perfect-freehand

Package Overview
Dependencies
Maintainers
1
Versions
56
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

perfect-freehand - npm Package Compare versions

Comparing version 0.4.2 to 0.4.3

5

CHANGELOG.md

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

## 0.4.3
- Improves caps, corners.
- Re-writes most comments.
## 0.4.2

@@ -2,0 +7,0 @@

12

dist/index.d.ts

@@ -7,2 +7,3 @@ import { StrokeOptions, StrokePoint } from './types';

* @param streamline How much to streamline the stroke.
* @param size The stroke's size.
*/

@@ -24,8 +25,10 @@ export declare function getStrokePoints<T extends number[], K extends {

* @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?: StrokeOptions): number[][];
export declare function getStrokeOutlinePoints(points: StrokePoint[], options?: Partial<StrokeOptions>): number[][];
/**
* ## getStroke
* @description Returns a stroke as an array of points.
* @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.

@@ -36,4 +39,7 @@ * @param options An (optional) object with options.

* @param options.smoothing How much to soften the stroke's edges.
* @param options.streamline How much to streamline the stroke.
* @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.
*/

@@ -40,0 +46,0 @@ export default function getStroke<T extends number[], K extends {

@@ -5,44 +5,2 @@ 'use strict';

function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(o);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _createForOfIteratorHelperLoose(o, allowArrayLike) {
var it;
if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) {
if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") {
if (it) o = it;
var i = 0;
return function () {
if (i >= o.length) return {
done: true
};
return {
done: false,
value: o[i++]
};
};
}
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
it = o[Symbol.iterator]();
return it.next.bind(it);
}
function lerp(y1, y2, mu) {

@@ -54,2 +12,8 @@ return y1 * (1 - mu) + y2 * mu;

}
/**
* Convert an array of points to the correct format ([x, y, radius])
* @param points
* @returns
*/
function toPointsArray(points) {

@@ -74,3 +38,21 @@ if (Array.isArray(points[0])) {

}
/**
* Compute a radius based on the pressure.
* @param size
* @param thinning
* @param easing
* @param pressure
* @returns
*/
function getStrokeRadius(size, thinning, easing, pressure) {
if (pressure === void 0) {
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;
}
/**

@@ -80,5 +62,2 @@ * Negate a vector.

*/
function neg(A) {
return [-A[0], -A[1]];
}
/**

@@ -173,11 +152,2 @@ * Add vectors.

/**
* Mean between two vectors or mid vector between two vectors
* @param A
* @param B
*/
function med(A, B) {
return mul(add(A, B), 0.5);
}
/**
* Rotate a vector around another vector by r (radians)

@@ -189,5 +159,5 @@ * @param A vector

function rotAround(A, C, rx, ry) {
var s = Math.sin(rx);
var c = Math.cos(ry);
function rotAround(A, C, r) {
var s = Math.sin(r);
var c = Math.cos(r);
var px = A[0] - C[0];

@@ -212,12 +182,2 @@ var py = A[1] - C[1];

PI = Math.PI;
function getStrokeRadius(size, thinning, easing, pressure) {
if (pressure === void 0) {
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;
}
/**

@@ -228,5 +188,5 @@ * ## getStrokePoints

* @param streamline How much to streamline the stroke.
* @param size The stroke's size.
*/
function getStrokePoints(points, streamline, size) {

@@ -242,5 +202,5 @@ if (streamline === void 0) {

var pts = toPointsArray(points);
var _short = true;
if (pts.length === 0) return [];
if (pts.length === 1) pts.push(add(pts[0], [1, 0]));
var len = pts.length;
if (len === 0) return [];
if (len === 1) pts.push(add(pts[0], [1, 0]));
var strokePoints = [{

@@ -255,3 +215,3 @@ point: [pts[0][0], pts[0][1]],

for (var i = 1, curr = pts[i], prev = strokePoints[0]; i < pts.length; i++, curr = pts[i], prev = strokePoints[i - 1]) {
var point = lrp(prev.point, [curr[0], curr[1]], 1 - streamline),
var point = lrp(prev.point, curr, 1 - streamline),
pressure = curr[2],

@@ -261,3 +221,3 @@ vector = uni(vec(point, prev.point)),

runningLength = prev.runningLength + distance;
var strokePoint = {
strokePoints.push({
point: point,

@@ -268,30 +228,49 @@ pressure: pressure,

runningLength: runningLength
};
strokePoints.push(strokePoint);
});
}
/*
Align vectors at the start of the line
Find the first stroke point past the size and then set all preceding points'
vectors to match this point's vector. This aligns the start cap and reduces
noise at the start of the line.
*/
if (_short && (runningLength > size || i === pts.length - 1)) {
_short = false;
for (var _iterator = _createForOfIteratorHelperLoose(strokePoints), _step; !(_step = _iterator()).done;) {
var pt = _step.value;
pt.vector = strokePoint.vector;
for (var _i = 0; _i < len; _i++) {
var _strokePoints$_i = strokePoints[_i],
_runningLength = _strokePoints$_i.runningLength,
_vector = _strokePoints$_i.vector;
if (_runningLength > size || _i === len - 1) {
for (var j = 0; j < _i; j++) {
strokePoints[j].vector = _vector;
}
break;
}
}
/*
Align vectors at the end of the line
Starting from the last point, work back until we've traveled more than
half of the line's size (width). Take the current point's vector and then
work forward, setting all remaining points' vectors to this vector. This
removes the "noise" at the end of the line and allows for a better-facing
end cap.
*/
if (i === pts.length - 1) {
var rlen = 0;
for (var k = i; k > 1; k--) {
var _strokePoint = strokePoints[k];
var totalLength = strokePoints[len - 1].runningLength;
if (rlen > size) {
for (var j = k; j < pts.length; j++) {
strokePoints[j].vector = _strokePoint.vector;
}
for (var _i2 = len - 1; _i2 > 1; _i2--) {
var _strokePoints$_i2 = strokePoints[_i2],
_runningLength2 = _strokePoints$_i2.runningLength,
_vector2 = _strokePoints$_i2.vector;
break;
}
if (totalLength - _runningLength2 > size / 2) {
for (var _j = _i2; _j < len; _j++) {
strokePoints[_j].vector = _vector2;
}
rlen += _strokePoint.distance;
}
break;
}

@@ -312,2 +291,4 @@ }

* @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.

@@ -339,7 +320,7 @@ */

_options$last = _options.last,
last = _options$last === void 0 ? false : _options$last;
isComplete = _options$last === void 0 ? false : _options$last;
var _start$taper = start.taper,
taperStart = _start$taper === void 0 ? 0 : _start$taper,
_start$easing = start.easing,
taperStartCurve = _start$easing === void 0 ? function (t) {
taperStartEase = _start$easing === void 0 ? function (t) {
return t * (2 - t);

@@ -350,158 +331,193 @@ } : _start$easing;

_end$easing = end.easing,
taperEndCurve = _end$easing === void 0 ? function (t) {
taperEndEase = _end$easing === void 0 ? function (t) {
return --t * t * t + 1;
} : _end$easing;
var len = points.length; // The number of points in the array
} : _end$easing; // The number of points in the array
var totalLength = points[len - 1].runningLength; // The total length of the line
var len = points.length; // We can't do anything with an empty array.
var minDist = size * smoothing; // The minimum distance for measurements
if (len === 0) return []; // The total length of the line
var leftPts = []; // Our collected left and right points
var totalLength = points[len - 1].runningLength; // Our collected left and right points
var rightPts = [];
var pl = points[0].point; // Previous left and right points
var leftPts = [];
var rightPts = []; // Previous pressure (start with average of first five pressures)
var pr = points[0].point;
var tl = pl; // Points to test distance from
var prevPressure = points.slice(0, 5).reduce(function (acc, cur) {
return (acc + cur.pressure) / 2;
}, points[0].pressure); // The current radius
var tr = pr;
var pa = points[0].vector;
var pp = 1; // Previous (maybe simulated) pressure
var radius = getStrokeRadius(size, thinning, easing, points[len - 1].pressure); // Previous vector
var ir = 0; // The initial radius
var prevVector = points[0].vector; // Previous left and right points
var r = size; // The current radius
var pl = points[0].point;
var pr = pl; // Temporary left and right points
var _short2 = true; // Whether the line is drawn far enough
// We can't do anything with an empty array.
var tl = pl;
var tr = pr;
/*
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.
*/
if (len === 0) return []; // Set initial radius
for (var i = 0; i < len - 1; i++) {
for (var i = 1; i < len - 1; i++) {
var _points$i = points[i],
point = _points$i.point,
pressure = _points$i.pressure,
vector = _points$i.vector,
distance = _points$i.distance,
runningLength = _points$i.runningLength;
/*
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 (runningLength > size) {
ir = getStrokeRadius(size, thinning, easing, pressure);
break;
}
} // Set radius for last point
r = getStrokeRadius(size, thinning, easing, points[len - 1].pressure); // For a point with more than one point, create an outline shape.
for (var _i = 1; _i < len - 1; _i++) {
var next = points[_i + 1];
var _points$_i = points[_i],
point = _points$_i.point,
_pressure = _points$_i.pressure,
vector = _points$_i.vector,
distance = _points$_i.distance,
_runningLength = _points$_i.runningLength;
if (_short2 && _runningLength > minDist) {
_short2 = false;
} // 1. Calculate the size of the current point.
if (thinning) {
if (simulatePressure) {
// Simulate pressure by accellerating the reported pressure.
var rp = min(1 - distance / size, 1);
var sp = min(distance / size, 1);
_pressure = min(1, pp + (rp - pp) * (sp / 2));
} // Compute the stroke radius based on the pressure, easing and thinning.
var rp = min(1, 1 - distance / size);
var sp = min(1, distance / size);
pressure = min(1, prevPressure + (rp - prevPressure) * (sp / 2));
}
r = getStrokeRadius(size, thinning, easing, _pressure);
radius = getStrokeRadius(size, thinning, easing, pressure);
} else {
r = size / 2;
} // 2. Apply tapering to start and end pressures
radius = size / 2;
}
/*
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.
*/
var ts = _runningLength < taperStart ? taperStartCurve(_runningLength / taperStart) : 1;
var te = totalLength - _runningLength < taperEnd ? taperEndCurve((totalLength - _runningLength) / taperEnd) : 1;
r = r * Math.min(ts, te); // 3. Handle sharp corners
// Find the delta between the current and next angle.
var ts = runningLength < taperStart ? taperStartEase(runningLength / taperStart) : 1;
var te = totalLength - runningLength < taperEnd ? taperEndEase((totalLength - runningLength) / taperEnd) : 1;
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.
*/
var dpr$1 = dpr(vector, next.vector);
var nextVector = points[i + 1].vector;
var dpr$1 = dpr(vector, nextVector);
if (dpr$1 < 0) {
// Draw a cap at the sharp corner.
var v = per(pa);
var pushedA = add(point, mul(v, r));
var pushedB = sub(point, mul(v, r));
var _offset = mul(per(prevVector), radius);
for (var t = 0; t <= 1; t += 0.25) {
var rx = PI * t;
var ry = PI * t;
tl = rotAround(pushedA, point, -rx, -ry);
tr = rotAround(pushedB, point, rx, ry);
var la = add(point, _offset);
var ra = sub(point, _offset);
for (var t = 0.2; t < 1; t += 0.2) {
tr = rotAround(la, point, PI * -t);
tl = rotAround(ra, point, PI * t);
rightPts.push(tr);
leftPts.push(tl);
rightPts.push(tr);
}
pl = tl;
pr = tr;
continue;
} // 4. Add regular point.
}
/*
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.
*/
pl = add(point, mul(per(vector), r));
pr = add(point, mul(neg(per(vector)), r));
var offset = mul(per(lrp(nextVector, vector, dpr$1)), radius);
tl = sub(point, offset);
tr = add(point, offset);
var tlu = uni(vec(tr, pr));
var tru = uni(vec(tl, pl));
var alwaysAdd = i === 1 || dpr$1 < 0.25;
var minDistance = (runningLength > size ? size : size / 2) * smoothing;
if (_i == 1 || dpr$1 < 0.25 || dist(pl, tl) > (_short2 ? minDist / 2 : minDist)) {
leftPts.push(med(tl, pl));
tl = pl;
if (alwaysAdd || dist(pr, tr) > minDistance && dpr(tlu, vector) > 0) {
rightPts.push(tr);
pr = tr;
}
if (_i == 1 || dpr$1 < 0.25 || dist(pr, tr) > (_short2 ? minDist / 2 : minDist)) {
rightPts.push(med(tr, pr));
tr = pr;
}
if (alwaysAdd || dist(pl, tl) > minDistance && dpr(tru, vector) > 0) {
leftPts.push(tl);
pl = tl;
} // Set variables for next iteration
pp = _pressure;
pa = vector;
} // 4. Draw caps
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.
*/
var firstPoint = points[0];
var lastPoint = points[points.length - 1];
var veryShort = leftPts.length < 2 || rightPts.length < 2;
var isTapering = taperStart + taperEnd > 0;
var lpv = lastPoint.vector;
var startCap = [];
var endCap = []; // Draw start cap if the end taper is set to zero
var lastPoint = points[len - 1];
var isVeryShort = 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 (veryShort) {
if (!isTapering || veryShort && last) {
// Backup: draw an inverse cap for the end cap
lpv = uni(vec(lastPoint.point, firstPoint.point));
if (isVeryShort && (!(taperStart || taperEnd) || isComplete)) {
var ir = 0;
var _start = add(firstPoint.point, mul(per(neg(lpv)), ir || r));
for (var _i3 = 0; _i3 < len; _i3++) {
var _points$_i = points[_i3],
_pressure = _points$_i.pressure,
_runningLength3 = _points$_i.runningLength;
for (var _t = 0, step = 0.1; _t <= 1; _t += step) {
var _rx = PI * -_t;
if (_runningLength3 > size) {
ir = getStrokeRadius(size, thinning, easing, _pressure);
break;
}
}
var _ry = PI * -_t;
var _start = sub(firstPoint.point, mul(per(uni(vec(lastPoint.point, firstPoint.point))), ir || radius));
startCap.push(rotAround(_start, firstPoint.point, _rx, _ry));
}
var dotPts = [];
leftPts.shift();
rightPts.shift();
for (var _t = 0, step = 0.1; _t <= 1; _t += step) {
dotPts.push(rotAround(_start, firstPoint.point, PI * 2 * _t));
}
} else if (taperStart === 0) {
// Draw a cap between second left / right points
var lp0 = leftPts[1];
var rp0 = rightPts[1];
var _start2 = add(firstPoint.point, mul(uni(vec(lp0, rp0)), dist(lp0, rp0) / 2));
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.
Finallym remove the first left and right points. :psyduck:
*/
for (var _t2 = 0, _step2 = 0.1; _t2 <= 1; _t2 += _step2) {
var _rx2 = PI * -_t2;
var _ry2 = PI * -_t2;
var startCap = [];
startCap.push(rotAround(_start2, firstPoint.point, _rx2, _ry2));
if (!taperStart && !(taperEnd && isVeryShort)) {
tr = rightPts[1];
tl = leftPts[1];
var _start2 = sub(firstPoint.point, mul(uni(vec(tr, tl)), dist(tr, tl) / 2));
for (var _t2 = 0, _step = 0.2; _t2 <= 1; _t2 += _step) {
startCap.push(rotAround(_start2, firstPoint.point, PI * _t2));
}

@@ -511,27 +527,39 @@

rightPts.shift();
} else if (points[1]) {
startCap.push(points[1].point);
} // Draw end cap if taper end is set to zero
}
/*
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.
*/
if (!isTapering || taperEnd === 0 && !veryShort || veryShort && last) {
var _start3 = add(lastPoint.point, mul(neg(per(lpv)), r));
var endCap = [];
for (var _t3 = 0, _step3 = 0.1; _t3 <= 1; _t3 += _step3) {
var _rx3 = PI * _t3;
if (!taperEnd && !(taperStart && isVeryShort)) {
var _start3 = sub(lastPoint.point, mul(per(lastPoint.vector), radius));
var _ry3 = PI * _t3;
for (var _t3 = 0, _step2 = 0.1; _t3 <= 1; _t3 += _step2) {
endCap.push(rotAround(_start3, lastPoint.point, PI * 3 * _t3));
}
endCap.push(rotAround(_start3, lastPoint.point, _rx3, _ry3));
}
leftPts.pop();
rightPts.pop();
} else {
endCap.push(lastPoint.point);
}
/*
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.
*/
var results = [].concat(startCap, leftPts, endCap.reverse(), rightPts.reverse());
return results;
return leftPts.concat(endCap, rightPts.reverse(), startCap);
}
/**
* ## getStroke
* @description Returns a stroke as an array of points.
* @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.

@@ -542,4 +570,7 @@ * @param options An (optional) object with options.

* @param options.smoothing How much to soften the stroke's edges.
* @param options.streamline How much to streamline the stroke.
* @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.
*/

@@ -552,4 +583,3 @@

var results = getStrokeOutlinePoints(getStrokePoints(points, options.streamline), options);
return results;
return getStrokeOutlinePoints(getStrokePoints(points, options.streamline), options);
}

@@ -556,0 +586,0 @@

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

"use strict";function n(n,r){(null==r||r>n.length)&&(r=n.length);for(var t=0,e=new Array(r);t<r;t++)e[t]=n[t];return e}function r(r,t){var e;if("undefined"==typeof Symbol||null==r[Symbol.iterator]){if(Array.isArray(r)||(e=function(r,t){if(r){if("string"==typeof r)return n(r,void 0);var e=Object.prototype.toString.call(r).slice(8,-1);return"Object"===e&&r.constructor&&(e=r.constructor.name),"Map"===e||"Set"===e?Array.from(r):"Arguments"===e||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(e)?n(r,void 0):void 0}}(r))||t&&r&&"number"==typeof r.length){e&&(r=e);var o=0;return function(){return o>=r.length?{done:!0}:{done:!1,value:r[o++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}return(e=r[Symbol.iterator]()).next.bind(e)}function t(n,r,t){return n*(1-t)+r*t}function e(n,r,t){return Math.max(r,Math.min(t,n))}function o(n){return[-n[0],-n[1]]}function i(n,r){return[n[0]+r[0],n[1]+r[1]]}function u(n,r){return[n[0]-r[0],n[1]-r[1]]}function a(n,r){return[r[0]-n[0],r[1]-n[1]]}function s(n,r){return[n[0]*r,n[1]*r]}function v(n){return[n[1],-n[0]]}function f(n){return function(n,r){return[n[0]/r,n[1]/r]}(n,function(n){return Math.hypot(n[0],n[1])}(n))}function c(n,r){return Math.hypot(n[1]-r[1],n[0]-r[0])}function p(n,r){return s(i(n,r),.5)}function h(n,r,t,e){var o=Math.sin(t),i=Math.cos(e),u=n[0]-r[0],a=n[1]-r[1];return[u*i-a*o+r[0],u*o+a*i+r[1]]}Object.defineProperty(exports,"__esModule",{value:!0});var l=Math.min,d=Math.PI;function g(n,r,o,i){return void 0===i&&(i=.5),r?(i=e(o(i),0,1),(r<0?t(n,n+n*e(r,-.95,-.05),i):t(n-n*e(r,.05,.95),n,i))/2):n/2}function m(n,t,e){void 0===t&&(t=.5),void 0===e&&(e=8);var o=function(n){return Array.isArray(n[0])?n.map((function(n){var r=n[2];return[n[0],n[1],void 0===r?.5:r]})):n.map((function(n){var r=n.pressure;return[n.x,n.y,void 0===r?.5:r]}))}(n),u=!0;if(0===o.length)return[];1===o.length&&o.push(i(o[0],[1,0]));for(var v,p,h=[{point:[o[0][0],o[0][1]],pressure:o[0][2],vector:[0,0],distance:0,runningLength:0}],l=1,d=o[l],g=h[0];l<o.length;d=o[++l],g=h[l-1]){var m=(p=1-t,i(v=g.point,s(a(v,[d[0],d[1]]),p))),y=d[2],b=f(a(m,g.point)),M=c(m,g.point),A=g.runningLength+M,S={point:m,pressure:y,vector:b,distance:M,runningLength:A};if(h.push(S),u&&(A>e||l===o.length-1)){u=!1;for(var x,L=r(h);!(x=L()).done;)x.value.vector=S.vector}if(l===o.length-1)for(var P=0,j=l;j>1;j--){var k=h[j];if(P>e){for(var I=j;I<o.length;I++)h[I].vector=k.vector;break}P+=k.distance}}return h}function y(n,r){void 0===r&&(r={});var t,e,m=r.size,y=void 0===m?8:m,b=r.thinning,M=void 0===b?.5:b,A=r.smoothing,S=r.simulatePressure,x=void 0===S||S,L=r.easing,P=void 0===L?function(n){return n}:L,j=r.start,k=void 0===j?{}:j,I=r.end,O=void 0===I?{}:I,w=r.last,_=void 0!==w&&w,z=k.taper,C=void 0===z?0:z,E=k.easing,T=void 0===E?function(n){return n*(2-n)}:E,U=O.taper,$=void 0===U?0:U,q=O.easing,B=void 0===q?function(n){return--n*n*n+1}:q,D=n.length,F=n[D-1].runningLength,G=y*(void 0===A?.5:A),H=[],J=[],K=n[0].point,N=n[0].point,Q=K,R=N,V=n[0].vector,W=1,X=0,Y=y,Z=!0;if(0===D)return[];for(var nn=0;nn<D-1;nn++){var rn=n[nn];if(rn.runningLength>y){X=g(y,M,P,rn.pressure);break}}Y=g(y,M,P,n[D-1].pressure);for(var tn=1;tn<D-1;tn++){var en=n[tn+1],on=n[tn],un=on.point,an=on.pressure,sn=on.vector,vn=on.distance,fn=on.runningLength;if(Z&&fn>G&&(Z=!1),M){if(x){var cn=l(1-vn/y,1),pn=l(vn/y,1);an=l(1,W+pn/2*(cn-W))}Y=g(y,M,P,an)}else Y=y/2;var hn=fn<C?T(fn/C):1,ln=F-fn<$?B((F-fn)/$):1;Y*=Math.min(hn,ln);var dn=(t=sn)[0]*(e=en.vector)[0]+t[1]*e[1];if(dn<0)for(var gn=v(V),mn=i(un,s(gn,Y)),yn=u(un,s(gn,Y)),bn=0;bn<=1;bn+=.25){var Mn=d*bn,An=d*bn;Q=h(mn,un,-Mn,-An),R=h(yn,un,Mn,An),H.push(Q),J.push(R)}else K=i(un,s(v(sn),Y)),N=i(un,s(o(v(sn)),Y)),(1==tn||dn<.25||c(K,Q)>(Z?G/2:G))&&(H.push(p(Q,K)),Q=K),(1==tn||dn<.25||c(N,R)>(Z?G/2:G))&&(J.push(p(R,N)),R=N),W=an,V=sn}var Sn=n[0],xn=n[n.length-1],Ln=H.length<2||J.length<2,Pn=C+$>0,jn=xn.vector,kn=[],In=[];if(Ln){if(!Pn||Ln&&_){jn=f(a(xn.point,Sn.point));for(var On=i(Sn.point,s(v(o(jn)),X||Y)),wn=0;wn<=1;wn+=.1)kn.push(h(On,Sn.point,d*-wn,d*-wn));H.shift(),J.shift()}}else if(0===C){for(var _n=H[1],zn=J[1],Cn=i(Sn.point,s(f(a(_n,zn)),c(_n,zn)/2)),En=0;En<=1;En+=.1)kn.push(h(Cn,Sn.point,d*-En,d*-En));H.shift(),J.shift()}else n[1]&&kn.push(n[1].point);if(!Pn||0===$&&!Ln||Ln&&_)for(var Tn=i(xn.point,s(o(v(jn)),Y)),Un=0;Un<=1;Un+=.1)In.push(h(Tn,xn.point,d*Un,d*Un));else In.push(xn.point);return[].concat(kn,H,In.reverse(),J.reverse())}exports.default=function(n,r){return void 0===r&&(r={}),y(m(n,r.streamline),r)},exports.getStrokeOutlinePoints=y,exports.getStrokePoints=m;
"use strict";function r(r,n,t){return r*(1-t)+n*t}function n(r,n,t){return Math.max(n,Math.min(t,r))}function t(t,e,i,o){return void 0===o&&(o=.5),e?(o=n(i(o),0,1),(e<0?r(t,t+t*n(e,-.95,-.05),o):r(t-t*n(e,.05,.95),t,o))/2):t/2}function e(r,n){return[r[0]+n[0],r[1]+n[1]]}function i(r,n){return[r[0]-n[0],r[1]-n[1]]}function o(r,n){return[n[0]-r[0],n[1]-r[1]]}function u(r,n){return[r[0]*n,r[1]*n]}function a(r){return[r[1],-r[0]]}function v(r,n){return r[0]*n[0]+r[1]*n[1]}function s(r){return function(r,n){return[r[0]/n,r[1]/n]}(r,function(r){return Math.hypot(r[0],r[1])}(r))}function f(r,n){return Math.hypot(r[1]-n[1],r[0]-n[0])}function p(r,n,t){var e=Math.sin(t),i=Math.cos(t),o=r[0]-n[0],u=r[1]-n[1];return[o*i-u*e+n[0],o*e+u*i+n[1]]}function c(r,n,t){return e(r,u(o(r,n),t))}Object.defineProperty(exports,"__esModule",{value:!0});var h=Math.min,g=Math.PI;function d(r,n,t){void 0===n&&(n=.5),void 0===t&&(t=8);var i=function(r){return Array.isArray(r[0])?r.map((function(r){var n=r[2];return[r[0],r[1],void 0===n?.5:n]})):r.map((function(r){var n=r.pressure;return[r.x,r.y,void 0===n?.5:n]}))}(r),u=i.length;if(0===u)return[];1===u&&i.push(e(i[0],[1,0]));for(var a=[{point:[i[0][0],i[0][1]],pressure:i[0][2],vector:[0,0],distance:0,runningLength:0}],v=1,p=i[v],h=a[0];v<i.length;p=i[++v],h=a[v-1]){var g=c(h.point,p,1-n),d=p[2],l=s(o(g,h.point)),M=f(g,h.point);a.push({point:g,pressure:d,vector:l,distance:M,runningLength:h.runningLength+M})}for(var m=0;m<u;m++){var L=a[m],x=L.vector;if(L.runningLength>t||m===u-1){for(var y=0;y<m;y++)a[y].vector=x;break}}for(var k=a[u-1].runningLength,P=u-1;P>1;P--){var b=a[P],A=b.vector;if(k-b.runningLength>t/2){for(var O=P;O<u;O++)a[O].vector=A;break}}return a}function l(r,n){void 0===n&&(n={});var d=n.size,l=void 0===d?8:d,M=n.thinning,m=void 0===M?.5:M,L=n.smoothing,x=void 0===L?.5:L,y=n.simulatePressure,k=void 0===y||y,P=n.easing,b=void 0===P?function(r){return r}:P,A=n.start,O=void 0===A?{}:A,S=n.end,_=void 0===S?{}:S,j=n.last,z=void 0!==j&&j,I=O.taper,q=void 0===I?0:I,w=O.easing,B=void 0===w?function(r){return r*(2-r)}:w,C=_.taper,D=void 0===C?0:C,E=_.easing,F=void 0===E?function(r){return--r*r*r+1}:E,G=r.length;if(0===G)return[];for(var H=r[G-1].runningLength,J=[],K=[],N=r.slice(0,5).reduce((function(r,n){return(r+n.pressure)/2}),r[0].pressure),Q=t(l,m,b,r[G-1].pressure),R=r[0].vector,T=r[0].point,U=T,V=T,W=U,X=1;X<G-1;X++){var Y=r[X],Z=Y.point,$=Y.pressure,rr=Y.vector,nr=Y.distance,tr=Y.runningLength;if(m){if(k){var er=h(1,1-nr/l),ir=h(1,nr/l);$=h(1,N+ir/2*(er-N))}Q=t(l,m,b,$)}else Q=l/2;var or=tr<q?B(tr/q):1,ur=H-tr<D?F((H-tr)/D):1;Q*=Math.min(or,ur);var ar=r[X+1].vector,vr=v(rr,ar);if(vr<0){for(var sr=u(a(R),Q),fr=e(Z,sr),pr=i(Z,sr),cr=.2;cr<1;cr+=.2)W=p(fr,Z,g*-cr),V=p(pr,Z,g*cr),K.push(W),J.push(V);T=V,U=W}else{var hr=u(a(c(ar,rr,vr)),Q);V=i(Z,hr);var gr=s(o(W=e(Z,hr),U)),dr=s(o(V,T)),lr=1===X||vr<.25,Mr=(tr>l?l:l/2)*x;(lr||f(U,W)>Mr&&v(gr,rr)>0)&&(K.push(W),U=W),(lr||f(T,V)>Mr&&v(dr,rr)>0)&&(J.push(V),T=V),N=$,R=rr}}var mr=r[0],Lr=r[G-1],xr=K.length<2||J.length<2;if(xr&&(!q&&!D||z)){for(var yr=0,kr=0;kr<G;kr++){var Pr=r[kr];if(Pr.runningLength>l){yr=t(l,m,b,Pr.pressure);break}}for(var br=i(mr.point,u(a(s(o(Lr.point,mr.point))),yr||Q)),Ar=[],Or=0;Or<=1;Or+=.1)Ar.push(p(br,mr.point,2*g*Or));return Ar}var Sr=[];if(!(q||D&&xr)){for(var _r=i(mr.point,u(s(o(W=K[1],V=J[1])),f(W,V)/2)),jr=0;jr<=1;jr+=.2)Sr.push(p(_r,mr.point,g*jr));J.shift(),K.shift()}var zr=[];if(D||q&&xr)zr.push(Lr.point);else{for(var Ir=i(Lr.point,u(a(Lr.vector),Q)),qr=0;qr<=1;qr+=.1)zr.push(p(Ir,Lr.point,3*g*qr));J.pop(),K.pop()}return J.concat(zr,K.reverse(),Sr)}exports.default=function(r,n){return void 0===n&&(n={}),l(d(r,n.streamline),n)},exports.getStrokeOutlinePoints=l,exports.getStrokePoints=d;
//# sourceMappingURL=perfect-freehand.cjs.production.min.js.map

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

function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(o);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _createForOfIteratorHelperLoose(o, allowArrayLike) {
var it;
if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) {
if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") {
if (it) o = it;
var i = 0;
return function () {
if (i >= o.length) return {
done: true
};
return {
done: false,
value: o[i++]
};
};
}
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
it = o[Symbol.iterator]();
return it.next.bind(it);
}
function lerp(y1, y2, mu) {

@@ -49,2 +7,8 @@ return y1 * (1 - mu) + y2 * mu;

}
/**
* Convert an array of points to the correct format ([x, y, radius])
* @param points
* @returns
*/
function toPointsArray(points) {

@@ -69,3 +33,21 @@ if (Array.isArray(points[0])) {

}
/**
* Compute a radius based on the pressure.
* @param size
* @param thinning
* @param easing
* @param pressure
* @returns
*/
function getStrokeRadius(size, thinning, easing, pressure) {
if (pressure === void 0) {
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;
}
/**

@@ -75,5 +57,2 @@ * Negate a vector.

*/
function neg(A) {
return [-A[0], -A[1]];
}
/**

@@ -168,11 +147,2 @@ * Add vectors.

/**
* Mean between two vectors or mid vector between two vectors
* @param A
* @param B
*/
function med(A, B) {
return mul(add(A, B), 0.5);
}
/**
* Rotate a vector around another vector by r (radians)

@@ -184,5 +154,5 @@ * @param A vector

function rotAround(A, C, rx, ry) {
var s = Math.sin(rx);
var c = Math.cos(ry);
function rotAround(A, C, r) {
var s = Math.sin(r);
var c = Math.cos(r);
var px = A[0] - C[0];

@@ -207,12 +177,2 @@ var py = A[1] - C[1];

PI = Math.PI;
function getStrokeRadius(size, thinning, easing, pressure) {
if (pressure === void 0) {
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;
}
/**

@@ -223,5 +183,5 @@ * ## getStrokePoints

* @param streamline How much to streamline the stroke.
* @param size The stroke's size.
*/
function getStrokePoints(points, streamline, size) {

@@ -237,5 +197,5 @@ if (streamline === void 0) {

var pts = toPointsArray(points);
var _short = true;
if (pts.length === 0) return [];
if (pts.length === 1) pts.push(add(pts[0], [1, 0]));
var len = pts.length;
if (len === 0) return [];
if (len === 1) pts.push(add(pts[0], [1, 0]));
var strokePoints = [{

@@ -250,3 +210,3 @@ point: [pts[0][0], pts[0][1]],

for (var i = 1, curr = pts[i], prev = strokePoints[0]; i < pts.length; i++, curr = pts[i], prev = strokePoints[i - 1]) {
var point = lrp(prev.point, [curr[0], curr[1]], 1 - streamline),
var point = lrp(prev.point, curr, 1 - streamline),
pressure = curr[2],

@@ -256,3 +216,3 @@ vector = uni(vec(point, prev.point)),

runningLength = prev.runningLength + distance;
var strokePoint = {
strokePoints.push({
point: point,

@@ -263,30 +223,49 @@ pressure: pressure,

runningLength: runningLength
};
strokePoints.push(strokePoint);
});
}
/*
Align vectors at the start of the line
Find the first stroke point past the size and then set all preceding points'
vectors to match this point's vector. This aligns the start cap and reduces
noise at the start of the line.
*/
if (_short && (runningLength > size || i === pts.length - 1)) {
_short = false;
for (var _iterator = _createForOfIteratorHelperLoose(strokePoints), _step; !(_step = _iterator()).done;) {
var pt = _step.value;
pt.vector = strokePoint.vector;
for (var _i = 0; _i < len; _i++) {
var _strokePoints$_i = strokePoints[_i],
_runningLength = _strokePoints$_i.runningLength,
_vector = _strokePoints$_i.vector;
if (_runningLength > size || _i === len - 1) {
for (var j = 0; j < _i; j++) {
strokePoints[j].vector = _vector;
}
break;
}
}
/*
Align vectors at the end of the line
Starting from the last point, work back until we've traveled more than
half of the line's size (width). Take the current point's vector and then
work forward, setting all remaining points' vectors to this vector. This
removes the "noise" at the end of the line and allows for a better-facing
end cap.
*/
if (i === pts.length - 1) {
var rlen = 0;
for (var k = i; k > 1; k--) {
var _strokePoint = strokePoints[k];
var totalLength = strokePoints[len - 1].runningLength;
if (rlen > size) {
for (var j = k; j < pts.length; j++) {
strokePoints[j].vector = _strokePoint.vector;
}
for (var _i2 = len - 1; _i2 > 1; _i2--) {
var _strokePoints$_i2 = strokePoints[_i2],
_runningLength2 = _strokePoints$_i2.runningLength,
_vector2 = _strokePoints$_i2.vector;
break;
}
if (totalLength - _runningLength2 > size / 2) {
for (var _j = _i2; _j < len; _j++) {
strokePoints[_j].vector = _vector2;
}
rlen += _strokePoint.distance;
}
break;
}

@@ -307,2 +286,4 @@ }

* @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.

@@ -334,7 +315,7 @@ */

_options$last = _options.last,
last = _options$last === void 0 ? false : _options$last;
isComplete = _options$last === void 0 ? false : _options$last;
var _start$taper = start.taper,
taperStart = _start$taper === void 0 ? 0 : _start$taper,
_start$easing = start.easing,
taperStartCurve = _start$easing === void 0 ? function (t) {
taperStartEase = _start$easing === void 0 ? function (t) {
return t * (2 - t);

@@ -345,158 +326,193 @@ } : _start$easing;

_end$easing = end.easing,
taperEndCurve = _end$easing === void 0 ? function (t) {
taperEndEase = _end$easing === void 0 ? function (t) {
return --t * t * t + 1;
} : _end$easing;
var len = points.length; // The number of points in the array
} : _end$easing; // The number of points in the array
var totalLength = points[len - 1].runningLength; // The total length of the line
var len = points.length; // We can't do anything with an empty array.
var minDist = size * smoothing; // The minimum distance for measurements
if (len === 0) return []; // The total length of the line
var leftPts = []; // Our collected left and right points
var totalLength = points[len - 1].runningLength; // Our collected left and right points
var rightPts = [];
var pl = points[0].point; // Previous left and right points
var leftPts = [];
var rightPts = []; // Previous pressure (start with average of first five pressures)
var pr = points[0].point;
var tl = pl; // Points to test distance from
var prevPressure = points.slice(0, 5).reduce(function (acc, cur) {
return (acc + cur.pressure) / 2;
}, points[0].pressure); // The current radius
var tr = pr;
var pa = points[0].vector;
var pp = 1; // Previous (maybe simulated) pressure
var radius = getStrokeRadius(size, thinning, easing, points[len - 1].pressure); // Previous vector
var ir = 0; // The initial radius
var prevVector = points[0].vector; // Previous left and right points
var r = size; // The current radius
var pl = points[0].point;
var pr = pl; // Temporary left and right points
var _short2 = true; // Whether the line is drawn far enough
// We can't do anything with an empty array.
var tl = pl;
var tr = pr;
/*
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.
*/
if (len === 0) return []; // Set initial radius
for (var i = 0; i < len - 1; i++) {
for (var i = 1; i < len - 1; i++) {
var _points$i = points[i],
point = _points$i.point,
pressure = _points$i.pressure,
vector = _points$i.vector,
distance = _points$i.distance,
runningLength = _points$i.runningLength;
/*
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 (runningLength > size) {
ir = getStrokeRadius(size, thinning, easing, pressure);
break;
}
} // Set radius for last point
r = getStrokeRadius(size, thinning, easing, points[len - 1].pressure); // For a point with more than one point, create an outline shape.
for (var _i = 1; _i < len - 1; _i++) {
var next = points[_i + 1];
var _points$_i = points[_i],
point = _points$_i.point,
_pressure = _points$_i.pressure,
vector = _points$_i.vector,
distance = _points$_i.distance,
_runningLength = _points$_i.runningLength;
if (_short2 && _runningLength > minDist) {
_short2 = false;
} // 1. Calculate the size of the current point.
if (thinning) {
if (simulatePressure) {
// Simulate pressure by accellerating the reported pressure.
var rp = min(1 - distance / size, 1);
var sp = min(distance / size, 1);
_pressure = min(1, pp + (rp - pp) * (sp / 2));
} // Compute the stroke radius based on the pressure, easing and thinning.
var rp = min(1, 1 - distance / size);
var sp = min(1, distance / size);
pressure = min(1, prevPressure + (rp - prevPressure) * (sp / 2));
}
r = getStrokeRadius(size, thinning, easing, _pressure);
radius = getStrokeRadius(size, thinning, easing, pressure);
} else {
r = size / 2;
} // 2. Apply tapering to start and end pressures
radius = size / 2;
}
/*
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.
*/
var ts = _runningLength < taperStart ? taperStartCurve(_runningLength / taperStart) : 1;
var te = totalLength - _runningLength < taperEnd ? taperEndCurve((totalLength - _runningLength) / taperEnd) : 1;
r = r * Math.min(ts, te); // 3. Handle sharp corners
// Find the delta between the current and next angle.
var ts = runningLength < taperStart ? taperStartEase(runningLength / taperStart) : 1;
var te = totalLength - runningLength < taperEnd ? taperEndEase((totalLength - runningLength) / taperEnd) : 1;
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.
*/
var dpr$1 = dpr(vector, next.vector);
var nextVector = points[i + 1].vector;
var dpr$1 = dpr(vector, nextVector);
if (dpr$1 < 0) {
// Draw a cap at the sharp corner.
var v = per(pa);
var pushedA = add(point, mul(v, r));
var pushedB = sub(point, mul(v, r));
var _offset = mul(per(prevVector), radius);
for (var t = 0; t <= 1; t += 0.25) {
var rx = PI * t;
var ry = PI * t;
tl = rotAround(pushedA, point, -rx, -ry);
tr = rotAround(pushedB, point, rx, ry);
var la = add(point, _offset);
var ra = sub(point, _offset);
for (var t = 0.2; t < 1; t += 0.2) {
tr = rotAround(la, point, PI * -t);
tl = rotAround(ra, point, PI * t);
rightPts.push(tr);
leftPts.push(tl);
rightPts.push(tr);
}
pl = tl;
pr = tr;
continue;
} // 4. Add regular point.
}
/*
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.
*/
pl = add(point, mul(per(vector), r));
pr = add(point, mul(neg(per(vector)), r));
var offset = mul(per(lrp(nextVector, vector, dpr$1)), radius);
tl = sub(point, offset);
tr = add(point, offset);
var tlu = uni(vec(tr, pr));
var tru = uni(vec(tl, pl));
var alwaysAdd = i === 1 || dpr$1 < 0.25;
var minDistance = (runningLength > size ? size : size / 2) * smoothing;
if (_i == 1 || dpr$1 < 0.25 || dist(pl, tl) > (_short2 ? minDist / 2 : minDist)) {
leftPts.push(med(tl, pl));
tl = pl;
if (alwaysAdd || dist(pr, tr) > minDistance && dpr(tlu, vector) > 0) {
rightPts.push(tr);
pr = tr;
}
if (_i == 1 || dpr$1 < 0.25 || dist(pr, tr) > (_short2 ? minDist / 2 : minDist)) {
rightPts.push(med(tr, pr));
tr = pr;
}
if (alwaysAdd || dist(pl, tl) > minDistance && dpr(tru, vector) > 0) {
leftPts.push(tl);
pl = tl;
} // Set variables for next iteration
pp = _pressure;
pa = vector;
} // 4. Draw caps
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.
*/
var firstPoint = points[0];
var lastPoint = points[points.length - 1];
var veryShort = leftPts.length < 2 || rightPts.length < 2;
var isTapering = taperStart + taperEnd > 0;
var lpv = lastPoint.vector;
var startCap = [];
var endCap = []; // Draw start cap if the end taper is set to zero
var lastPoint = points[len - 1];
var isVeryShort = 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 (veryShort) {
if (!isTapering || veryShort && last) {
// Backup: draw an inverse cap for the end cap
lpv = uni(vec(lastPoint.point, firstPoint.point));
if (isVeryShort && (!(taperStart || taperEnd) || isComplete)) {
var ir = 0;
var _start = add(firstPoint.point, mul(per(neg(lpv)), ir || r));
for (var _i3 = 0; _i3 < len; _i3++) {
var _points$_i = points[_i3],
_pressure = _points$_i.pressure,
_runningLength3 = _points$_i.runningLength;
for (var _t = 0, step = 0.1; _t <= 1; _t += step) {
var _rx = PI * -_t;
if (_runningLength3 > size) {
ir = getStrokeRadius(size, thinning, easing, _pressure);
break;
}
}
var _ry = PI * -_t;
var _start = sub(firstPoint.point, mul(per(uni(vec(lastPoint.point, firstPoint.point))), ir || radius));
startCap.push(rotAround(_start, firstPoint.point, _rx, _ry));
}
var dotPts = [];
leftPts.shift();
rightPts.shift();
for (var _t = 0, step = 0.1; _t <= 1; _t += step) {
dotPts.push(rotAround(_start, firstPoint.point, PI * 2 * _t));
}
} else if (taperStart === 0) {
// Draw a cap between second left / right points
var lp0 = leftPts[1];
var rp0 = rightPts[1];
var _start2 = add(firstPoint.point, mul(uni(vec(lp0, rp0)), dist(lp0, rp0) / 2));
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.
Finallym remove the first left and right points. :psyduck:
*/
for (var _t2 = 0, _step2 = 0.1; _t2 <= 1; _t2 += _step2) {
var _rx2 = PI * -_t2;
var _ry2 = PI * -_t2;
var startCap = [];
startCap.push(rotAround(_start2, firstPoint.point, _rx2, _ry2));
if (!taperStart && !(taperEnd && isVeryShort)) {
tr = rightPts[1];
tl = leftPts[1];
var _start2 = sub(firstPoint.point, mul(uni(vec(tr, tl)), dist(tr, tl) / 2));
for (var _t2 = 0, _step = 0.2; _t2 <= 1; _t2 += _step) {
startCap.push(rotAround(_start2, firstPoint.point, PI * _t2));
}

@@ -506,27 +522,39 @@

rightPts.shift();
} else if (points[1]) {
startCap.push(points[1].point);
} // Draw end cap if taper end is set to zero
}
/*
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.
*/
if (!isTapering || taperEnd === 0 && !veryShort || veryShort && last) {
var _start3 = add(lastPoint.point, mul(neg(per(lpv)), r));
var endCap = [];
for (var _t3 = 0, _step3 = 0.1; _t3 <= 1; _t3 += _step3) {
var _rx3 = PI * _t3;
if (!taperEnd && !(taperStart && isVeryShort)) {
var _start3 = sub(lastPoint.point, mul(per(lastPoint.vector), radius));
var _ry3 = PI * _t3;
for (var _t3 = 0, _step2 = 0.1; _t3 <= 1; _t3 += _step2) {
endCap.push(rotAround(_start3, lastPoint.point, PI * 3 * _t3));
}
endCap.push(rotAround(_start3, lastPoint.point, _rx3, _ry3));
}
leftPts.pop();
rightPts.pop();
} else {
endCap.push(lastPoint.point);
}
/*
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.
*/
var results = [].concat(startCap, leftPts, endCap.reverse(), rightPts.reverse());
return results;
return leftPts.concat(endCap, rightPts.reverse(), startCap);
}
/**
* ## getStroke
* @description Returns a stroke as an array of points.
* @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.

@@ -537,4 +565,7 @@ * @param options An (optional) object with options.

* @param options.smoothing How much to soften the stroke's edges.
* @param options.streamline How much to streamline the stroke.
* @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.
*/

@@ -547,4 +578,3 @@

var results = getStrokeOutlinePoints(getStrokePoints(points, options.streamline), options);
return results;
return getStrokeOutlinePoints(getStrokePoints(points, options.streamline), options);
}

@@ -551,0 +581,0 @@

export interface StrokeOptions {
size?: number;
thinning?: number;
smoothing?: number;
streamline?: number;
easing?: (pressure: number) => number;
simulatePressure?: boolean;
start?: {
taper?: number;
easing?: (distance: number) => number;
size: number;
thinning: number;
smoothing: number;
streamline: number;
easing: (pressure: number) => number;
simulatePressure: boolean;
start: {
taper: number;
easing: (distance: number) => number;
};
end?: {
taper?: number;
easing?: (distance: number) => number;
end: {
taper: number;
easing: (distance: number) => number;
};
last?: boolean;
last: boolean;
}

@@ -18,0 +18,0 @@ export interface StrokePoint {

export declare function lerp(y1: number, y2: number, mu: number): number;
export declare function clamp(n: number, a: number, b: number): number;
/**
* Convert an array of points to the correct format ([x, y, radius])
* @param points
* @returns
*/
export declare function toPointsArray<T extends number[], K extends {

@@ -8,1 +13,10 @@ x: number;

}>(points: (T | K)[]): number[][];
/**
* Compute a radius based on the pressure.
* @param size
* @param thinning
* @param easing
* @param pressure
* @returns
*/
export declare function getStrokeRadius(size: number, thinning: number, easing: (t: number) => number, pressure?: number): number;

@@ -58,7 +58,2 @@ /**

/**
* Get normalized / unit vector.
* @param A
*/
export declare function normalize(A: number[]): number[];
/**
* Dist length from A to B

@@ -81,3 +76,3 @@ * @param A

*/
export declare function rotAround(A: number[], C: number[], rx: number, ry: number): number[];
export declare function rotAround(A: number[], C: number[], r: number): number[];
/**

@@ -84,0 +79,0 @@ * Interpolate vector A to B with a scalar t

{
"version": "0.4.2",
"version": "0.4.3",
"name": "perfect-freehand",

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

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

import { toPointsArray, clamp, lerp } from './utils'
import { toPointsArray, getStrokeRadius } from './utils'
import { StrokeOptions, StrokePoint } from './types'

@@ -7,17 +7,2 @@ import * as vec from './vec'

function getStrokeRadius(
size: number,
thinning: number,
easing: (t: number) => number,
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
)
}
/**

@@ -28,2 +13,3 @@ * ## getStrokePoints

* @param streamline How much to streamline the stroke.
* @param size The stroke's size.
*/

@@ -35,9 +21,8 @@ export function getStrokePoints<

const pts = toPointsArray(points)
const len = pts.length
let short = true
if (len === 0) return []
if (pts.length === 0) return []
if (len === 1) pts.push(vec.add(pts[0], [1, 0]))
if (pts.length === 1) pts.push(vec.add(pts[0], [1, 0]))
const strokePoints: StrokePoint[] = [

@@ -58,3 +43,3 @@ {

) {
const point = vec.lrp(prev.point, [curr[0], curr[1]], 1 - streamline),
const point = vec.lrp(prev.point, curr, 1 - streamline),
pressure = curr[2],

@@ -65,3 +50,3 @@ vector = vec.uni(vec.vec(point, prev.point)),

const strokePoint = {
strokePoints.push({
point,

@@ -72,25 +57,40 @@ pressure,

runningLength,
}
})
}
strokePoints.push(strokePoint)
if (short && (runningLength > size || i === pts.length - 1)) {
short = false
for (let pt of strokePoints) {
pt.vector = strokePoint.vector
/*
Align vectors at the start of the line
Find the first stroke point past the size and then set all preceding points'
vectors to match this point's vector. This aligns the start cap and reduces
noise at the start of the line.
*/
for (let i = 0; i < len; i++) {
const { runningLength, vector } = strokePoints[i]
if (runningLength > size || i === len - 1) {
for (let j = 0; j < i; j++) {
strokePoints[j].vector = vector
}
break
}
}
if (i === pts.length - 1) {
let rlen = 0
for (let k = i; k > 1; k--) {
const strokePoint = strokePoints[k]
if (rlen > size) {
for (let j = k; j < pts.length; j++) {
strokePoints[j].vector = strokePoint.vector
}
break
}
rlen += strokePoint.distance
/*
Align vectors at the end of the line
Starting from the last point, work back until we've traveled more than
half of the line's size (width). Take the current point's vector and then
work forward, setting all remaining points' vectors to this vector. This
removes the "noise" at the end of the line and allows for a better-facing
end cap.
*/
const totalLength = strokePoints[len - 1].runningLength
for (let i = len - 1; i > 1; i--) {
const { runningLength, vector } = strokePoints[i]
if (totalLength - runningLength > size / 2) {
for (let j = i; j < len; j++) {
strokePoints[j].vector = vector
}
break
}

@@ -112,2 +112,4 @@ }

* @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.

@@ -117,3 +119,3 @@ */

points: StrokePoint[],
options: StrokeOptions = {} as StrokeOptions
options: Partial<StrokeOptions> = {} as Partial<StrokeOptions>
): number[][] {

@@ -126,5 +128,5 @@ const {

easing = t => t,
start = {},
end = {},
last = false,
start = {} as Partial<StrokeOptions['start']>,
end = {} as Partial<StrokeOptions['end']>,
last: isComplete = false,
} = options

@@ -134,3 +136,3 @@

taper: taperStart = 0,
easing: taperStartCurve = t => t * (2 - t),
easing: taperStartEase = t => t * (2 - t),
} = start

@@ -140,67 +142,78 @@

taper: taperEnd = 0,
easing: taperEndCurve = t => --t * t * t + 1,
easing: taperEndEase = t => --t * t * t + 1,
} = end
const len = points.length // The number of points in the array
const totalLength = points[len - 1].runningLength // The total length of the line
const minDist = size * smoothing // The minimum distance for measurements
const leftPts: number[][] = [] // Our collected left and right points
// 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[][] = []
let pl = points[0].point // Previous left and right points
let pr = points[0].point
let tl = pl // Points to test distance from
// Previous pressure (start with average of first five pressures)
let prevPressure = points
.slice(0, 5)
.reduce((acc, cur) => (acc + cur.pressure) / 2, points[0].pressure)
// The current radius
let radius = getStrokeRadius(size, thinning, easing, points[len - 1].pressure)
// 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 pa = points[0].vector
let pp = 1 // Previous (maybe simulated) pressure
let ir = 0 // The initial radius
let r = size // The current radius
let short = true // Whether the line is drawn far enough
// We can't do anything with an empty array.
if (len === 0) return []
/*
Find the outline's left and right points
// Set initial radius
for (let i = 0; i < len - 1; i++) {
let { pressure, runningLength } = points[i]
if (runningLength > size) {
ir = getStrokeRadius(size, thinning, easing, pressure)
break
}
}
Iterating through the points and populate the rightPts and leftPts arrays,
skipping the first and last pointsm, which will get caps later on.
*/
// Set radius for last point
r = getStrokeRadius(size, thinning, easing, points[len - 1].pressure)
// For a point with more than one point, create an outline shape.
for (let i = 1; i < len - 1; i++) {
const next = points[i + 1]
let { point, pressure, vector, distance, runningLength } = points[i]
if (short && runningLength > minDist) {
short = false
}
/*
Calculate the radius
// 1. Calculate the size of the current point.
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 (simulatePressure) {
// Simulate pressure by accellerating the reported pressure.
const rp = min(1 - distance / size, 1)
const sp = min(distance / size, 1)
pressure = min(1, pp + (rp - pp) * (sp / 2))
const rp = min(1, 1 - distance / size)
const sp = min(1, distance / size)
pressure = min(1, prevPressure + (rp - prevPressure) * (sp / 2))
}
// Compute the stroke radius based on the pressure, easing and thinning.
r = getStrokeRadius(size, thinning, easing, pressure)
radius = getStrokeRadius(size, thinning, easing, pressure)
} else {
r = size / 2
radius = size / 2
}
// 2. Apply tapering to start and end pressures
/*
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
? taperStartCurve(runningLength / taperStart)
? taperStartEase(runningLength / taperStart)
: 1

@@ -210,114 +223,182 @@

totalLength - runningLength < taperEnd
? taperEndCurve((totalLength - runningLength) / taperEnd)
? taperEndEase((totalLength - runningLength) / taperEnd)
: 1
r = r * Math.min(ts, te)
radius *= Math.min(ts, te)
// 3. Handle sharp corners
/*
Handle sharp corners
// Find the delta between the current and next angle.
const dpr = vec.dpr(vector, next.vector)
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) {
// Draw a cap at the sharp corner.
const v = vec.per(pa)
const pushedA = vec.add(point, vec.mul(v, r))
const pushedB = vec.sub(point, vec.mul(v, r))
const offset = vec.mul(vec.per(prevVector), radius)
const la = vec.add(point, offset)
const ra = vec.sub(point, offset)
for (let t = 0; t <= 1; t += 0.25) {
const rx = PI * t
const ry = PI * t
tl = vec.rotAround(pushedA, point, -rx, -ry)
tr = vec.rotAround(pushedB, point, rx, ry)
for (let t = 0.2; t < 1; t += 0.2) {
tr = vec.rotAround(la, point, PI * -t)
tl = vec.rotAround(ra, point, PI * t)
rightPts.push(tr)
leftPts.push(tl)
rightPts.push(tr)
}
pl = tl
pr = tr
continue
}
// 4. Add regular point.
/*
Add regular points
pl = vec.add(point, vec.mul(vec.per(vector), r))
pr = vec.add(point, vec.mul(vec.neg(vec.per(vector)), r))
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 tlu = vec.uni(vec.vec(tr, pr))
const tru = vec.uni(vec.vec(tl, pl))
const alwaysAdd = i === 1 || dpr < 0.25
const minDistance = (runningLength > size ? size : size / 2) * smoothing
if (
i == 1 ||
dpr < 0.25 ||
vec.dist(pl, tl) > (short ? minDist / 2 : minDist)
alwaysAdd ||
(vec.dist(pr, tr) > minDistance && vec.dpr(tlu, vector) > 0)
) {
leftPts.push(vec.med(tl, pl))
tl = pl
rightPts.push(tr)
pr = tr
}
if (
i == 1 ||
dpr < 0.25 ||
vec.dist(pr, tr) > (short ? minDist / 2 : minDist)
alwaysAdd ||
(vec.dist(pl, tl) > minDistance && vec.dpr(tru, vector) > 0)
) {
rightPts.push(vec.med(tr, pr))
tr = pr
leftPts.push(tl)
pl = tl
}
pp = pressure
pa = vector
// Set variables for next iteration
prevPressure = pressure
prevVector = vector
}
// 4. Draw caps
/*
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[points.length - 1]
const veryShort = leftPts.length < 2 || rightPts.length < 2
const isTapering = taperStart + taperEnd > 0
let lpv = lastPoint.vector
const lastPoint = points[len - 1]
const isVeryShort = rightPts.length < 2 || leftPts.length < 2
const startCap: number[][] = []
const endCap: number[][] = []
/*
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.
*/
// Draw start cap if the end taper is set to zero
if (isVeryShort && (!(taperStart || taperEnd) || isComplete)) {
let ir = 0
if (veryShort) {
if (!isTapering || (veryShort && last)) {
// Backup: draw an inverse cap for the end cap
lpv = vec.uni(vec.vec(lastPoint.point, firstPoint.point))
const start = vec.add(
firstPoint.point,
vec.mul(vec.per(vec.neg(lpv)), ir || r)
)
for (let t = 0, step = 0.1; t <= 1; t += step) {
const rx = PI * -t
const ry = PI * -t
startCap.push(vec.rotAround(start, firstPoint.point, rx, ry))
for (let i = 0; i < len; i++) {
const { pressure, runningLength } = points[i]
if (runningLength > size) {
ir = getStrokeRadius(size, thinning, easing, pressure)
break
}
leftPts.shift()
rightPts.shift()
}
} else if (taperStart === 0) {
// Draw a cap between second left / right points
const lp0 = leftPts[1]
const rp0 = rightPts[1]
const start = vec.add(
const start = vec.sub(
firstPoint.point,
vec.mul(vec.uni(vec.vec(lp0, rp0)), vec.dist(lp0, rp0) / 2)
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) {
const rx = PI * -t
const ry = PI * -t
startCap.push(vec.rotAround(start, firstPoint.point, rx, ry))
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.
Finallym remove the first left and right points. :psyduck:
*/
const startCap: number[][] = []
if (!taperStart && !(taperEnd && isVeryShort)) {
tr = rightPts[1]
tl = leftPts[1]
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.2; t <= 1; t += step) {
startCap.push(vec.rotAround(start, firstPoint.point, PI * t))
}
leftPts.shift()
rightPts.shift()
} else if (points[1]) {
startCap.push(points[1].point)
}
// Draw end cap if taper end is set to zero
/*
Draw an end cap
if (!isTapering || (taperEnd === 0 && !veryShort) || (veryShort && last)) {
const start = vec.add(lastPoint.point, vec.mul(vec.neg(vec.per(lpv)), r))
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.
*/
const endCap: number[][] = []
if (!taperEnd && !(taperStart && isVeryShort)) {
const start = vec.sub(
lastPoint.point,
vec.mul(vec.per(lastPoint.vector), radius)
)
for (let t = 0, step = 0.1; t <= 1; t += step) {
const rx = PI * t
const ry = PI * t
endCap.push(vec.rotAround(start, lastPoint.point, rx, ry))
endCap.push(vec.rotAround(start, lastPoint.point, PI * 3 * t))
}
leftPts.pop()
rightPts.pop()
} else {

@@ -327,10 +408,9 @@ endCap.push(lastPoint.point)

const results = [
...startCap,
...leftPts,
...endCap.reverse(),
...rightPts.reverse(),
]
/*
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 results
return leftPts.concat(endCap, rightPts.reverse(), startCap)
}

@@ -340,3 +420,3 @@

* ## getStroke
* @description Returns a stroke as an array of points.
* @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.

@@ -347,4 +427,7 @@ * @param options An (optional) object with options.

* @param options.smoothing How much to soften the stroke's edges.
* @param options.streamline How much to streamline the stroke.
* @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.
*/

@@ -355,10 +438,8 @@ export default function getStroke<

>(points: (T | K)[], options: StrokeOptions = {} as StrokeOptions): number[][] {
const results = getStrokeOutlinePoints(
return getStrokeOutlinePoints(
getStrokePoints(points, options.streamline),
options
)
return results
}
export { StrokeOptions }
export interface StrokeOptions {
size?: number
thinning?: number
smoothing?: number
streamline?: number
easing?: (pressure: number) => number
simulatePressure?: boolean
start?: {
taper?: number
easing?: (distance: number) => number
size: number
thinning: number
smoothing: number
streamline: number
easing: (pressure: number) => number
simulatePressure: boolean
start: {
taper: number
easing: (distance: number) => number
}
end?: {
taper?: number
easing?: (distance: number) => number
end: {
taper: number
easing: (distance: number) => number
}
last?: boolean
last: boolean
}

@@ -18,0 +18,0 @@

@@ -9,2 +9,7 @@ export function lerp(y1: number, y2: number, mu: number) {

/**
* Convert an array of points to the correct format ([x, y, radius])
* @param points
* @returns
*/
export function toPointsArray<

@@ -28,1 +33,24 @@ T extends number[],

}
/**
* Compute a radius based on the pressure.
* @param size
* @param thinning
* @param easing
* @param pressure
* @returns
*/
export function getStrokeRadius(
size: number,
thinning: number,
easing: (t: number) => number,
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
)
}

@@ -89,10 +89,2 @@ /**

/**
* Get normalized / unit vector.
* @param A
*/
export function normalize(A: number[]) {
return uni(A)
}
/**
* Dist length from A to B

@@ -121,5 +113,5 @@ * @param A

*/
export function rotAround(A: number[], C: number[], rx: number, ry: number) {
const s = Math.sin(rx)
const c = Math.cos(ry)
export function rotAround(A: number[], C: number[], r: number) {
const s = Math.sin(r)
const c = Math.cos(r)

@@ -126,0 +118,0 @@ const px = A[0] - C[0]

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

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

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc