UNPKG

@thi.ng/geom

Version:

Functional, polymorphic API for 2D geometry types & SVG generation

210 lines (209 loc) 5.63 kB
import { peek } from "@thi.ng/arrays/peek"; import { unsupported } from "@thi.ng/errors/unsupported"; import { eqDelta } from "@thi.ng/math/eqdelta"; import { add } from "@thi.ng/vectors/add"; import { copy } from "@thi.ng/vectors/copy"; import { eqDelta as eqDeltaV } from "@thi.ng/vectors/eqdelta"; import { mulN } from "@thi.ng/vectors/muln"; import { set } from "@thi.ng/vectors/set"; import { zeroes } from "@thi.ng/vectors/setn"; import { sub } from "@thi.ng/vectors/sub"; import { Cubic } from "./api/cubic.js"; import { Cubic3 } from "./api/cubic3.js"; import { Line } from "./api/line.js"; import { Line3 } from "./api/line3.js"; import { Path } from "./api/path.js"; import { Path3 } from "./api/path3.js"; import { Quadratic } from "./api/quadratic.js"; import { Quadratic3 } from "./api/quadratic3.js"; import { arcFrom2Points } from "./arc.js"; const P2D = { path: Path, a: arcFrom2Points, c: Cubic, l: Line, q: Quadratic }; const P3D = { path: Path3, c: Cubic3, l: Line3, q: Quadratic3 }; class PathBuilder { constructor(ctors, attribs, opts = {}) { this.ctors = ctors; this.attribs = attribs; this.opts = opts; this.paths = []; this.attribs = attribs; this.newPath(); } /** * Array of all paths which have been built already (incl. the current) */ paths; curr; currP; bezierP; startP; *[Symbol.iterator]() { yield* this.paths; } /** * Returns the current path being constructed. */ current() { return this.curr; } /** * Starts a new path and makes it the current one. Any future build commands * will only act on this new path. */ newPath() { this.curr = new this.ctors.path( [], [], this.attribs ? { ...this.attribs } : void 0 ); this.paths.push(this.curr); const dim = this.curr.dim; this.currP = zeroes(dim); this.bezierP = zeroes(dim); this.startP = zeroes(dim); } moveTo(p, relative = false) { if (this.opts.autoSplit !== false && this.curr.segments.length > 0) { this.curr = new this.ctors.path([], [], this.attribs); this.paths.push(this.curr); } p = this.updateCurrent(p, relative); set(this.startP, p); set(this.bezierP, p); this.curr.addSegments({ type: "m", point: p }); return this; } lineTo(p, relative = false) { this.curr.addSegments({ type: "l", geo: new this.ctors.l([ copy(this.currP), this.updateCurrent(p, relative) ]) }); set(this.bezierP, this.currP); return this; } hlineTo(x, relative = false) { this.addHVLine(x, 0, relative); return this; } vlineTo(y, relative = false) { this.addHVLine(y, 1, relative); return this; } // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#Cubic_B%C3%A9zier_Curve cubicTo(cp1, cp2, p, relative = false) { this.addCubic(this.absPoint(cp1, relative), cp2, p, relative); return this; } // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#Quadratic_B%C3%A9zier_Curve quadraticTo(cp, p, relative = false) { this.addQuadratic(this.absPoint(cp, relative), p, relative); return this; } cubicChainTo(cp2, p, relative = false) { const prevMode = peek(this.curr.segments).type; const c1 = copy(this.currP); prevMode === "c" && add(null, sub([], c1, this.bezierP), c1); this.addCubic(c1, cp2, p, relative); return this; } quadraticChainTo(p, relative = false) { const prevMode = peek(this.curr.segments).type; const c1 = copy(this.currP); prevMode === "q" && sub(null, mulN(null, c1, 2), this.bezierP); this.addQuadratic(c1, p, relative); return this; } arcTo(p, r, xaxis, xl, clockwise, relative = false) { if (!this.ctors.a) unsupported("arcs"); if (eqDelta(r[0], 0) || eqDelta(r[1], 0)) { return this.lineTo(p, relative); } const prev = copy(this.currP); this.curr.addSegments({ type: "a", geo: this.ctors.a( prev, this.updateCurrent(p, relative), r, xaxis, xl, clockwise ) }); set(this.bezierP, this.currP); return this; } close() { if (!eqDeltaV(this.startP, this.currP)) { this.curr.addSegments({ type: "l", geo: new this.ctors.l([copy(this.currP), copy(this.startP)]) }); } this.curr.close(); return this; } updateCurrent(p, relative) { p = copy(relative ? add(null, this.currP, p) : set(this.currP, p)); return p; } absPoint(p, relative) { return relative ? add(null, p, this.currP) : p; } addHVLine(p, i, relative) { const prev = copy(this.currP); this.currP[i] = relative ? this.currP[i] + p : p; set(this.bezierP, this.currP); this.curr.addSegments({ type: "l", geo: new this.ctors.l([prev, copy(this.currP)]) }); } addCubic(cp1, cp2, p, relative) { cp2 = this.absPoint(cp2, relative); set(this.bezierP, cp2); this.curr.addSegments({ type: "c", geo: new this.ctors.c([ copy(this.currP), cp1, cp2, this.updateCurrent(p, relative) ]) }); } addQuadratic(cp, p, relative) { set(this.bezierP, cp); this.curr.addSegments({ type: "q", geo: new this.ctors.q([ copy(this.currP), cp, this.updateCurrent(p, relative) ]) }); } } const pathBuilder = (attribs, opts) => new PathBuilder(P2D, attribs, opts); const pathBuilder3 = (attribs, opts) => new PathBuilder(P3D, attribs, opts); export { PathBuilder, pathBuilder, pathBuilder3 };