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.2.5 to 0.3.0

dist/types.d.ts

21

CHANGELOG.md

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

# 0.3.0
This version has breaking changes.
- Removes polygon-clipping as a dependency. The problems it solved are no longer problems but a developer might still use it separately for aesthetic reasons.
- Removes `clipPath`.
- Removes options types other than `StrokeOptions`.
- Removes `getShortStrokeOutlinePoints`.
- Removes `pressure` option.
- Removes `minSize` and `maxSize` options.
- Adds `size` and `thinning` options.
- Renames `smooth` to `smoothing`.
- Improves caps.
- Improves dots and short strokes.
- You can now use `thinning` to create strokes that shink at high pressure as well as at low pressure. This is a normalized value based on the `size` option:
- at `0` the `thinning` property will have no effect on a stroke's width.
- at `1` a stroke will reach zero width at the lowest pressure and its full width (`size`) at the highest pressure
- at `-1` a stroke will reach zero width at the highest pressure and its full width at the lowest pressure.
- Setting `thinning` to zero has the same effect as had setting the now removed `pressure` option to `false`.
- Improves code organization and comments.
# 0.2.5

@@ -2,0 +23,0 @@

53

dist/index.d.ts

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

import polygonClipping from 'polygon-clipping';
export declare function lerpAngles(a0: number, a1: number, t: number): number;
export interface StrokePointsOptions {
streamline?: number;
}
export interface StrokeOutlineOptions extends StrokePointsOptions {
simulatePressure?: boolean;
pressure?: boolean;
minSize?: number;
maxSize?: number;
smooth?: number;
}
export interface StrokeOptions extends StrokeOutlineOptions {
clip?: boolean;
}
import { StrokeOptions } from './types';
/**

@@ -20,3 +6,3 @@ * ## getStrokePoints

* @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional.
* @param options An (optional) object with options.
* @param streamline How much to streamline the stroke.
*/

@@ -27,11 +13,4 @@ export declare function getStrokePoints<T extends number[], K extends {

pressure?: number;
}>(points: (T | K)[], options?: StrokePointsOptions): number[][];
}>(points: (T | K)[], streamline?: number): number[][];
/**
* ## getShortStrokeOutlinePoints
* @description Draw an outline around a short stroke.
* @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional.
* @param options An (optional) object with options.
*/
export declare function getShortStrokeOutlinePoints(points: number[][], options?: StrokeOutlineOptions): number[][];
/**
* ## getStrokeOutlinePoints

@@ -41,20 +20,24 @@ * @description Get an array of points (as `[x, y]`) representing the outline of a stroke.

* @param options An (optional) object with options.
* @param options.size The base size (diameter) of the stroke.
* @param options.thinning The effect of pressure on the stroke's size.
* @param options.smoothing How much to soften the stroke's edges.
* @param options.simulatePressure Whether to simulate pressure based on velocity.
*/
export declare function getStrokeOutlinePoints(points: number[][], options?: StrokeOutlineOptions): number[][];
export declare function getStrokeOutlinePoints(points: number[][], options?: StrokeOptions): number[][];
/**
* ## clipPath
* @description Returns a clipped polygon of the provided points.
* @param points An array of points (as number[]), the output of getStrokeOutlinePoints.
*/
export declare function clipPath(points: number[][]): polygonClipping.MultiPolygon;
/**
* ## getPath
* @description Returns a pressure sensitive stroke SVG data
* ## getStroke
* @description Returns a stroke as an array of points.
* @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional.
* @param options An (optional) object with options.
* @param options.size The base size (diameter) of the stroke.
* @param options.thinning The effect of pressure on the stroke's size.
* @param options.smoothing How much to soften the stroke's edges.
* @param options.streamline How much to streamline the stroke.
* @param options.simulatePressure Whether to simulate pressure based on velocity.
*/
export default function getPath<T extends number[], K extends {
export default function getStroke<T extends number[], K extends {
x: number;
y: number;
pressure?: number;
}>(points: (T | K)[], options?: StrokeOptions): string;
}>(points: (T | K)[], options?: StrokeOptions): number[][];
export { StrokeOptions };

@@ -5,52 +5,3 @@ 'use strict';

function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var polygonClipping = _interopDefault(require('polygon-clipping'));
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);
}
/* --------------------- Helpers -------------------- */
var abs = Math.abs,
hypot = Math.hypot,
var hypot = Math.hypot,
cos = Math.cos,

@@ -61,12 +12,28 @@ max = Math.max,

