UNPKG

toosoon-utils

Version:
188 lines (187 loc) 6.51 kB
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(' '); } }