toosoon-utils
Version:
Utility functions & classes
195 lines (194 loc) • 6.98 kB
JavaScript
import { EPSILON, PI, TWO_PI } from '../../constants';
import { toDegrees } from '../../geometry';
import { LineCurve, PolylineCurve, QuadraticBezierCurve, CubicBezierCurve, CatmullRomCurve, SplineCurve, EllipseCurve, ArcCurve } from '../curves';
import PathContext from './PathContext';
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 PathContext
*/
export default class PathSVG extends PathContext {
/**
* Serialize this path into a SVG path string
*
* @param {PathSVGSerializationParameters} [params] Serialization parameters
* @returns {string}
*/
toString(params) {
return PathSVG.serialize(this, params);
}
/**
* Convert a {@link Curve} into spaced points
*
* @param {Curve} curve Curve to approximate
* @param {number} [resolution=5] Approximation resolution
* @returns
*/
static approximate(curve, resolution = 5) {
const divisions = (curve.getLength() * resolution) / 100;
return curve.getSpacedPoints(divisions);
}
/**
* Serialize a {@link Curve}
*
* @param {Curve} curve Curve to serialize
* @param {PathSVGSerializationParameters} [params] Serialization parameters
* @returns {string}
*/
static serialize(curve, { approximate, resolution } = {}) {
if (curve instanceof Path) {
return this.serializePath(curve, { approximate, resolution });
}
if (approximate === true) {
const points = this.approximate(curve, resolution);
return points.map(([x, y]) => `L${x},${y}`).join(' ');
}
if (curve instanceof LineCurve) {
return this.serializeLineCurve(curve);
}
if (curve instanceof PolylineCurve) {
return this.serializePolylineCurve(curve);
}
if (curve instanceof QuadraticBezierCurve) {
return this.serializeQuadraticBezierCurve(curve);
}
if (curve instanceof CubicBezierCurve) {
return this.serializeCubicBezierCurve(curve);
}
if (curve instanceof CatmullRomCurve) {
return this.serializeCatmullRomCurve(curve, resolution);
}
if (curve instanceof SplineCurve) {
return this.serializeSplineCurve(curve, resolution);
}
if (curve instanceof EllipseCurve) {
return this.serializeEllipseCurve(curve);
}
if (curve instanceof ArcCurve) {
return this.serializeArcCurve(curve);
}
return '';
}
/**
* Serialize a {@link LineCurve}
*
* @param {LineCurve} curve LineCurve to serialize
* @returns {string}
*/
static serializeLineCurve(curve) {
const { x2, y2 } = curve;
return `L${x2},${y2}`;
}
/**
* Serialize a {@link PolylineCurve}
*
* @param {PolylineCurve} curve PolylineCurve to serialize
* @returns {string}
*/
static serializePolylineCurve(curve) {
const { points } = curve;
return points.map(([x, y]) => `L${x},${y}`).join(' ');
}
/**
* Serialize a {@link QuadraticBezierCurve}
*
* @param {QuadraticBezierCurve} curve QuadraticBezierCurve to serialize
* @returns {string}
*/
static serializeQuadraticBezierCurve(curve) {
const { cpx, cpy, x2, y2 } = curve;
return `Q${cpx},${cpy},${x2},${y2}`;
}
/**
* Serialize a {@link CubicBezierCurve}
*
* @param {CubicBezierCurve} curve CubicBezierCurve to serialize
* @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} by approximating it into straight lines
*
* @param {CatmullRomCurve} curve CatmullRomCurve to serialize
* @param {number} [resolution] Approximation resolution
* @returns {string}
*/
static serializeCatmullRomCurve(curve, resolution) {
const points = this.approximate(curve, resolution);
return points.map(([x, y]) => `L${x},${y}`).join(' ');
}
/**
* Serialize a {@link SplineCurve} by approximating it into straight lines
*
* @param {SplineCurve} curve SplineCurve to serialize
* @param {number} [resolution] Approximation resolution
* @returns {string}
*/
static serializeSplineCurve(curve, resolution) {
const points = this.approximate(curve, resolution);
return points.map(([x, y]) => `L${x},${y}`).join(' ');
}
/**
* Serialize an {@link EllipseCurve}
*
* @param {EllipseCurve} curve EllipseCurve to serialize
* @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 [x0, y0] = EllipseCurve.interpolate(0, cx, cy, rx, ry, rotation, startAngle, endAngle, counterclockwise);
const [x1, y1] = EllipseCurve.interpolate(1, cx, cy, rx, ry, rotation, startAngle, endAngle, counterclockwise);
const xAxisRotation = toDegrees(rotation);
const largeArcFlag = deltaAngle >= PI ? 1 : 0;
const sweepFlag = counterclockwise ? 0 : 1;
if (deltaAngle > TWO_PI - EPSILON) {
return (`A${rx},${ry},${xAxisRotation},1,${sweepFlag},${x0},${y0}` +
`A${rx},${ry},${xAxisRotation},1,${sweepFlag},${x1},${y1}`);
}
else if (deltaAngle > EPSILON) {
return `A${rx},${ry},${xAxisRotation},${largeArcFlag},${sweepFlag},${x1},${y1}`;
}
return '';
}
/**
* Serialize an {@link ArcCurve}
*
* @param {ArcCurve} curve ArcCurve to serialize
* @returns {string}
*/
static serializeArcCurve(curve) {
return this.serializeEllipseCurve(curve);
}
/**
* Serialize an {@link Path}
*
* @param {Path} path Path to serialize
* @param {PathSVGSerializationParameters} [params] Serialization parameters
* @returns {string}
*/
static serializePath(path, params) {
return path.curves
.map((curve, index) => {
let commands = ``;
const previousPoint = path.curves[index - 1]?.getPoint(1);
const newPoint = curve.getPoint(0);
if (index === 0 || !previousPoint?.equals(newPoint)) {
commands += `M${newPoint.x},${newPoint.y}`;
}
commands += this.serialize(curve, params);
return commands;
})
.join(' ');
}
}