toosoon-utils
Version:
Utility functions & classes
188 lines (187 loc) • 6.51 kB
JavaScript
import { EPSILON, PI, TWO_PI, TAU_EPSILON } from '../../constants';
import { isCoincident, toDegrees } from '../../geometry';
import LineCurve from '../curves/LineCurve';
import PolylineCurve from '../curves/PolylineCurve';
import QuadraticBezierCurve from '../curves/QuadraticBezierCurve';
import CubicBezierCurve from '../curves/CubicBezierCurve';
import CatmullRomCurve from '../curves/CatmullRomCurve';
import SplineCurve from '../curves/SplineCurve';
import EllipseCurve from '../curves/EllipseCurve';
import ArcCurve from '../curves/ArcCurve';
import PathContext from './PathContext';
/**
* 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 PathContext
*/
export default class PathSVG extends PathContext {
/**
* Serialize a {@link Curve}
*
* @param {Curve} curve
* @param {object} [params.curveResolution=5] Resolution used for Catmull-Rom curves and Spline curves approximations
* @returns string
*/
static serialize(curve, { curveResolution } = {}) {
if (curve instanceof LineCurve) {
return PathSVG.serializeLineCurve(curve);
}
if (curve instanceof PolylineCurve) {
return PathSVG.serializePolylineCurve(curve);
}
if (curve instanceof QuadraticBezierCurve) {
return PathSVG.serializeQuadraticBezierCurve(curve);
}
if (curve instanceof CubicBezierCurve) {
return PathSVG.serializeCubicBezierCurve(curve);
}
if (curve instanceof CatmullRomCurve) {
return PathSVG.serializeCatmullRomCurve(curve, { curveResolution });
}
if (curve instanceof SplineCurve) {
return PathSVG.serializeSplineCurve(curve, { curveResolution });
}
if (curve instanceof EllipseCurve) {
return PathSVG.serializeEllipseCurve(curve);
}
if (curve instanceof ArcCurve) {
return PathSVG.serializeArcCurve(curve);
}
return '';
}
/**
* Serialize a {@link LineCurve}
*
* @param {LineCurve} curve
* @returns string
*/
static serializeLineCurve(curve) {
const { x2, y2 } = curve;
return `L${x2},${y2}`;
}
/**
* Serialize a {@link PolylineCurve}
*
* @param {PolylineCurve} curve
* @returns string
*/
static serializePolylineCurve(curve) {
const { points } = curve;
return points.map(([x, y]) => `L${x},${y}`).join(' ');
}
/**
* Serialize a {@link QuadraticBezierCurve}
*
* @param {QuadraticBezierCurve} curve
* @returns string
*/
static serializeQuadraticBezierCurve(curve) {
const { cpx, cpy, x2, y2 } = curve;
return `Q${cpx},${cpy},${x2},${y2}`;
}
/**
* Serialize a {@link CubicBezierCurve}
*
* @param {CubicBezierCurve} curve
* @returns string
*/
static serializeCubicBezierCurve(curve) {
const { cp1x, cp1y, cp2x, cp2y, x2, y2 } = curve;
return `C${cp1x},${cp1y},${cp2x},${cp2y},${x2},${y2}`;
}
/**
* Serialize a {@link CatmullRomCurve}
*
* @param {CatmullRomCurve} curve
* @param {object} params
* @param {number} [params.curveResolution=5]
* @returns string
*/
static serializeCatmullRomCurve(curve, { curveResolution = 5 }) {
const divisions = (curve.getLength() * curveResolution) / 100;
return curve
.getSpacedPoints(divisions)
.map(([x, y]) => `L${x},${y}`)
.join(' ');
}
/**
* Serialize a {@link SplineCurve}
*
* @param {SplineCurve} curve
* @param {object} params
* @param {number} [params.curveResolution=5]
* @returns string
*/
static serializeSplineCurve(curve, { curveResolution = 5 }) {
const divisions = (curve.getLength() * curveResolution) / 100;
return curve
.getSpacedPoints(divisions)
.map(([x, y]) => `L${x},${y}`)
.join(' ');
}
/**
* Serialize an {@link EllipseCurve}
*
* @param {EllipseCurve} curve
* @returns string
*/
static serializeEllipseCurve(curve) {
const { cx, cy, rx, ry, rotation, startAngle, endAngle, counterclockwise } = curve;
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;
return (`A${rx},${ry},${xAxisRotation},1,${sweepFlag},${cx - dx},${cy - dy}` +
`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;
return `A${rx},${ry},${xAxisRotation},${largeArcFlag},${sweepFlag},${cx + dx},${cy + dy}`;
}
return '';
}
/**
* Serialize an {@link ArcCurve}
*
* @param {ArcCurve} curve
* @returns string
*/
static serializeArcCurve(curve) {
return PathSVG.serializeEllipseCurve(curve);
}
/**
* Return SVG path data string
*
* @param {object} [params]
* @param {object} [params.curveResolution=5] Resolution used for Catmull-Rom curves and Spline curves approximations
* @returns {string}
*/
toString(params = {}) {
return this.curves
.map((curve, index) => {
let commands = ``;
const lastPoint = index === 0 ? this.curves[0]?.getPoint(0) : this.curves[index - 1]?.getPoint(1);
if (index === 0 || !isCoincident(...lastPoint, ...curve.getPoint(0))) {
commands += `M${lastPoint[0]},${lastPoint[1]}`;
}
// commands += PathSVG.serialize(curve, params);
const divisions = curve.getLength() / 50;
commands += curve
.getSpacedPoints(divisions)
.map(([x, y]) => `L${x},${y}`)
.join(' ');
return commands;
})
.join(' ');
}
}