toosoon-utils
Version:
Utility functions & classes
138 lines (137 loc) • 5.03 kB
JavaScript
import { EPSILON, PI, TWO_PI, TAU_EPSILON } from '../../constants';
import { toDegrees } from '../../geometry';
import PolylineCurve from './PolylineCurve';
import CatmullRomCurve from './CatmulRomCurve';
import SplineCurve from './SplineCurve';
import EllipseCurve from './EllipseCurve';
import ArcCurve from './ArcCurve';
import Path from './Path';
/**
* Utility class for manipulating connected curves and generating SVG path
*
* It works by serializing 2D Canvas API to SVG path data
*
* @exports
* @class PathSVG
* @extends Path
*/
export default class PathSVG extends Path {
/**
* Resolution used for Catmul-Rom curve and Spline curve approximations
*/
curveResolution;
constructor({ curveResolution = 5 }) {
super();
this.curveResolution = curveResolution;
}
_commands = ``;
moveTo(x, y) {
super.moveTo(x, y);
this._write(`M${x},${y}`);
return this;
}
lineTo(x, y) {
super.lineTo(x, y);
this._writeLineTo(x, y);
return this;
}
polylineTo(points) {
super.polylineTo(points);
points.forEach(([x, y]) => this._writeLineTo(x, y));
return this;
}
// public arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): this {
// super.arcTo(x1, y1, x2, y2, radius);
// return this;
// }
quadraticCurveTo(cpx, cpy, x2, y2) {
super.quadraticCurveTo(cpx, cpy, x2, y2);
this._write(`Q${cpx},${cpy},${x2},${y2}`);
return this;
}
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x2, y2) {
super.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x2, y2);
this._write(`C${cp1x},${cp1y},${cp2x},${cp2y},${x2},${y2}`);
return this;
}
catmulRomCurveTo(cp1x, cp1y, cp2x, cp2y, x2, y2) {
super.catmulRomCurveTo(cp1x, cp1y, cp2x, cp2y, x2, y2);
const curve = this.curves[this.curves.length - 1];
if (curve instanceof CatmullRomCurve) {
const divisions = (curve.getLength() * this.curveResolution) / 100;
curve.getSpacedPoints(divisions).forEach(([x, y]) => this._writeLineTo(x, y));
}
return this;
}
splineTo(points) {
super.splineTo(points);
const curve = this.curves[this.curves.length - 1];
if (curve instanceof SplineCurve) {
const divisions = (curve.getLength() * this.curveResolution) / 100;
curve.getSpacedPoints(divisions).forEach(([x, y]) => this._writeLineTo(x, y));
}
return this;
}
ellipse(cx, cy, rx, ry, rotation = 0, startAngle = 0, endAngle = TWO_PI, counterclockwise) {
super.ellipse(cx, cy, rx, ry, rotation, startAngle, endAngle, counterclockwise);
const curve = this.curves[this.curves.length - 1];
if (curve instanceof EllipseCurve) {
this._writeArc(cx, cy, rx, ry, rotation, startAngle, endAngle, counterclockwise);
}
return this;
}
arc(cx, cy, radius, startAngle, endAngle, counterclockwise) {
super.arc(cx, cy, radius, startAngle, endAngle, counterclockwise);
const curve = this.curves[this.curves.length - 1];
if (curve instanceof ArcCurve) {
this._writeArc(cx, cy, radius, radius, 0, startAngle, endAngle, counterclockwise);
}
return this;
}
rect(x, y, width, height) {
super.rect(x, y, width, height);
const curve = this.curves[this.curves.length - 1];
if (curve instanceof PolylineCurve) {
curve.points?.forEach(([x, y]) => this._writeLineTo(x, y));
}
return this;
}
closePath() {
super.closePath();
this._write(`Z`);
return this;
}
_writeLineTo(x, y) {
this._write(`L${x},${y}`);
}
_writeArc(cx, cy, rx, ry, rotation = 0, startAngle = 0, endAngle = TWO_PI, counterclockwise) {
let deltaAngle = counterclockwise ? startAngle - endAngle : endAngle - startAngle;
if (deltaAngle < 0)
deltaAngle = (deltaAngle % TWO_PI) + TWO_PI;
const xAxisRotation = toDegrees(rotation);
const largeArcFlag = deltaAngle >= PI ? 1 : 0;
const sweepFlag = counterclockwise ? 0 : 1;
if (deltaAngle > TAU_EPSILON) {
const dx = Math.cos(startAngle) * rx;
const dy = Math.sin(startAngle) * ry;
this._write(`A${rx},${ry},${xAxisRotation},1,${sweepFlag},${cx - dx},${cy - dy}`);
this._write(`A${rx},${ry},${xAxisRotation},1,${sweepFlag},${cx + dx},${cy + dy}`);
}
else if (deltaAngle > EPSILON) {
const dx = Math.cos(endAngle) * rx;
const dy = Math.sin(endAngle) * ry;
this._write(`A${rx},${ry},${xAxisRotation},${largeArcFlag},${sweepFlag},${cx + dx},${cy + dy}`);
}
}
_write(command) {
this._commands += `${command}`;
}
/**
* Return SVG path data string
*
* @returns {string}
*/
toString() {
return this._commands;
}
}