atan2 = Math.atan2,
PI = Math.PI,
TAU = PI / 2,
PI2 = PI * 2;
PI = Math.PI;
/**
* Linear interpolation betwen two numbers.
* @param y1
* @param y2
* @param mu
*/
function projectPoint(x0, y0, a, d) {
return [cos(a) * d + x0, sin(a) * d + y0];
function lerp(y1, y2, mu) {
return y1 * (1 - mu) + y2 * mu;
}
/**
* Project a point in a direction, by an angle.
* @param x0
* @param y0
* @param a
* @param d
* @returns
*/
function projectPoint(p0, a, d) {
return [cos(a) * d + p0[0], sin(a) * d + p0[1]];
}
function shortAngleDist(a0, a1) {
var max = PI2;
var max = PI * 2;
var da = (a1 - a0) % max;

@@ -76,11 +43,6 @@ return 2 * da % max - da;

function lerpAngles(a0, a1, t) {
return a0 + shortAngleDist(a0, a1) * t;
}
function angleDelta(a0, a1) {
function getAngleDelta(a0, a1) {
return shortAngleDist(a0, a1);
}
function getPointBetween(x0, y0, x1, y1, d) {
function getPointBetween(p0, p1, d) {
if (d === void 0) {

@@ -90,17 +52,13 @@ d = 0.5;

return [x0 + (x1 - x0) * d, y0 + (y1 - y0) * d];
return [p0[0] + (p1[0] - p0[0]) * d, p0[1] + (p1[1] - p0[1]) * d];
}
function getAngle(x0, y0, x1, y1) {
return atan2(y1 - y0, x1 - x0);
function getAngle(p0, p1) {
return atan2(p1[1] - p0[1], p1[0] - p0[0]);
}
function getDistance(x0, y0, x1, y1) {
return hypot(y1 - y0, x1 - x0);
function getDistance(p0, p1) {
return hypot(p1[1] - p0[1], p1[0] - p0[0]);
}
function clamp(n, a, b) {
return max(a, min(b, n));
}
function toPointsArray(points) {

@@ -125,4 +83,8 @@ if (Array.isArray(points[0])) {

}
/* --------------------- Methods -------------------- */
var abs = Math.abs,
min$1 = Math.min,
PI$1 = Math.PI,
TAU = PI$1 / 2,
SHARP = PI$1 * 0.7;
/**

@@ -132,14 +94,10 @@ * ## getStrokePoints

* @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional.
* @param options An (optional) object with options.
* @param streamline How much to streamline the stroke.
*/
function getStrokePoints(points, options) {
if (options === void 0) {
options = {};
function getStrokePoints(points, streamline) {
if (streamline === void 0) {
streamline = 0.5;
}
var _options = options,
_options$streamline = _options.streamline,
streamline = _options$streamline === void 0 ? 0.5 : _options$streamline;
var aPoints = toPointsArray(points);

@@ -149,3 +107,3 @@ var x,

angle,
length = 0,
totalLength = 0,
distance = 0.01,

@@ -172,15 +130,9 @@ len = aPoints.length,

distance = getDistance(x, y, px, py); // Angle
distance = getDistance([x, y], prev); // Angle
angle = getAngle(px, py, x, y); // If distance is very short, blend the angles
angle = getAngle(prev, [x, y]); // Increment total length
if (distance < 1) angle = lerpAngles(prev[2], angle, 0.5);
length += distance;
prev = [x, y, angle, ip, distance, length];
totalLength += distance;
prev = [x, y, ip, angle, distance, totalLength];
pts.push(prev);
} // Assign second angle to first point
if (pts.length > 1) {
pts[0][2] = pts[1][2];
}

@@ -191,9 +143,13 @@

/**
* ## getShortStrokeOutlinePoints
* @description Draw an outline around a short stroke.
* ## getStrokeOutlinePoints
* @description Get an array of points (as `[x, y]`) representing the outline of a stroke.
* @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional.
* @param options An (optional) object with options.
* @param options.size The base size (diameter) of the stroke.
* @param options.thinning The effect of pressure on the stroke's size.
* @param options.smoothing How much to soften the stroke's edges.
* @param options.simulatePressure Whether to simulate pressure based on velocity.
*/
function getShortStrokeOutlinePoints(points, options) {
function getStrokeOutlinePoints(points, options) {
if (options === void 0) {

@@ -203,163 +159,147 @@ options = {};

var _options2 = options,
_options2$minSize = _options2.minSize,
minSize = _options2$minSize === void 0 ? 2.5 : _options2$minSize,
_options2$maxSize = _options2.maxSize,
maxSize = _options2$maxSize === void 0 ? 8 : _options2$maxSize;
var len = points.length; // Can't draw an outline without any points
var _options = options,
_options$size = _options.size,
size = _options$size === void 0 ? 8 : _options$size,
_options$thinning = _options.thinning,
thinning = _options$thinning === void 0 ? 0.5 : _options$thinning,
_options$smoothing = _options.smoothing,
smoothing = _options$smoothing === void 0 ? 0.5 : _options$smoothing,
_options$simulatePres = _options.simulatePressure,
simulatePressure = _options$simulatePres === void 0 ? true : _options$simulatePres;
var len = points.length,
totalLength = points[len - 1][5],
// The total length of the line
minDist = size * smoothing,
// The minimum distance for measurements
leftPts = [],
// Our collected left and right points
rightPts = [];
var pl = points[0],
// Previous left and right points
pr = points[0],
tl = pl,
// Points to test distance from
tr = pr,
pp = 0,
// Previous (maybe simulated) pressure
r = size / 2,
// The current point radius
_short = true; // Whether the line is drawn far enough
// We can't do anything with an empty array.
if (len === 0) {
return [];
}
} // If the point is only one point long, draw two caps at either end.
var _points$ = points[0],
x0 = _points$[0],
y0 = _points$[1],
_points = points[len - 1],
x1 = _points[0],
y1 = _points[1],
p = points[len - 1][3],
leftPts = [],
rightPts = [],
size = clamp(minSize + (maxSize - minSize) * (p ? p : 0.5), minSize, maxSize),
angle = x0 === x1 ? 0 : getAngle(x0, y0, x1, y1);
for (var t = 0, step = 0.1; t <= 1; t += step) {
leftPts.push(projectPoint(x1, y1, angle + TAU - t * PI, size - 1));
rightPts.push(projectPoint(x0, y0, angle + TAU + t * PI, size - 1));
}
if (len === 1 || totalLength < size / 2) {
var first = points[0],
last = points[len - 1],
angle = getAngle(first, last);
return leftPts.concat(rightPts.reverse());
}
/**
* ## getStrokeOutlinePoints
* @description Get an array of points (as `[x, y]`) representing the outline of a stroke.
* @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional.
* @param options An (optional) object with options.
*/
if (thinning) {
var pressure = last[3] ? clamp(last[3], 0, 1) : 0.5;
r = (thinning > 0 ? lerp(size - size * thinning, size, clamp(pressure, 0, 1)) : lerp(size, size + size * thinning, clamp(pressure, 0, 1))) / 2;
}
function getStrokeOutlinePoints(points, options) {
if (options === void 0) {
options = {};
}
for (var t = 0, step = 0.1; t <= 1; t += step) {
tl = projectPoint(first, angle + PI$1 + TAU - t * PI$1, r - 1);
tr = projectPoint(last, angle + TAU - t * PI$1, r - 1);
leftPts.push(tl);
rightPts.push(tr);
}
var _options3 = options,
_options3$simulatePre = _options3.simulatePressure,
simulatePressure = _options3$simulatePre === void 0 ? true : _options3$simulatePre,
_options3$pressure = _options3.pressure,
pressure = _options3$pressure === void 0 ? true : _options3$pressure,
_options3$minSize = _options3.minSize,
minSize = _options3$minSize === void 0 ? 2.5 : _options3$minSize,
_options3$maxSize = _options3.maxSize,
maxSize = _options3$maxSize === void 0 ? 8 : _options3$maxSize,
_options3$smooth = _options3.smooth,
smooth = _options3$smooth === void 0 ? 8 : _options3$smooth;
var len = points.length,
p0 = points[0],
p1 = points[0],
t0 = p0,
t1 = p1,
m0 = p0,
m1 = p0,
size = 0,
pp = 0.5,
started = false,
length = 0,
leftPts = [p0],
rightPts = [p0],
d0,
d1;
return leftPts.concat(rightPts);
} // For a point with more than one point, create an outline shape.
if (len === 0) {
return [];
} // Use the points to create an outline shape, where the width
// of the shape is determined by the pressure at each point.
for (var i = 1; i < len; i++) {
var _points2 = points[i - 1],
px = _points2[0],
py = _points2[1],
pa = _points2[2];
var prev = points[i - 1],
pa = prev[3];
var _points$i = points[i],
x = _points$i[0],
y = _points$i[1],
angle = _points$i[2],
ip = _points$i[3],
_pressure = _points$i[2],
_angle = _points$i[3],
distance = _points$i[4],
clen = _points$i[5];
length += clen; // Size
clen = _points$i[5]; // 1.
// Calculate the size of the current point.
if (pressure) {
if (thinning) {
if (simulatePressure) {
// Simulate pressure by accellerating the reported pressure.
var rp = min(1 - distance / maxSize, 1);
var sp = min(distance / maxSize, 1);
ip = min(1, pp + (rp - pp) * (sp / 2));
} // Compute the size based on the pressure.
var rp = min$1(1 - distance / size, 1);
var sp = min$1(distance / size, 1);
_pressure = min$1(1, pp + (rp - pp) * (sp / 2));
} // Compute the size based on the pressure and thinning.
size = clamp(minSize + ip * (maxSize - minSize), minSize, maxSize);
} else {
size = maxSize;
} // Handle line start
r = (thinning > 0 ? lerp(size - size * thinning, size, clamp(_pressure, 0, 1)) : lerp(size, size + size * thinning, clamp(_pressure, 0, 1))) / 2;
} // 2.
// Draw a cap once we've reached the minimum length.
if (!started && length > size / 2) {
var _points$2 = points[0],
sx = _points$2[0],
sy = _points$2[1];
if (_short) {
if (clen < size / 2) {
continue;
} // The first point after we've reached the minimum length.
for (var t = 0, step = 0.25; t <= 1; t += step) {
m0 = projectPoint(sx, sy, angle + TAU + t * PI, size - 1);
leftPts.push(m0);
m1 = projectPoint(sx, sy, angle - TAU + t * -PI, size - 1);
rightPts.push(m1);
}
started = true;
continue;
} // 3. Shape
_short = false; // Draw a cap at the first point angled toward the current point.
var _first = points[0];
p0 = projectPoint(x, y, angle - TAU, size); // left
for (var _t = 0, _step = 0.1; _t <= 1; _t += _step) {
tl = projectPoint(_first, _angle + TAU + _t * PI$1, r - 1);
leftPts.push(tl);
}
p1 = projectPoint(x, y, angle + TAU, size); // right
tr = projectPoint(_first, _angle + TAU, r - 1);
rightPts.push(tr);
} // 3.
// Add points for the current point.
var delta = angleDelta(pa, angle); // Handle sharp corners differently
if (i === points.length - 1 || abs(delta) > PI * 0.75 && length > size) {
var _getPointBetween = getPointBetween(px, py, x, y, 0.5),
mx = _getPointBetween[0],
my = _getPointBetween[1];
for (var _t = 0, _step = 0.25; _t <= 1; _t += _step) {
m0 = projectPoint(mx, my, pa - TAU + _t * PI, size - 1);
leftPts.push(m0);
m1 = projectPoint(mx, my, pa + TAU + _t * -PI, size - 1);
rightPts.push(m1);
if (i === len - 1) {
// The last point in the line.
// Add points for an end cap.
for (var _t2 = 0, _step2 = 0.1; _t2 <= 1; _t2 += _step2) {
tr = projectPoint([x, y], _angle + TAU - _t2 * PI$1, r - 1);
rightPts.push(tr);
}
t0 = m0;
t1 = m1;
} else {
// Project sideways
d0 = getDistance(p0[0], p0[1], t0[0], t0[1]);
// Find the delta between the current and previous angle.
var delta = getAngleDelta(prev[3], _angle);
if (d0 > smooth) {
leftPts.push(m0);
m0 = getPointBetween(t0[0], t0[1], p0[0], p0[1], 0.5);
t0 = p0;
}
if (abs(delta) > SHARP && clen > r) {
// A sharp corner.
// Project points (left and right) for a cap.
var mid = getPointBetween(prev, [x, y], 0.5);
d1 = getDistance(p1[0], p1[1], t1[0], t1[1]);
for (var _t3 = 0, _step3 = 0.25; _t3 <= 1; _t3 += _step3) {
tl = projectPoint(mid, pa - TAU + _t3 * PI$1, r - 1);
tr = projectPoint(mid, pa + TAU + _t3 * -PI$1, r - 1);
leftPts.push(tl);
rightPts.push(tr);
}
} else {
// A regular point.
// Add projected points left and right.
pl = projectPoint([x, y], _angle - TAU, r);
pr = projectPoint([x, y], _angle + TAU, r); // Add projected point if far enough away from last left point
if (d1 > smooth) {
rightPts.push(m1);
m1 = getPointBetween(t1[0], t1[1], p1[0], p1[1], 0.5);
t1 = p1;
if (getDistance(pl, tl) > minDist) {
leftPts.push(getPointBetween(tl, pl, 0.5));
tl = pl;
} // Add point if far enough away from last right point
if (getDistance(pr, tr) > minDist) {
rightPts.push(getPointBetween(tr, pr, 0.5));
tr = pr;
}
}
pp = _pressure;
}
pp = ip;
}

@@ -370,18 +310,14 @@

/**
* ## clipPath
* @description Returns a clipped polygon of the provided points.
* @param points An array of points (as number[]), the output of getStrokeOutlinePoints.
*/
function clipPath(points) {
return polygonClipping.union([points]);
}
/**
* ## getPath
* @description Returns a pressure sensitive stroke SVG data
* ## getStroke
* @description Returns a stroke as an array of points.
* @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional.
* @param options An (optional) object with options.
* @param options.size The base size (diameter) of the stroke.
* @param options.thinning The effect of pressure on the stroke's size.
* @param options.smoothing How much to soften the stroke's edges.
* @param options.streamline How much to streamline the stroke.
* @param options.simulatePressure Whether to simulate pressure based on velocity.
*/
function getPath(points, options) {
function getStroke(points, options) {
if (options === void 0) {

@@ -391,69 +327,8 @@ options = {};

if (points.length === 0) {
return '';
}
var _options4 = options,
_options4$clip = _options4.clip,
clip = _options4$clip === void 0 ? true : _options4$clip,
_options4$maxSize = _options4.maxSize,
maxSize = _options4$maxSize === void 0 ? 8 : _options4$maxSize;
var ps = getStrokePoints(points, options),
totalLength = ps[ps.length - 1][5],
pts = totalLength < maxSize ? getShortStrokeOutlinePoints(ps, options) : getStrokeOutlinePoints(ps, options),
d = []; // If the length is too short, just draw a dot.
// If we're clipping the path, then find the polygon and add its faces.
if (clip) {
var poly = clipPath(pts);
for (var _iterator = _createForOfIteratorHelperLoose(poly), _step2; !(_step2 = _iterator()).done;) {
var face = _step2.value;
for (var _iterator2 = _createForOfIteratorHelperLoose(face), _step3; !(_step3 = _iterator2()).done;) {
var verts = _step3.value;
var v0 = verts[0];
var v1 = verts[1];
verts.push(v0);
d.push("M " + v0[0] + " " + v0[1]);
for (var i = 1; i < verts.length; i++) {
var _getPointBetween2 = getPointBetween(v0[0], v0[1], v1[0], v1[1], 0.5),
mpx = _getPointBetween2[0],
mpy = _getPointBetween2[1];
d.push(" Q " + v0[0] + "," + v0[1] + " " + mpx + "," + mpy);
v0 = v1;
v1 = verts[i + 1];
}
}
}
} else {
// If we're not clipping the path, just trace it.
var _v = pts[0];
var _v2 = pts[1];
pts.push(_v);
d.push("M " + _v[0] + " " + _v[1]);
for (var _i = 1; _i < pts.length; _i++) {
var _getPointBetween3 = getPointBetween(_v[0], _v[1], _v2[0], _v2[1], 0.5),
_mpx = _getPointBetween3[0],
_mpy = _getPointBetween3[1];
d.push("Q " + _v[0] + "," + _v[1] + " " + _mpx + "," + _mpy);
_v = _v2;
_v2 = pts[_i + 1];
}
}
d.push('Z');
return d.join(' ');
return getStrokeOutlinePoints(getStrokePoints(points, options.streamline), options);
}
exports.clipPath = clipPath;
exports.default = getPath;
exports.getShortStrokeOutlinePoints = getShortStrokeOutlinePoints;
exports.default = getStroke;
exports.getStrokeOutlinePoints = getStrokeOutlinePoints;
exports.getStrokePoints = getStrokePoints;
exports.lerpAngles = lerpAngles;
//# sourceMappingURL=perfect-freehand.cjs.development.js.map

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

"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var r,t=(r=require("polygon-clipping"))&&"object"==typeof r&&"default"in r?r.default:r;function e(r,t){(null==t||t>r.length)&&(t=r.length);for(var e=0,n=new Array(t);e<t;e++)n[e]=r[e];return n}function n(r,t){var n;if("undefined"==typeof Symbol||null==r[Symbol.iterator]){if(Array.isArray(r)||(n=function(r,t){if(r){if("string"==typeof r)return e(r,void 0);var n=Object.prototype.toString.call(r).slice(8,-1);return"Object"===n&&r.constructor&&(n=r.constructor.name),"Map"===n||"Set"===n?Array.from(r):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?e(r,void 0):void 0}}(r))||t&&r&&"number"==typeof r.length){n&&(r=n);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(n=r[Symbol.iterator]()).next.bind(n)}var o=Math.abs,i=Math.hypot,u=Math.cos,a=Math.max,s=Math.min,v=Math.sin,f=Math.atan2,l=Math.PI,p=l/2,h=2*l;function c(r,t,e,n){return[u(e)*n+r,v(e)*n+t]}function d(r,t){var e=(t-r)%h;return 2*e%h-e}function m(r,t,e){return r+d(r,t)*e}function g(r,t,e,n,o){return void 0===o&&(o=.5),[r+(e-r)*o,t+(n-t)*o]}function y(r,t,e,n){return f(n-t,e-r)}function b(r,t,e,n){return i(n-t,e-r)}function S(r,t,e){return a(t,s(e,r))}function x(r,t){void 0===t&&(t={});var e,n,o,i=t.streamline,u=void 0===i?.5:i,a=function(r){return Array.isArray(r[0])?r.map((function(r){var t=r[2];return[r[0],r[1],void 0===t?.5:t]})):r.map((function(r){var t=r.pressure;return[r.x,r.y,void 0===t?.5:t]}))}(r),s=0,v=.01,f=a.length,l=[].concat(a[0],[0,0,0]),p=[l];if(0===f)return[];for(var h=1;h<f;h++){var c=a[h],d=c[2],g=l[0],S=l[1];v=b(e=g+(c[0]-g)*(1-u),n=S+(c[1]-S)*(1-u),g,S),o=y(g,S,e,n),v<1&&(o=m(l[2],o,.5)),p.push(l=[e,n,o,d,v,s+=v])}return p.length>1&&(p[0][2]=p[1][2]),p}function M(r,t){void 0===t&&(t={});var e=t.minSize,n=void 0===e?2.5:e,o=t.maxSize,i=void 0===o?8:o,u=r.length;if(0===u)return[];for(var a=r[0],s=a[0],v=a[1],f=r[u-1],h=f[0],d=f[1],m=[],g=[],b=S(n+(i-n)*(r[u-1][3]||.5),n,i),x=s===h?0:y(s,v,h,d),M=0;M<=1;M+=.1)m.push(c(h,d,x+p-M*l,b-1)),g.push(c(s,v,x+p+M*l,b-1));return m.concat(g.reverse())}function A(r,t){void 0===t&&(t={});var e=t.simulatePressure,n=void 0===e||e,i=t.pressure,u=void 0===i||i,a=t.minSize,v=void 0===a?2.5:a,f=t.maxSize,h=void 0===f?8:f,m=t.smooth,y=void 0===m?8:m,x=r.length,M=r[0],A=r[0],P=M,j=A,z=M,O=M,I=0,k=.5,w=!1,Q=0,_=[M],q=[M];if(0===x)return[];for(var C=1;C<x;C++){var E=r[C-1],T=E[0],U=E[1],Z=E[2],$=r[C],B=$[0],D=$[1],F=$[2],G=$[3],H=$[4];if(Q+=$[5],u){if(n){var J=s(1-H/h,1),K=s(H/h,1);G=s(1,k+K/2*(J-k))}I=S(v+G*(h-v),v,h)}else I=h;if(!w&&Q>I/2){for(var L=r[0],N=L[0],R=L[1],V=0;V<=1;V+=.25)z=c(N,R,F+p+V*l,I-1),_.push(z),O=c(N,R,F-p+V*-l,I-1),q.push(O);w=!0}else{M=c(B,D,F-p,I),A=c(B,D,F+p,I);var W=d(Z,F);if(C===r.length-1||o(W)>.75*l&&Q>I){for(var X=g(T,U,B,D,.5),Y=X[0],rr=X[1],tr=0;tr<=1;tr+=.25)z=c(Y,rr,Z-p+tr*l,I-1),_.push(z),O=c(Y,rr,Z+p+tr*-l,I-1),q.push(O);P=z,j=O}else b(M[0],M[1],P[0],P[1])>y&&(_.push(z),z=g(P[0],P[1],M[0],M[1],.5),P=M),b(A[0],A[1],j[0],j[1])>y&&(q.push(O),O=g(j[0],j[1],A[0],A[1],.5),j=A);k=G}}return _.concat(q.reverse())}function P(r){return t.union([r])}exports.clipPath=P,exports.default=function(r,t){if(void 0===t&&(t={}),0===r.length)return"";var e=t.clip,o=void 0===e||e,i=t.maxSize,u=void 0===i?8:i,a=x(r,t),s=a[a.length-1][5]<u?M(a,t):A(a,t),v=[];if(o)for(var f,l=n(P(s));!(f=l()).done;)for(var p,h=n(f.value);!(p=h()).done;){var c=p.value,d=c[0],m=c[1];c.push(d),v.push("M "+d[0]+" "+d[1]);for(var y=1;y<c.length;y++){var b=g(d[0],d[1],m[0],m[1],.5);v.push(" Q "+d[0]+","+d[1]+" "+b[0]+","+b[1]),d=m,m=c[y+1]}}else{var S=s[0],j=s[1];s.push(S),v.push("M "+S[0]+" "+S[1]);for(var z=1;z<s.length;z++){var O=g(S[0],S[1],j[0],j[1],.5);v.push("Q "+S[0]+","+S[1]+" "+O[0]+","+O[1]),S=j,j=s[z+1]}}return v.push("Z"),v.join(" ")},exports.getShortStrokeOutlinePoints=M,exports.getStrokeOutlinePoints=A,exports.getStrokePoints=x,exports.lerpAngles=m;
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var r=Math.hypot,t=Math.cos,n=Math.max,e=Math.min,i=Math.sin,u=Math.atan2,o=Math.PI;function a(r,t,n){return r*(1-n)+t*n}function s(r,n,e){return[t(n)*e+r[0],i(n)*e+r[1]]}function f(r,t,n){return void 0===n&&(n=.5),[r[0]+(t[0]-r[0])*n,r[1]+(t[1]-r[1])*n]}function v(r,t){return u(t[1]-r[1],t[0]-r[0])}function h(t,n){return r(n[1]-t[1],n[0]-t[0])}function c(r,t,i){return n(t,e(i,r))}var p=Math.abs,d=Math.min,M=Math.PI,l=M/2,m=.7*M;function g(r,t){void 0===t&&(t=.5);var n,e,i,u=function(r){return Array.isArray(r[0])?r.map((function(r){var t=r[2];return[r[0],r[1],void 0===t?.5:t]})):r.map((function(r){var t=r.pressure;return[r.x,r.y,void 0===t?.5:t]}))}(r),o=0,a=.01,s=u.length,f=[].concat(u[0],[0,0,0]),c=[f];if(0===s)return[];for(var p=1;p<s;p++){var d=u[p],M=d[2],l=f[0],m=f[1];a=h([n=l+(d[0]-l)*(1-t),e=m+(d[1]-m)*(1-t)],f),i=v(f,[n,e]),c.push(f=[n,e,M,i,a,o+=a])}return c}function x(r,t){void 0===t&&(t={});var n=t.size,e=void 0===n?8:n,i=t.thinning,u=void 0===i?.5:i,g=t.smoothing,x=t.simulatePressure,P=void 0===x||x,y=r.length,b=e*(void 0===g?.5:g),k=[],A=[],I=r[0],O=r[0],S=I,_=O,j=0,z=e/2,q=!0;if(0===y)return[];if(1===y||r[y-1][5]<e/2){var w=r[0],B=r[y-1],C=v(w,B);if(u){var D=B[3]?c(B[3],0,1):.5;z=(u>0?a(e-e*u,e,c(D,0,1)):a(e,e+e*u,c(D,0,1)))/2}for(var E=0;E<=1;E+=.1)S=s(w,C+M+l-E*M,z-1),_=s(B,C+l-E*M,z-1),k.push(S),A.push(_);return k.concat(A)}for(var F=1;F<y;F++){var G=r[F-1],H=G[3],J=r[F],K=J[0],L=J[1],N=J[2],Q=J[3],R=J[4],T=J[5];if(u){if(P){var U=d(1-R/e,1),V=d(R/e,1);N=d(1,j+V/2*(U-j))}z=(u>0?a(e-e*u,e,c(N,0,1)):a(e,e+e*u,c(N,0,1)))/2}if(q){if(T<e/2)continue;q=!1;for(var W=r[0],X=0;X<=1;X+=.1)S=s(W,Q+l+X*M,z-1),k.push(S);_=s(W,Q+l,z-1),A.push(_)}if(F===y-1)for(var Y=0;Y<=1;Y+=.1)_=s([K,L],Q+l-Y*M,z-1),A.push(_);else{var Z=function(r,t){var n=2*o,e=(t-r)%n;return 2*e%n-e}(G[3],Q);if(p(Z)>m&&T>z)for(var $=f(G,[K,L],.5),rr=0;rr<=1;rr+=.25)S=s($,H-l+rr*M,z-1),_=s($,H+l+rr*-M,z-1),k.push(S),A.push(_);else I=s([K,L],Q-l,z),O=s([K,L],Q+l,z),h(I,S)>b&&(k.push(f(S,I,.5)),S=I),h(O,_)>b&&(A.push(f(_,O,.5)),_=O);j=N}}return k.concat(A.reverse())}exports.default=function(r,t){return void 0===t&&(t={}),x(g(r,t.streamline),t)},exports.getStrokeOutlinePoints=x,exports.getStrokePoints=g;
//# sourceMappingURL=perfect-freehand.cjs.production.min.js.map

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

import polygonClipping from 'polygon-clipping';
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);
}
/* --------------------- Helpers -------------------- */
var abs = Math.abs,
hypot = Math.hypot,
var hypot = Math.hypot,
cos = Math.cos,

@@ -54,12 +7,28 @@ max = Math.max,

atan2 = Math.atan2,
PI = Math.PI,
TAU = PI / 2,
PI2 = PI * 2;
PI = Math.PI;
/**
* Linear interpolation betwen two numbers.
* @param y1
* @param y2
* @param mu
*/
function projectPoint(x0, y0, a, d) {
return [cos(a) * d + x0, sin(a) * d + y0];
function lerp(y1, y2, mu) {
return y1 * (1 - mu) + y2 * mu;
}
/**
* Project a point in a direction, by an angle.
* @param x0
* @param y0
* @param a
* @param d
* @returns
*/
function projectPoint(p0, a, d) {
return [cos(a) * d + p0[0], sin(a) * d + p0[1]];
}
function shortAngleDist(a0, a1) {
var max = PI2;
var max = PI * 2;
var da = (a1 - a0) % max;

@@ -69,11 +38,6 @@ return 2 * da % max - da;

function lerpAngles(a0, a1, t) {
return a0 + shortAngleDist(a0, a1) * t;
}
function angleDelta(a0, a1) {
function getAngleDelta(a0, a1) {
return shortAngleDist(a0, a1);
}
function getPointBetween(x0, y0, x1, y1, d) {
function getPointBetween(p0, p1, d) {
if (d === void 0) {

@@ -83,17 +47,13 @@ d = 0.5;

return [x0 + (x1 - x0) * d, y0 + (y1 - y0) * d];
return [p0[0] + (p1[0] - p0[0]) * d, p0[1] + (p1[1] - p0[1]) * d];
}
function getAngle(x0, y0, x1, y1) {
return atan2(y1 - y0, x1 - x0);
function getAngle(p0, p1) {
return atan2(p1[1] - p0[1], p1[0] - p0[0]);
}
function getDistance(x0, y0, x1, y1) {
return hypot(y1 - y0, x1 - x0);
function getDistance(p0, p1) {
return hypot(p1[1] - p0[1], p1[0] - p0[0]);
}
function clamp(n, a, b) {
return max(a, min(b, n));
}
function toPointsArray(points) {

@@ -118,4 +78,8 @@ if (Array.isArray(points[0])) {

}
/* --------------------- Methods -------------------- */
var abs = Math.abs,
min$1 = Math.min,
PI$1 = Math.PI,
TAU = PI$1 / 2,
SHARP = PI$1 * 0.7;
/**

@@ -125,14 +89,10 @@ * ## getStrokePoints

* @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional.
* @param options An (optional) object with options.
* @param streamline How much to streamline the stroke.
*/
function getStrokePoints(points, options) {
if (options === void 0) {
options = {};
function getStrokePoints(points, streamline) {
if (streamline === void 0) {
streamline = 0.5;
}
var _options = options,
_options$streamline = _options.streamline,
streamline = _options$streamline === void 0 ? 0.5 : _options$streamline;
var aPoints = toPointsArray(points);

@@ -142,3 +102,3 @@ var x,

angle,
length = 0,
totalLength = 0,
distance = 0.01,

@@ -165,15 +125,9 @@ len = aPoints.length,

distance = getDistance(x, y, px, py); // Angle
distance = getDistance([x, y], prev); // Angle
angle = getAngle(px, py, x, y); // If distance is very short, blend the angles
angle = getAngle(prev, [x, y]); // Increment total length
if (distance < 1) angle = lerpAngles(prev[2], angle, 0.5);
length += distance;
prev = [x, y, angle, ip, distance, length];
totalLength += distance;
prev = [x, y, ip, angle, distance, totalLength];
pts.push(prev);
} // Assign second angle to first point
if (pts.length > 1) {
pts[0][2] = pts[1][2];
}

@@ -184,9 +138,13 @@

/**
* ## getShortStrokeOutlinePoints
* @description Draw an outline around a short stroke.
* ## getStrokeOutlinePoints
* @description Get an array of points (as `[x, y]`) representing the outline of a stroke.
* @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional.
* @param options An (optional) object with options.
* @param options.size The base size (diameter) of the stroke.
* @param options.thinning The effect of pressure on the stroke's size.
* @param options.smoothing How much to soften the stroke's edges.
* @param options.simulatePressure Whether to simulate pressure based on velocity.
*/
function getShortStrokeOutlinePoints(points, options) {
function getStrokeOutlinePoints(points, options) {
if (options === void 0) {

@@ -196,163 +154,147 @@ options = {};

var _options2 = options,
_options2$minSize = _options2.minSize,
minSize = _options2$minSize === void 0 ? 2.5 : _options2$minSize,
_options2$maxSize = _options2.maxSize,
maxSize = _options2$maxSize === void 0 ? 8 : _options2$maxSize;
var len = points.length; // Can't draw an outline without any points
var _options = options,
_options$size = _options.size,
size = _options$size === void 0 ? 8 : _options$size,
_options$thinning = _options.thinning,
thinning = _options$thinning === void 0 ? 0.5 : _options$thinning,
_options$smoothing = _options.smoothing,
smoothing = _options$smoothing === void 0 ? 0.5 : _options$smoothing,
_options$simulatePres = _options.simulatePressure,
simulatePressure = _options$simulatePres === void 0 ? true : _options$simulatePres;
var len = points.length,
totalLength = points[len - 1][5],
// The total length of the line
minDist = size * smoothing,
// The minimum distance for measurements
leftPts = [],
// Our collected left and right points
rightPts = [];
var pl = points[0],
// Previous left and right points
pr = points[0],
tl = pl,
// Points to test distance from
tr = pr,
pp = 0,
// Previous (maybe simulated) pressure
r = size / 2,
// The current point radius
_short = true; // Whether the line is drawn far enough
// We can't do anything with an empty array.
if (len === 0) {
return [];
}
} // If the point is only one point long, draw two caps at either end.
var _points$ = points[0],
x0 = _points$[0],
y0 = _points$[1],
_points = points[len - 1],
x1 = _points[0],
y1 = _points[1],
p = points[len - 1][3],
leftPts = [],
rightPts = [],
size = clamp(minSize + (maxSize - minSize) * (p ? p : 0.5), minSize, maxSize),
angle = x0 === x1 ? 0 : getAngle(x0, y0, x1, y1);
for (var t = 0, step = 0.1; t <= 1; t += step) {
leftPts.push(projectPoint(x1, y1, angle + TAU - t * PI, size - 1));
rightPts.push(projectPoint(x0, y0, angle + TAU + t * PI, size - 1));
}
if (len === 1 || totalLength < size / 2) {
var first = points[0],
last = points[len - 1],
angle = getAngle(first, last);
return leftPts.concat(rightPts.reverse());
}
/**
* ## getStrokeOutlinePoints
* @description Get an array of points (as `[x, y]`) representing the outline of a stroke.
* @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional.
* @param options An (optional) object with options.
*/
if (thinning) {
var pressure = last[3] ? clamp(last[3], 0, 1) : 0.5;
r = (thinning > 0 ? lerp(size - size * thinning, size, clamp(pressure, 0, 1)) : lerp(size, size + size * thinning, clamp(pressure, 0, 1))) / 2;
}
function getStrokeOutlinePoints(points, options) {
if (options === void 0) {
options = {};
}
for (var t = 0, step = 0.1; t <= 1; t += step) {
tl = projectPoint(first, angle + PI$1 + TAU - t * PI$1, r - 1);
tr = projectPoint(last, angle + TAU - t * PI$1, r - 1);
leftPts.push(tl);
rightPts.push(tr);
}
var _options3 = options,
_options3$simulatePre = _options3.simulatePressure,
simulatePressure = _options3$simulatePre === void 0 ? true : _options3$simulatePre,
_options3$pressure = _options3.pressure,
pressure = _options3$pressure === void 0 ? true : _options3$pressure,
_options3$minSize = _options3.minSize,
minSize = _options3$minSize === void 0 ? 2.5 : _options3$minSize,
_options3$maxSize = _options3.maxSize,
maxSize = _options3$maxSize === void 0 ? 8 : _options3$maxSize,
_options3$smooth = _options3.smooth,
smooth = _options3$smooth === void 0 ? 8 : _options3$smooth;
var len = points.length,
p0 = points[0],
p1 = points[0],
t0 = p0,
t1 = p1,
m0 = p0,
m1 = p0,
size = 0,
pp = 0.5,
started = false,
length = 0,
leftPts = [p0],
rightPts = [p0],
d0,
d1;
return leftPts.concat(rightPts);
} // For a point with more than one point, create an outline shape.
if (len === 0) {
return [];
} // Use the points to create an outline shape, where the width
// of the shape is determined by the pressure at each point.
for (var i = 1; i < len; i++) {
var _points2 = points[i - 1],
px = _points2[0],
py = _points2[1],
pa = _points2[2];
var prev = points[i - 1],
pa = prev[3];
var _points$i = points[i],
x = _points$i[0],
y = _points$i[1],
angle = _points$i[2],
ip = _points$i[3],
_pressure = _points$i[2],
_angle = _points$i[3],
distance = _points$i[4],
clen = _points$i[5];
length += clen; // Size
clen = _points$i[5]; // 1.
// Calculate the size of the current point.
if (pressure) {
if (thinning) {
if (simulatePressure) {
// Simulate pressure by accellerating the reported pressure.
var rp = min(1 - distance / maxSize, 1);
var sp = min(distance / maxSize, 1);
ip = min(1, pp + (rp - pp) * (sp / 2));
} // Compute the size based on the pressure.
var rp = min$1(1 - distance / size, 1);
var sp = min$1(distance / size, 1);
_pressure = min$1(1, pp + (rp - pp) * (sp / 2));
} // Compute the size based on the pressure and thinning.
size = clamp(minSize + ip * (maxSize - minSize), minSize, maxSize);
} else {
size = maxSize;
} // Handle line start
r = (thinning > 0 ? lerp(size - size * thinning, size, clamp(_pressure, 0, 1)) : lerp(size, size + size * thinning, clamp(_pressure, 0, 1))) / 2;
} // 2.
// Draw a cap once we've reached the minimum length.
if (!started && length > size / 2) {
var _points$2 = points[0],
sx = _points$2[0],
sy = _points$2[1];
if (_short) {
if (clen < size / 2) {
continue;
} // The first point after we've reached the minimum length.
for (var t = 0, step = 0.25; t <= 1; t += step) {
m0 = projectPoint(sx, sy, angle + TAU + t * PI, size - 1);
leftPts.push(m0);
m1 = projectPoint(sx, sy, angle - TAU + t * -PI, size - 1);
rightPts.push(m1);
}
started = true;
continue;
} // 3. Shape
_short = false; // Draw a cap at the first point angled toward the current point.
var _first = points[0];
p0 = projectPoint(x, y, angle - TAU, size); // left
for (var _t = 0, _step = 0.1; _t <= 1; _t += _step) {
tl = projectPoint(_first, _angle + TAU + _t * PI$1, r - 1);
leftPts.push(tl);
}
p1 = projectPoint(x, y, angle + TAU, size); // right
tr = projectPoint(_first, _angle + TAU, r - 1);
rightPts.push(tr);
} // 3.
// Add points for the current point.
var delta = angleDelta(pa, angle); // Handle sharp corners differently
if (i === points.length - 1 || abs(delta) > PI * 0.75 && length > size) {
var _getPointBetween = getPointBetween(px, py, x, y, 0.5),
mx = _getPointBetween[0],
my = _getPointBetween[1];
for (var _t = 0, _step = 0.25; _t <= 1; _t += _step) {
m0 = projectPoint(mx, my, pa - TAU + _t * PI, size - 1);
leftPts.push(m0);
m1 = projectPoint(mx, my, pa + TAU + _t * -PI, size - 1);
rightPts.push(m1);
if (i === len - 1) {
// The last point in the line.
// Add points for an end cap.
for (var _t2 = 0, _step2 = 0.1; _t2 <= 1; _t2 += _step2) {
tr = projectPoint([x, y], _angle + TAU - _t2 * PI$1, r - 1);
rightPts.push(tr);
}
t0 = m0;
t1 = m1;
} else {
// Project sideways
d0 = getDistance(p0[0], p0[1], t0[0], t0[1]);
// Find the delta between the current and previous angle.
var delta = getAngleDelta(prev[3], _angle);
if (d0 > smooth) {
leftPts.push(m0);
m0 = getPointBetween(t0[0], t0[1], p0[0], p0[1], 0.5);
t0 = p0;
}
if (abs(delta) > SHARP && clen > r) {
// A sharp corner.
// Project points (left and right) for a cap.
var mid = getPointBetween(prev, [x, y], 0.5);
d1 = getDistance(p1[0], p1[1], t1[0], t1[1]);
for (var _t3 = 0, _step3 = 0.25; _t3 <= 1; _t3 += _step3) {
tl = projectPoint(mid, pa - TAU + _t3 * PI$1, r - 1);
tr = projectPoint(mid, pa + TAU + _t3 * -PI$1, r - 1);
leftPts.push(tl);
rightPts.push(tr);
}
} else {
// A regular point.
// Add projected points left and right.
pl = projectPoint([x, y], _angle - TAU, r);
pr = projectPoint([x, y], _angle + TAU, r); // Add projected point if far enough away from last left point
if (d1 > smooth) {
rightPts.push(m1);
m1 = getPointBetween(t1[0], t1[1], p1[0], p1[1], 0.5);
t1 = p1;
if (getDistance(pl, tl) > minDist) {
leftPts.push(getPointBetween(tl, pl, 0.5));
tl = pl;
} // Add point if far enough away from last right point
if (getDistance(pr, tr) > minDist) {
rightPts.push(getPointBetween(tr, pr, 0.5));
tr = pr;
}
}
pp = _pressure;
}
pp = ip;
}

@@ -363,18 +305,14 @@

/**
* ## clipPath
* @description Returns a clipped polygon of the provided points.
* @param points An array of points (as number[]), the output of getStrokeOutlinePoints.
*/
function clipPath(points) {
return polygonClipping.union([points]);
}
/**
* ## getPath
* @description Returns a pressure sensitive stroke SVG data
* ## getStroke
* @description Returns a stroke as an array of points.
* @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional.
* @param options An (optional) object with options.
* @param options.size The base size (diameter) of the stroke.
* @param options.thinning The effect of pressure on the stroke's size.
* @param options.smoothing How much to soften the stroke's edges.
* @param options.streamline How much to streamline the stroke.
* @param options.simulatePressure Whether to simulate pressure based on velocity.
*/
function getPath(points, options) {
function getStroke(points, options) {
if (options === void 0) {

@@ -384,65 +322,7 @@ options = {};

if (points.length === 0) {
return '';
}
var _options4 = options,
_options4$clip = _options4.clip,
clip = _options4$clip === void 0 ? true : _options4$clip,
_options4$maxSize = _options4.maxSize,
maxSize = _options4$maxSize === void 0 ? 8 : _options4$maxSize;
var ps = getStrokePoints(points, options),
totalLength = ps[ps.length - 1][5],
pts = totalLength < maxSize ? getShortStrokeOutlinePoints(ps, options) : getStrokeOutlinePoints(ps, options),
d = []; // If the length is too short, just draw a dot.
// If we're clipping the path, then find the polygon and add its faces.
if (clip) {
var poly = clipPath(pts);
for (var _iterator = _createForOfIteratorHelperLoose(poly), _step2; !(_step2 = _iterator()).done;) {
var face = _step2.value;
for (var _iterator2 = _createForOfIteratorHelperLoose(face), _step3; !(_step3 = _iterator2()).done;) {
var verts = _step3.value;
var v0 = verts[0];
var v1 = verts[1];
verts.push(v0);
d.push("M " + v0[0] + " " + v0[1]);
for (var i = 1; i < verts.length; i++) {
var _getPointBetween2 = getPointBetween(v0[0], v0[1], v1[0], v1[1], 0.5),
mpx = _getPointBetween2[0],
mpy = _getPointBetween2[1];
d.push(" Q " + v0[0] + "," + v0[1] + " " + mpx + "," + mpy);
v0 = v1;
v1 = verts[i + 1];
}
}
}
} else {
// If we're not clipping the path, just trace it.
var _v = pts[0];
var _v2 = pts[1];
pts.push(_v);
d.push("M " + _v[0] + " " + _v[1]);
for (var _i = 1; _i < pts.length; _i++) {
var _getPointBetween3 = getPointBetween(_v[0], _v[1], _v2[0], _v2[1], 0.5),
_mpx = _getPointBetween3[0],
_mpy = _getPointBetween3[1];
d.push("Q " + _v[0] + "," + _v[1] + " " + _mpx + "," + _mpy);
_v = _v2;
_v2 = pts[_i + 1];
}
}
d.push('Z');
return d.join(' ');
return getStrokeOutlinePoints(getStrokePoints(points, options.streamline), options);
}
export default getPath;
export { clipPath, getShortStrokeOutlinePoints, getStrokeOutlinePoints, getStrokePoints, lerpAngles };
export default getStroke;
export { getStrokeOutlinePoints, getStrokePoints };
//# sourceMappingURL=perfect-freehand.esm.js.map
{
"version": "0.2.5",
"version": "0.3.0",
"license": "MIT",

@@ -55,5 +55,3 @@ "main": "dist/index.js",

},
"dependencies": {
"polygon-clipping": "^0.15.2"
}
"dependencies": {}
}

@@ -23,16 +23,15 @@ # Perfect Freehand

The library exports a default function, `getPath`, that accepts an array of points and an (optional) options object and returns SVG path data for a stroke.
The library exports a default function, `getStroke`, that:
The array of points may be _either_ an array of number pairs representing the point's x, y, and (optionally) pressure...
- accepts an array of points and an (optional) options object
- returns a stroke as an array of points formatted as `[x, y]`
```js
import getPath from 'perfect-freehand'
import getStroke from 'perfect-freehand'
```
const path = getPath([
[0, 0],
[10, 5],
[20, 8],
])
You may format your input points _either_ as an array or an object as shown below. In both cases, the pressure value is optional.
const path = getPath([
```js
getStroke([
[0, 0, 0],

@@ -42,15 +41,5 @@ [10, 5, 0.5],

])
```
...or an array of objects with `x`, `y`, and (optionally) `pressure` properties.
```
getPath([
{ x: 0, y: 0 },
{ x: 10, y: 5 },
{ x: 20, y: 8 },
])
getPath([
{ x: 0, y: 0, pressure: 0, },
getStroke([
{ x: 0, y: 0, pressure: 0 },
{ x: 10, y: 5, pressure: 0.5 },

@@ -63,31 +52,61 @@ { x: 20, y: 8, pressure: 0.3 },

The options object is optional, as are its properties.
The options object is optional, as are each of its properties.
| Property | Type | Default | Description |
| ------------------ | ------- | ------- | ---------------------------------------------------- |
| `minSize` | number | 2.5 | The thinnest size of the stroke. |
| `maxSize` | number | 8 | The thickest size of the stroke. |
| `simulatePressure` | boolean | true | Whether to simulate pressure based on velocity. |
| `pressure` | boolean | true | Whether to apply pressure. |
| `streamline` | number | .5 | How much to streamline the stroke. |
| `smooth` | number | .5 | How much to soften the stroke's edges. |
| `clip` | boolean | true | Whether to flatten the stroke into a single polygon. |
| Property | Type | Default | Description |
| ------------------ | ------- | ------- | ----------------------------------------------- |
| `size` | number | 8 | The base size (diameter) of the stroke. |
| `thinning` | number | .5 | The effect of pressure on the stroke's size. |
| `smoothing` | number | .5 | How much to soften the stroke's edges. |
| `streamline` | number | .5 | How much to streamline the stroke. |
| `simulatePressure` | boolean | true | Whether to simulate pressure based on velocity. |
```js
getPath(myPoints, {
minSize: 2.5,
maxSize: 8,
getStroke(myPoints, {
size: 8,
thinning: 0.5,
smoothing: 0.5,
streamline: 0.5,
simulatePressure: true,
pressure: true,
streamline: 0.5,
smooth: 0.5,
clip: true,
})
```
## Example
> **Tip:** To create a stroke that gets thinner with pressure instead of thicker, use a negative number for the `thinning` option.
### Rendering
While `getStroke` returns an array of points representing a stroke, it's up to you to decide how you will render the stroke. The library does not export any rendering solutions.
For example, here is a function that takes in a stroke and returns SVG path data. You can use the string returned by this function in two ways. For SVG, you can pass the data into `path` element's [`d` property](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d). For HTML canvas, you can pass the string into the [`Path2D` constructor](https://developer.mozilla.org/en-US/docs/Web/API/Path2D/Path2D#using_svg_paths) and then stroke or fill the path.
```js
import getStroke from 'perfect-freehand'
// Create SVG path data using the points from perfect-freehand.
function getSvgPathFromStroke(stroke) {
const d = []
let [p0, p1] = stroke
d.push(`M ${p0[0]} ${p0[1]} Q`)
for (let i = 1; i < stroke.length; i++) {
const mpx = p0[0] + (p1[0] - p0[0]) / 2
const mpy = p0[1] + (p1[1] - p0[1]) / 2
d.push(`${p0[0]},${p0[1]} ${mpx},${mpy}`)
p0 = p1
p1 = stroke[i + 1]
}
d.push('Z')
return d.join(' ')
}
```
# Example
```jsx
import * as React from 'react'
import getPath from 'perfect-freehand'
import getStroke from 'perfect-freehand'
import getSvgPathFromStroke from './utils' // See "Rendering" section above.

@@ -98,5 +117,7 @@ export default function Example() {

function handlePointerDown(e) {
const point = [e.pageX, e.pageY, e.pressure]
setCurrentMark({
type: e.pointerType,
points: [[e.pageX, e.pageY, e.pressure]],
points: [point],
})

@@ -106,6 +127,8 @@ }

function handlePointerMove(e) {
const point = [e.pageX, e.pageY, e.pressure]
if (e.buttons === 1) {
setCurrentMark({
...currentMark,
points: [...currentMark.points, [e.pageX, e.pageY, e.pressure]],
points: [...currentMark.points, point],
})

@@ -115,2 +138,10 @@ }

const stroke = getStroke(currentMark.points, {
size: 8,
thinning: 0.5,
smoothing: 0.5,
streamline: 0.5,
simulatePressure: currentMark.type !== 'pen',
})
return (

@@ -124,9 +155,3 @@ <svg

>
{currentMark && (
<path
d={getPath(currentMark.points, {
simulatePressure: currentMark.type !== 'pen',
})}
/>
)}
{currentMark && <path d={getSvgPathFromStroke(stroke)} />}
</svg>

@@ -139,6 +164,8 @@ )

## Advanced Usage
# Advanced Usage
For advanced usage, the library also exports smaller functions that `getPath` uses to generate its SVG data. While you can use `getPath`'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.
## Functions
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.
#### `getStrokePoints`

@@ -152,8 +179,36 @@

#### `getShortStrokeOutlinePoints`
## Rendering a Flattened Stroke
Works like `getStrokeOutlinePoints`, but designed to work with short paths.
To render a stroke as a flat polygon, add the `polygon-clipping` package and use (or refer to) the following function.
#### `clipPath`
```js
import getStroke from 'perfect-freehand'
import polygonClipping from 'polygon-clipping'
Accepts a series of points (formatted as `[x, y]`, i.e. the output of `getStrokeOutlinePoints` or `getShortStrokeOutlinePoints`) and returns a polygon (a series of faces) from the stroke.
function getFlatSvgPathFromStroke(stroke) {
const poly = polygonClipping.union([stroke] as any)
const d = []
for (let face of poly) {
for (let pts of face) {
let [p0, p1] = pts
d.push(`M ${p0[0]} ${p0[1]} Q`)
for (let i = 1; i < pts.length; i++) {
const mpx = p0[0] + (p1[0] - p0[0]) / 2
const mpy = p0[1] + (p1[1] - p0[1]) / 2
d.push(`${p0[0]},${p0[1]} ${mpx},${mpy}`)
p0 = p1
p1 = pts[i + 1]
}
d.push('Z')
}
}
return d.join(' ')
}
```

@@ -1,87 +0,17 @@

import polygonClipping from 'polygon-clipping'
import {
toPointsArray,
clamp,
getAngle,
getAngleDelta,
getDistance,
getPointBetween,
projectPoint,
lerp,
} from './utils'
import { StrokeOptions } from './types'
/* --------------------- Helpers -------------------- */
const { abs, hypot, cos, max, min, sin, atan2, PI } = Math,
const { abs, min, PI } = Math,
TAU = PI / 2,
PI2 = PI * 2
SHARP = PI * 0.7
function projectPoint(x0: number, y0: number, a: number, d: number) {
return [cos(a) * d + x0, sin(a) * d + y0]
}
function shortAngleDist(a0: number, a1: number) {
var max = PI2
var da = (a1 - a0) % max
return ((2 * da) % max) - da
}
export function lerpAngles(a0: number, a1: number, t: number) {
return a0 + shortAngleDist(a0, a1) * t
}
function angleDelta(a0: number, a1: number) {
return shortAngleDist(a0, a1)
}
function getPointBetween(
x0: number,
y0: number,
x1: number,
y1: number,
d = 0.5
) {
return [x0 + (x1 - x0) * d, y0 + (y1 - y0) * d]
}
function getAngle(x0: number, y0: number, x1: number, y1: number) {
return atan2(y1 - y0, x1 - x0)
}
function getDistance(x0: number, y0: number, x1: number, y1: number) {
return hypot(y1 - y0, x1 - x0)
}
function clamp(n: number, a: number, b: number) {
return max(a, min(b, n))
}
function toPointsArray<
T extends number[],
K extends { x: number; y: number; pressure?: number }
>(points: (T | K)[]): number[][] {
if (Array.isArray(points[0])) {
return (points as number[][]).map(([x, y, pressure = 0.5]) => [
x,
y,
pressure,
])
} else {
return (points as {
x: number
y: number
pressure?: number
}[]).map(({ x, y, pressure = 0.5 }) => [x, y, pressure])
}
}
/* ---------------------- Types --------------------- */
export interface StrokePointsOptions {
streamline?: number
}
export interface StrokeOutlineOptions extends StrokePointsOptions {
simulatePressure?: boolean
pressure?: boolean
minSize?: number
maxSize?: number
smooth?: number
}
export interface StrokeOptions extends StrokeOutlineOptions {
clip?: boolean
}
/* --------------------- Methods -------------------- */
/**

@@ -91,3 +21,3 @@ * ## getStrokePoints

* @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional.
* @param options An (optional) object with options.
* @param streamline How much to streamline the stroke.
*/

@@ -97,8 +27,3 @@ export function getStrokePoints<

K extends { x: number; y: number; pressure?: number }
>(
points: (T | K)[],
options: StrokePointsOptions = {} as StrokePointsOptions
): number[][] {
const { streamline = 0.5 } = options
>(points: (T | K)[], streamline = 0.5): number[][] {
const aPoints = toPointsArray(points)

@@ -109,3 +34,3 @@

angle: number,
length = 0,
totalLength = 0,
distance = 0.01,

@@ -129,20 +54,15 @@ len = aPoints.length,

// Distance
distance = getDistance(x, y, px, py)
distance = getDistance([x, y], prev)
// Angle
angle = getAngle(px, py, x, y)
angle = getAngle(prev, [x, y])
// If distance is very short, blend the angles
if (distance < 1) angle = lerpAngles(prev[2], angle, 0.5)
// Increment total length
totalLength += distance
length += distance
prev = [x, y, angle, ip, distance, length]
prev = [x, y, ip, angle, distance, totalLength]
pts.push(prev)
}
// Assign second angle to first point
if (pts.length > 1) {
pts[0][2] = pts[1][2]
}
return pts

@@ -152,40 +72,2 @@ }

/**
* ## getShortStrokeOutlinePoints
* @description Draw an outline around a short stroke.
* @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional.
* @param options An (optional) object with options.
*/
export function getShortStrokeOutlinePoints(
points: number[][],
options: StrokeOutlineOptions = {} as StrokeOutlineOptions
) {
const { minSize = 2.5, maxSize = 8 } = options
const len = points.length
// Can't draw an outline without any points
if (len === 0) {
return []
}
const [x0, y0] = points[0],
[x1, y1] = points[len - 1],
p = points[len - 1][3],
leftPts: number[][] = [],
rightPts: number[][] = [],
size = clamp(
minSize + (maxSize - minSize) * (p ? p : 0.5),
minSize,
maxSize
),
angle = x0 === x1 ? 0 : getAngle(x0, y0, x1, y1)
for (let t = 0, step = 0.1; t <= 1; t += step) {
leftPts.push(projectPoint(x1, y1, angle + TAU - t * PI, size - 1))
rightPts.push(projectPoint(x0, y0, angle + TAU + t * PI, size - 1))
}
return leftPts.concat(rightPts.reverse())
}
/**
* ## getStrokeOutlinePoints

@@ -195,31 +77,33 @@ * @description Get an array of points (as `[x, y]`) representing the outline of a stroke.

* @param options An (optional) object with options.
* @param options.size The base size (diameter) of the stroke.
* @param options.thinning The effect of pressure on the stroke's size.
* @param options.smoothing How much to soften the stroke's edges.
* @param options.simulatePressure Whether to simulate pressure based on velocity.
*/
export function getStrokeOutlinePoints(
points: number[][],
options: StrokeOutlineOptions = {} as StrokeOutlineOptions
options: StrokeOptions = {} as StrokeOptions
): number[][] {
const {
size = 8,
thinning = 0.5,
smoothing = 0.5,
simulatePressure = true,
pressure = true,
minSize = 2.5,
maxSize = 8,
smooth = 8,
} = options
let len = points.length,
p0 = points[0],
p1 = points[0],
t0 = p0,
t1 = p1,
m0 = p0,
m1 = p0,
size = 0,
pp = 0.5,
started = false,
length = 0,
leftPts: number[][] = [p0],
rightPts: number[][] = [p0],
d0: number,
d1: number
const len = points.length,
totalLength = points[len - 1][5], // The total length of the line
minDist = size * smoothing, // The minimum distance for measurements
leftPts: number[][] = [], // Our collected left and right points
rightPts: number[][] = []
let pl = points[0], // Previous left and right points
pr = points[0],
tl = pl, // Points to test distance from
tr = pr,
pp = 0, // Previous (maybe simulated) pressure
r = size / 2, // The current point radius
short = true // Whether the line is drawn far enough
// We can't do anything with an empty array.
if (len === 0) {

@@ -229,77 +113,122 @@ return []

// Use the points to create an outline shape, where the width
// of the shape is determined by the pressure at each point.
// If the point is only one point long, draw two caps at either end.
if (len === 1 || totalLength < size / 2) {
let first = points[0],
last = points[len - 1],
angle = getAngle(first, last)
if (thinning) {
const pressure = last[3] ? clamp(last[3], 0, 1) : 0.5
r =
(thinning > 0
? lerp(size - size * thinning, size, clamp(pressure, 0, 1))
: lerp(size, size + size * thinning, clamp(pressure, 0, 1))) / 2
}
for (let t = 0, step = 0.1; t <= 1; t += step) {
tl = projectPoint(first, angle + PI + TAU - t * PI, r - 1)
tr = projectPoint(last, angle + TAU - t * PI, r - 1)
leftPts.push(tl)
rightPts.push(tr)
}
return leftPts.concat(rightPts)
}
// For a point with more than one point, create an outline shape.
for (let i = 1; i < len; i++) {
const [px, py, pa] = points[i - 1]
let [x, y, angle, ip, distance, clen] = points[i]
const prev = points[i - 1],
pa = prev[3]
length += clen
let [x, y, pressure, angle, distance, clen] = points[i]
// Size
if (pressure) {
// 1.
// Calculate the size of the current point.
if (thinning) {
if (simulatePressure) {
// Simulate pressure by accellerating the reported pressure.
const rp = min(1 - distance / maxSize, 1)
const sp = min(distance / maxSize, 1)
ip = min(1, pp + (rp - pp) * (sp / 2))
const rp = min(1 - distance / size, 1)
const sp = min(distance / size, 1)
pressure = min(1, pp + (rp - pp) * (sp / 2))
}
// Compute the size based on the pressure.
size = clamp(minSize + ip * (maxSize - minSize), minSize, maxSize)
} else {
size = maxSize
// Compute the size based on the pressure and thinning.
r =
(thinning > 0
? lerp(size - size * thinning, size, clamp(pressure, 0, 1))
: lerp(size, size + size * thinning, clamp(pressure, 0, 1))) / 2
}
// Handle line start
if (!started && length > size / 2) {
const [sx, sy] = points[0]
// 2.
// Draw a cap once we've reached the minimum length.
if (short) {
if (clen < size / 2) {
continue
}
for (let t = 0, step = 0.25; t <= 1; t += step) {
m0 = projectPoint(sx, sy, angle + TAU + t * PI, size - 1)
leftPts.push(m0)
// The first point after we've reached the minimum length.
short = false
m1 = projectPoint(sx, sy, angle - TAU + t * -PI, size - 1)
rightPts.push(m1)
// Draw a cap at the first point angled toward the current point.
const first = points[0]
for (let t = 0, step = 0.1; t <= 1; t += step) {
tl = projectPoint(first, angle + TAU + t * PI, r - 1)
leftPts.push(tl)
}
started = true
continue
tr = projectPoint(first, angle + TAU, r - 1)
rightPts.push(tr)
}
// 3. Shape
p0 = projectPoint(x, y, angle - TAU, size) // left
p1 = projectPoint(x, y, angle + TAU, size) // right
// 3.
// Add points for the current point.
if (i === len - 1) {
// The last point in the line.
const delta = angleDelta(pa, angle)
// Add points for an end cap.
for (let t = 0, step = 0.1; t <= 1; t += step) {
tr = projectPoint([x, y], angle + TAU - t * PI, r - 1)
rightPts.push(tr)
}
} else {
// Find the delta between the current and previous angle.
const delta = getAngleDelta(prev[3], angle)
// Handle sharp corners differently
if (i === points.length - 1 || (abs(delta) > PI * 0.75 && length > size)) {
const [mx, my] = getPointBetween(px, py, x, y, 0.5)
if (abs(delta) > SHARP && clen > r) {
// A sharp corner.
for (let t = 0, step = 0.25; t <= 1; t += step) {
m0 = projectPoint(mx, my, pa - TAU + t * PI, size - 1)
leftPts.push(m0)
// Project points (left and right) for a cap.
const mid = getPointBetween(prev, [x, y], 0.5)
m1 = projectPoint(mx, my, pa + TAU + t * -PI, size - 1)
rightPts.push(m1)
for (let t = 0, step = 0.25; t <= 1; t += step) {
tl = projectPoint(mid, pa - TAU + t * PI, r - 1)
tr = projectPoint(mid, pa + TAU + t * -PI, r - 1)
leftPts.push(tl)
rightPts.push(tr)
}
} else {
// A regular point.
// Add projected points left and right.
pl = projectPoint([x, y], angle - TAU, r)
pr = projectPoint([x, y], angle + TAU, r)
// Add projected point if far enough away from last left point
if (getDistance(pl, tl) > minDist) {
leftPts.push(getPointBetween(tl, pl, 0.5))
tl = pl
}
// Add point if far enough away from last right point
if (getDistance(pr, tr) > minDist) {
rightPts.push(getPointBetween(tr, pr, 0.5))
tr = pr
}
}
t0 = m0
t1 = m1
} else {
// Project sideways
d0 = getDistance(p0[0], p0[1], t0[0], t0[1])
if (d0 > smooth) {
leftPts.push(m0)
m0 = getPointBetween(t0[0], t0[1], p0[0], p0[1], 0.5)
t0 = p0
}
d1 = getDistance(p1[0], p1[1], t1[0], t1[1])
if (d1 > smooth) {
rightPts.push(m1)
m1 = getPointBetween(t1[0], t1[1], p1[0], p1[1], 0.5)
t1 = p1
}
pp = pressure
}
pp = ip
}

@@ -311,72 +240,22 @@

/**
* ## clipPath
* @description Returns a clipped polygon of the provided points.
* @param points An array of points (as number[]), the output of getStrokeOutlinePoints.
*/
export function clipPath(points: number[][]) {
return polygonClipping.union([points] as any)
}
/**
* ## getPath
* @description Returns a pressure sensitive stroke SVG data
* ## getStroke
* @description Returns a stroke as an array of points.
* @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional.
* @param options An (optional) object with options.
* @param options.size The base size (diameter) of the stroke.
* @param options.thinning The effect of pressure on the stroke's size.
* @param options.smoothing How much to soften the stroke's edges.
* @param options.streamline How much to streamline the stroke.
* @param options.simulatePressure Whether to simulate pressure based on velocity.
*/
export default function getPath<
export default function getStroke<
T extends number[],
K extends { x: number; y: number; pressure?: number }
>(points: (T | K)[], options: StrokeOptions = {} as StrokeOptions): string {
if (points.length === 0) {
return ''
}
>(points: (T | K)[], options: StrokeOptions = {} as StrokeOptions): number[][] {
return getStrokeOutlinePoints(
getStrokePoints(points, options.streamline),
options
)
}
const { clip = true, maxSize = 8 } = options
let ps = getStrokePoints(points, options),
totalLength = ps[ps.length - 1][5],
pts =
totalLength < maxSize
? getShortStrokeOutlinePoints(ps, options)
: getStrokeOutlinePoints(ps, options),
d: string[] = []
// If the length is too short, just draw a dot.
// If we're clipping the path, then find the polygon and add its faces.
if (clip) {
const poly = clipPath(pts)
for (let face of poly) {
for (let verts of face) {
let v0 = verts[0]
let v1 = verts[1]
verts.push(v0)
d.push(`M ${v0[0]} ${v0[1]}`)
for (let i = 1; i < verts.length; i++) {
const [mpx, mpy] = getPointBetween(v0[0], v0[1], v1[0], v1[1], 0.5)
d.push(` Q ${v0[0]},${v0[1]} ${mpx},${mpy}`)
v0 = v1
v1 = verts[i + 1]
}
}
}
} else {
// If we're not clipping the path, just trace it.
let v0 = pts[0]
let v1 = pts[1]
pts.push(v0)
d.push(`M ${v0[0]} ${v0[1]}`)
for (let i = 1; i < pts.length; i++) {
const [mpx, mpy] = getPointBetween(v0[0], v0[1], v1[0], v1[1], 0.5)
d.push(`Q ${v0[0]},${v0[1]} ${mpx},${mpy}`)
v0 = v1
v1 = pts[i + 1]
}
}
d.push('Z')
return d.join(' ')
}
export { StrokeOptions }

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