Add fraktur E and G (#2448)

* Add
  - MATHEMATICAL FRAKTUR CAPITAL E (`U+1D508`) (#444).
  - MATHEMATICAL FRAKTUR CAPITAL G (`U+1D50A`) (#444).

* Cleanup

* Refine
This commit is contained in:
Belleve 2024-07-31 01:55:49 -10:00 committed by GitHub
parent d08e7db129
commit a9a0556b17
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 716 additions and 361 deletions

View file

@ -5,7 +5,7 @@ import * as CurveUtil from "./curve-util.mjs";
import { Point } from "./point.mjs";
import { QuadifySink } from "./quadify.mjs";
import { SpiroExpander } from "./spiro-expand.mjs";
import { createSpiroPenGeometry } from "./spiro-pen-expander.mjs";
import { PenSpiroExpander } from "./spiro-pen-expand.mjs";
import { spiroToOutlineWithSimplification } from "./spiro-to-outline.mjs";
import { strokeArcs } from "./stroke.mjs";
import { Transform } from "./transform.mjs";
@ -141,23 +141,23 @@ export class SpiroGeometry extends CachedGeometry {
}
export class SpiroPenGeometry extends CachedGeometry {
constructor(gizmo, closed, pen, knots) {
constructor(gizmo, penProfile, closed, knots) {
super();
this.m_gizmo = gizmo;
this.m_penProfile = penProfile;
this.m_closed = closed;
this.m_knots = knots;
this.m_pen = pen;
}
toContoursImpl() {
let contours = createSpiroPenGeometry(
const expander = new PenSpiroExpander(
this.m_gizmo,
this.m_penProfile,
this.m_closed,
this.m_knots,
this.m_pen,
);
if (!contours.length) return [];
let contours = expander.getGeometry();
if (!contours || !contours.length) return [];
let stack = [];
for (const [i, c] of contours.entries()) {
@ -189,7 +189,7 @@ export class SpiroPenGeometry extends CachedGeometry {
measureComplexity() {
let cplx = CPLX_NON_EMPTY | CPLX_NON_SIMPLE;
for (const z of this.m_pen) {
for (const z of this.m_penProfile) {
if (!isFinite(z.x) || !isFinite(z.y)) cplx |= CPLX_BROKEN;
}
for (const z of this.m_knots) {
@ -204,8 +204,8 @@ export class SpiroPenGeometry extends CachedGeometry {
h.bool(this.m_closed);
// Serialize the pen
h.beginArray(this.m_pen.length);
for (const z of this.m_pen) h.point(z);
h.beginArray(this.m_penProfile.length);
for (const z of this.m_penProfile) h.point(z);
h.endArray();
// Serialize the knots

View file

@ -37,6 +37,7 @@ export class SpiroFlattener {
for (const fn of this.preControlFunctions) fn.call(collector);
for (const control of this.controls) collector.pushKnot(control);
for (const postControl of this.postControls) postControl.applyTo(collector);
collector.finish();
}
/// Add a control object (or list) to a sink
@ -326,6 +327,28 @@ export class UserCloseKnotPair {
}
}
export class VirtualControlKnot {
constructor(x, y, af) {
this.center = new UserControlKnot("corner", x, y, af);
}
getDependency(stage) {
return this.center.getDependency(stage);
}
getKernelKnot() {
return this.center.getKernelKnot();
}
resolveCoordiantePropogation(ic, pre, post) {
this.center.resolveCoordiantePropogation(ic, pre, post);
}
resolveNonInterpolated() {
return [];
}
resolveInterpolation() {
throw new Error("Unreachable");
}
}
export class InterpolatorBase {
constructor() {}
@ -415,6 +438,8 @@ export function WithKnotProxy(proxy, actual) {
return new KnotProxyInterpolator(proxy, actual);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
export class TerminateInstruction {
constructor(type, af) {
this.type = type;
@ -443,139 +468,3 @@ export class DerivedCoordinateBase {
throw new Error("Unimplemented");
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
export class BiKnotCollector {
constructor(contrast) {
this.contrast = contrast; // stroke contrast
this.defaultD1 = 0; // default LHS
this.defaultD2 = 0; // default RHS sw
this.lastKnot = null; // last knot in the processed items
this.controls = []; // all the control items
this.closed = false; // whether the shape is closed
}
pushKnot(c) {
let k;
if (this.lastKnot) {
k = new BiKnot(c.type, c.x, c.y, this.lastKnot.d1, this.lastKnot.d2);
} else {
k = new BiKnot(c.type, c.x, c.y, this.defaultD1, this.defaultD2);
}
this.controls.push(k);
this.lastKnot = k;
c.applyTo(this);
}
setWidth(l, r) {
if (this.lastKnot) {
this.lastKnot.d1 = l;
this.lastKnot.d2 = r;
} else {
this.defaultD1 = l;
this.defaultD2 = r;
}
}
headsTo(direction) {
if (this.lastKnot) {
this.lastKnot.proposedNormal = direction;
}
}
setUnimportant() {
if (this.lastKnot) {
this.lastKnot.unimportant = 1;
}
}
setContrast(c) {
this.contrast = c;
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
export class MonoKnot {
constructor(type, unimportant, x, y) {
this.type = type;
this.x = x;
this.y = y;
this.unimportant = unimportant;
}
clone() {
const k1 = new MonoKnot(this.type, this.x, this.y, this.unimportant);
return k1;
}
hash(h) {
h.beginStruct("MonoKnot");
h.str(this.type);
h.bool(this.unimportant);
h.f64(this.x);
h.f64(this.y);
h.endStruct();
}
reverseType() {
if (this.type === "left") {
this.type = "right";
} else if (this.type === "right") {
this.type = "left";
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
class BiKnot {
constructor(type, x, y, d1, d2) {
this.type = type;
this.x = x;
this.y = y;
this.d1 = d1;
this.d2 = d2;
this.proposedNormal = null;
this.unimportant = 0;
// Derived properties
this.origTangent = null;
}
clone() {
const k1 = new BiKnot(this.type, this.x, this.y, this.d1, this.d2);
k1.origTangent = this.origTangent;
k1.proposedNormal = this.proposedNormal;
k1.unimportant = this.unimportant;
return k1;
}
withGizmo(gizmo) {
const tfZ = gizmo.applyXY(this.x, this.y);
const k1 = new BiKnot(this.type, tfZ.x, tfZ.y, this.d1, this.d2);
k1.origTangent = this.origTangent ? gizmo.applyOffset(this.origTangent) : null;
k1.proposedNormal = this.proposedNormal ? gizmo.applyOffset(this.proposedNormal) : null;
k1.unimportant = this.unimportant;
return k1;
}
hash(h) {
h.beginStruct("BiKnot");
h.str(this.type);
h.bool(this.unimportant);
h.f64(this.x);
h.f64(this.y);
h.bool(this.d1 != null);
if (this.d1 != null) h.f64(this.d1);
h.bool(this.d2 != null);
if (this.d2 != null) h.f64(this.d2);
h.bool(this.proposedNormal != null);
if (this.proposedNormal) {
h.f64(this.proposedNormal.x);
h.f64(this.proposedNormal.y);
}
h.endStruct();
}
toMono() {
return new MonoKnot(this.type, this.unimportant, this.x, this.y);
}
}

View file

@ -2,7 +2,129 @@ import { linreg, mix } from "@iosevka/util";
import * as SpiroJs from "spiro";
import { Vec2 } from "./point.mjs";
import { MonoKnot } from "./spiro-control.mjs";
import { MonoKnot } from "./spiro-to-outline.mjs";
///////////////////////////////////////////////////////////////////////////////////////////////////
export class BiKnotCollector {
constructor(contrast) {
this.contrast = contrast; // stroke contrast
this.defaultD1 = 0; // default LHS
this.defaultD2 = 0; // default RHS sw
this.lastKnot = null; // last knot in the processed items
this.knots = []; // all the control items
this.closed = false; // whether the shape is closed
this.m_finished = false;
}
get controls() {
throw new Error("Not implemented");
}
finish() {
this.m_finished = true;
}
pushKnot(c) {
if (this.m_finished) throw new Error("Cannot push knot after finish");
let k;
if (this.lastKnot) {
k = new BiKnot(c.type, c.x, c.y, this.lastKnot.d1, this.lastKnot.d2);
} else {
k = new BiKnot(c.type, c.x, c.y, this.defaultD1, this.defaultD2);
}
this.knots.push(k);
this.lastKnot = k;
c.applyTo(this);
}
setWidth(l, r) {
if (this.lastKnot) {
this.lastKnot.d1 = l;
this.lastKnot.d2 = r;
} else {
this.defaultD1 = l;
this.defaultD2 = r;
}
}
headsTo(direction) {
if (this.lastKnot) {
this.lastKnot.proposedNormal = direction;
}
}
setUnimportant() {
if (this.lastKnot) {
this.lastKnot.unimportant = 1;
}
}
setContrast(c) {
this.contrast = c;
}
getMonoKnots() {
let a = [];
for (const c of this.knots) {
a.push(c.toMono());
}
return a;
}
}
export class BiKnot {
constructor(type, x, y, d1, d2) {
this.type = type;
this.x = x;
this.y = y;
this.d1 = d1;
this.d2 = d2;
this.proposedNormal = null;
this.unimportant = 0;
// Derived properties
this.origTangent = null;
}
clone() {
const k1 = new BiKnot(this.type, this.x, this.y, this.d1, this.d2);
k1.origTangent = this.origTangent;
k1.proposedNormal = this.proposedNormal;
k1.unimportant = this.unimportant;
return k1;
}
withGizmo(gizmo) {
const tfZ = gizmo.applyXY(this.x, this.y);
const k1 = new BiKnot(this.type, tfZ.x, tfZ.y, this.d1, this.d2);
k1.origTangent = this.origTangent ? gizmo.applyOffset(this.origTangent) : null;
k1.proposedNormal = this.proposedNormal ? gizmo.applyOffset(this.proposedNormal) : null;
k1.unimportant = this.unimportant;
return k1;
}
hash(h) {
h.beginStruct("BiKnot");
h.str(this.type);
h.bool(this.unimportant);
h.f64(this.x);
h.f64(this.y);
h.bool(this.d1 != null);
if (this.d1 != null) h.f64(this.d1);
h.bool(this.d2 != null);
if (this.d2 != null) h.f64(this.d2);
h.bool(this.proposedNormal != null);
if (this.proposedNormal) {
h.f64(this.proposedNormal.x);
h.f64(this.proposedNormal.y);
}
h.endStruct();
}
toMono() {
return new MonoKnot(this.type, this.unimportant, this.x, this.y);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
@ -78,19 +200,47 @@ export class SpiroExpander {
return { lhs: lhsT, rhs: rhsT, lhsUntransformed: lhsU, rhsUntransformed: rhsU };
}
interpolateUnimportantKnots(lhsT, rhsT, lhsU, rhsU) {
for (let j = 0; j < this.m_biKnotsU.length; j++) {
const knotU = this.m_biKnotsU[j];
if (!knotU.unimportant) continue;
let jBefore, jAfter;
for (jBefore = j - 1; cyNth(this.m_biKnotsU, jBefore).unimportant; jBefore--);
for (jAfter = j + 1; cyNth(this.m_biKnotsU, jAfter).unimportant; jAfter++);
let firstImportantIdx = -1;
let lastImportantIdx = -1;
const knotUBefore = cyNth(this.m_biKnotsU, jBefore),
knotUAfter = cyNth(this.m_biKnotsU, jAfter),
lhsUBefore = cyNth(lhsU, jBefore),
lhsUAfter = cyNth(lhsU, jAfter),
rhsUBefore = cyNth(rhsU, jBefore),
rhsUAfter = cyNth(rhsU, jAfter);
for (let j = 0; j < this.m_biKnotsU.length; j++) {
// If the current knot is unimportant, skip it
if (this.m_biKnotsU[j].unimportant) continue;
// If we've scanned an important knot before, interpolate the unimportant knots between
if (lastImportantIdx !== -1) {
this.interpolateUnimportantKnotsRg(lhsT, rhsT, lhsU, rhsU, lastImportantIdx, j);
}
if (firstImportantIdx === -1) firstImportantIdx = j;
lastImportantIdx = j;
}
// Handle the last important ... first important wraparound
if (firstImportantIdx !== -1 && lastImportantIdx !== -1) {
this.interpolateUnimportantKnotsRg(
lhsT,
rhsT,
lhsU,
rhsU,
lastImportantIdx,
firstImportantIdx,
);
}
}
interpolateUnimportantKnotsRg(lhsT, rhsT, lhsU, rhsU, jBefore, jAfter) {
let count = jAfter > jBefore ? jAfter - jBefore : lhsT.length - jBefore + jAfter;
for (let offset = 1; offset < count; offset++) {
let j = (jBefore + offset) % lhsT.length;
const knotUBefore = this.m_biKnotsU[jBefore],
knotU = this.m_biKnotsU[j],
knotUAfter = this.m_biKnotsU[jAfter],
lhsUBefore = lhsU[jBefore],
lhsUAfter = lhsU[jAfter],
rhsUBefore = rhsU[jBefore],
rhsUAfter = rhsU[jAfter];
lhsU[j].x = linreg(knotUBefore.x, lhsUBefore.x, knotUAfter.x, lhsUAfter.x, knotU.x);
lhsU[j].y = linreg(knotUBefore.y, lhsUBefore.y, knotUAfter.y, lhsUAfter.y, knotU.y);

View file

@ -0,0 +1,243 @@
import * as SpiroJs from "spiro";
import { linreg } from "@iosevka/util";
import * as CurveUtil from "./curve-util.mjs";
import { Point } from "./point.mjs";
import { MonoKnot } from "./spiro-to-outline.mjs";
export class PenKnotCollector {
constructor(gizmo, defaultProfile) {
this.gizmo = gizmo;
this.m_profile = defaultProfile;
this.m_lastKnot = null;
this.m_finished = false;
this.knots = [];
this.closed = false;
}
finish() {
this.m_finished = true;
}
pushKnot(c) {
if (this.m_finished) throw new Error("Cannot push knot after finish");
let k = new PenKnot(c.type, c.x, c.y, this.m_profile);
this.knots.push(k);
this.m_lastKnot = k;
c.applyTo(this);
}
setWidth() {}
headsTo() {}
setUnimportant() {
if (this.m_lastKnot) this.m_lastKnot.profile = null;
}
setContrast() {}
setProfile(profile) {
if (profile.length !== this.m_profile.length)
throw new Error("Pen profile length mismatch");
if (this.m_lastKnot) this.m_lastKnot.profile = profile;
this.m_profile = profile;
}
}
export class PenKnot {
constructor(type, x, y, profile) {
this.type = type;
this.x = x;
this.y = y;
this.profile = profile;
}
clone() {
const k1 = new PenKnot(this.type, this.x, this.y, this.profile);
return k1;
}
withGizmo(gizmo) {
const tfZ = gizmo.applyXY(this.x, this.y);
const k1 = new PenKnot(this.type, tfZ.x, tfZ.y, this.profile);
return k1;
}
hash(h) {
h.beginStruct("PenKnot");
h.str(this.type);
h.f64(this.x);
h.f64(this.y);
h.bool(this.profile != null);
if (this.profile) {
h.beginArray(this.profile.length);
for (let i = 0; i < this.profile.length; i++) {
h.f64(this.profile[i].x);
h.f64(this.profile[i].y);
}
h.endArray();
}
h.endStruct();
}
}
export class PenSpiroExpander {
constructor(gizmo, profile, closed, knots) {
this.m_gizmo = gizmo;
this.m_closed = closed;
this.m_knotsU = Array.from(knots);
this.m_knotsT = knots.map(k => k.withGizmo(gizmo));
this.m_profileEdges = profile.length;
this.m_traces = [];
}
getGeometry() {
this.traceAll();
const contours = [];
for (let i = 0; i < this.m_traces.length; i++) {
const iNext = (i + 1) % this.m_traces.length;
if (this.m_traces[i].length !== this.m_traces[iNext].length) {
throw new Error("Different number of arcs in traces");
}
for (let j = 0; j < this.m_traces[i].length; j++) {
const arcForward = this.m_traces[i][j];
const arcBackward = this.m_traces[iNext][j];
makeProfiledStroke(contours, arcForward, arcBackward);
}
}
return contours;
}
traceAll() {
for (let i = 0; i < this.m_profileEdges; i++) {
this.calculateShiftedProfile(i);
}
}
calculateShiftedProfile(iEdge) {
let traceT = [],
traceU = [];
for (let i = 0; i < this.m_knotsT.length; i++) {
const trT = this.getTrace(this.m_knotsT[i], iEdge);
const trU = trT.clone();
this.m_gizmo.unapplyToSink(trT, trU);
traceT.push(trT), traceU.push(trU);
}
this.interpolateUnimportantTraceKnots(traceU, traceT);
let arcc = new SimplyCollectArcs();
SpiroJs.spiroToArcsOnContext(traceT, this.m_closed, arcc);
this.m_traces.push(arcc.arcs);
}
getTrace(k, iEdge) {
if (k.profile) {
return new MonoKnot(k.type, false, k.x + k.profile[iEdge].x, k.y + k.profile[iEdge].y);
} else {
return new MonoKnot(k.type, true, k.x, k.y);
}
}
interpolateUnimportantTraceKnots(traceU, traceT) {
let firstImportantIdx = -1;
let lastImportantIdx = -1;
for (let i = 0; i < traceU.length; i++) {
if (traceU[i].unimportant) continue;
if (lastImportantIdx !== -1) {
this.interpolateUnimportantTraceKnotsRg(traceU, traceT, lastImportantIdx, i);
}
if (firstImportantIdx === -1) firstImportantIdx = i;
lastImportantIdx = i;
}
if (firstImportantIdx !== -1 && lastImportantIdx !== -1) {
this.interpolateUnimportantTraceKnotsRg(
traceU,
traceT,
lastImportantIdx,
firstImportantIdx,
);
}
}
interpolateUnimportantTraceKnotsRg(traceU, traceT, last, next) {
let count = next > last ? next - last : traceU.length - last + next;
for (let offset = 1; offset < count; offset++) {
let i = (last + offset) % traceU.length;
this.interpolateKnot(
this.m_knotsU[last],
traceU[last],
this.m_knotsU[i],
traceU[i],
traceT[i],
this.m_knotsU[next],
traceU[next],
);
}
}
interpolateKnot(rBefore, uBefore, rCurr, uCurr, tCurr, rAfter, uAfter) {
uCurr.x = linreg(rBefore.x, uBefore.x, rAfter.x, uAfter.x, rCurr.x);
uCurr.y = linreg(rBefore.y, uBefore.y, rAfter.y, uAfter.y, rCurr.y);
this.m_gizmo.applyToSink(uCurr, tCurr);
}
}
class SimplyCollectArcs {
constructor() {
this.arcs = [];
}
beginShape() {}
endShape() {}
moveTo() {}
arcTo(arc) {
this.arcs.push(arc);
}
}
// arcForward and arcBackward must be spiro arcs.
function makeProfiledStroke(contours, arcForward, arcBackward) {
const [subdividesForward, subdividesBackward] = subdivideKnotPair(
arcForward,
arcBackward,
CurveUtil.GEOMETRY_PRECISION,
);
for (let i = 0; i < subdividesForward.length; i++) {
const [a1, b1, c1, d1] = subdividesForward[i].toCubicBezier();
const [a2, b2, c2, d2] = subdividesBackward[i].toCubicBezier();
contours.push([
Point.from(Point.Type.Corner, a1),
Point.from(Point.Type.CubicStart, b1),
Point.from(Point.Type.CubicEnd, c1),
Point.from(Point.Type.Corner, d1),
Point.from(Point.Type.Corner, d2),
Point.from(Point.Type.CubicStart, c2),
Point.from(Point.Type.CubicEnd, b2),
Point.from(Point.Type.Corner, a2),
]);
}
}
function subdivideKnotPair(arcForward, arcBackward, delta) {
const MAX_STOPS = 16;
let sinkForward = [],
sinkBackward = [];
for (let stops = 1; stops < MAX_STOPS; stops++) {
sinkForward.length = 0;
sinkBackward.length = 0;
uniformSubdivide(arcForward, stops, sinkForward);
uniformSubdivide(arcBackward, stops, sinkBackward);
let flatEnough = true;
for (const fwd of sinkForward) if (fwd.bend > delta) flatEnough = false;
for (const bwd of sinkBackward) if (bwd.bend > delta) flatEnough = false;
if (flatEnough) break;
}
return [sinkForward, sinkBackward];
}
function uniformSubdivide(arc, stops, sink) {
for (; stops > 1; stops--) {
const f = arc.subdivide(1 / stops);
sink.push(f[0]), (arc = f[1]);
}
sink.push(arc);
}

View file

@ -1,81 +0,0 @@
import * as SpiroJs from "spiro";
import * as CurveUtil from "./curve-util.mjs";
import { Point } from "./point.mjs";
export function createSpiroPenGeometry(gizmo, closed, knots, pen) {
const collector = new ArcCollector(gizmo, pen);
SpiroJs.spiroToBezierOnContext(knots, closed, collector, CurveUtil.GEOMETRY_PRECISION);
return collector.contoursCollected;
}
class ArcCollector {
constructor(gizmo, pen) {
this.gizmo = gizmo;
this.lastX = 0;
this.lastY = 0;
this.m_pen = pen;
this.contoursCollected = [];
}
beginShape() {}
endShape() {}
moveTo(x, y) {
const lastTf = this.gizmo.applyXY(x, y);
this.lastX = lastTf.x;
this.lastY = lastTf.y;
this.addPenProfileAt(this.lastX, this.lastY);
}
lineTo(x1, y1) {
const z1 = this.gizmo.applyXY(x1, y1);
for (let i = 0; i < this.m_pen.length; i++) {
let penPrev = this.m_pen[i];
let penNext = this.m_pen[(i + 1) % this.m_pen.length];
const l1 = new Point(Point.Type.Corner, this.lastX + penPrev.x, this.lastY + penPrev.y);
const l2 = new Point(Point.Type.Corner, z1.x + penPrev.x, z1.y + penPrev.y);
const r2 = new Point(Point.Type.Corner, z1.x + penNext.x, z1.y + penNext.y);
const r1 = new Point(Point.Type.Corner, this.lastX + penNext.x, this.lastY + penNext.y);
this.contoursCollected.push([l1, l2, r2, r1]);
}
this.lastX = z1.x;
this.lastY = z1.y;
this.addPenProfileAt(this.lastX, this.lastY);
}
cubicTo(x2, y2, x3, y3, x4, y4) {
const z2 = this.gizmo.applyXY(x2, y2);
const z3 = this.gizmo.applyXY(x3, y3);
const z4 = this.gizmo.applyXY(x4, y4);
for (let i = 0; i < this.m_pen.length; i++) {
let penPrev = this.m_pen[i];
let penNext = this.m_pen[(i + 1) % this.m_pen.length];
const l1 = new Point(Point.Type.Corner, this.lastX + penPrev.x, this.lastY + penPrev.y);
const l2 = new Point(Point.Type.CubicStart, z2.x + penPrev.x, z2.y + penPrev.y);
const l3 = new Point(Point.Type.CubicEnd, z3.x + penPrev.x, z3.y + penPrev.y);
const l4 = new Point(Point.Type.Corner, z4.x + penPrev.x, z4.y + penPrev.y);
const r4 = new Point(Point.Type.Corner, z4.x + penNext.x, z4.y + penNext.y);
const r3 = new Point(Point.Type.CubicStart, z3.x + penNext.x, z3.y + penNext.y);
const r2 = new Point(Point.Type.CubicEnd, z2.x + penNext.x, z2.y + penNext.y);
const r1 = new Point(Point.Type.Corner, this.lastX + penNext.x, this.lastY + penNext.y);
this.contoursCollected.push([l1, l2, l3, l4, r4, r3, r2, r1]);
}
this.lastX = z4.x;
this.lastY = z4.y;
this.addPenProfileAt(this.lastX, this.lastY);
}
addPenProfileAt(x, y) {
let c = [];
for (let i = 0; i < this.m_pen.length; i++) {
let pen = this.m_pen[i];
c.push(new Point(Point.Type.Corner, x + pen.x, y + pen.y));
}
this.contoursCollected.push(c);
}
}

View file

@ -21,6 +21,38 @@ export function spiroToOutlineWithSimplification(knots, fClosed, gizmo) {
return sink.contours;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
export class MonoKnot {
constructor(type, unimportant, x, y) {
this.type = type;
this.unimportant = unimportant;
this.x = x;
this.y = y;
}
clone() {
return new MonoKnot(this.type, this.unimportant, this.x, this.y);
}
hash(h) {
h.beginStruct("MonoKnot");
h.str(this.type);
h.bool(this.unimportant);
h.f64(this.x);
h.f64(this.y);
h.endStruct();
}
reverseType() {
if (this.type === "left") {
this.type = "right";
} else if (this.type === "right") {
this.type = "left";
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
class SpiroSimplifier {
constructor(knots) {
this.m_knots = knots;