diff --git a/font-src/gen/caching/index.mjs b/font-src/gen/caching/index.mjs index 63d83b19d..53017a785 100644 --- a/font-src/gen/caching/index.mjs +++ b/font-src/gen/caching/index.mjs @@ -3,7 +3,7 @@ import zlib from "zlib"; import { encode, decode } from "@msgpack/msgpack"; -const Edition = 24; +const Edition = 25; const MAX_AGE = 16; class GfEntry { constructor(age, value) { diff --git a/font-src/glyphs/common/shapes.ptl b/font-src/glyphs/common/shapes.ptl index 2662ce20d..ef5b5c0a3 100644 --- a/font-src/glyphs/common/shapes.ptl +++ b/font-src/glyphs/common/shapes.ptl @@ -4,6 +4,7 @@ import [Transform] from"../../support/geometry/transform.mjs" import [mix linreg clamp fallback] from"../../support/utils.mjs" import [Radical] from"../../support/gr.mjs" +import [Interpolator] from"../../support/geometry/spiro-control.mjs" glyph-module @@ -513,25 +514,12 @@ glyph-block CommonShapes : begin return : HookShape before after false args.y args.tight args.sw args.swItalicAdj args.noAdjTerminalY glyph-block-export hookstart - define [hookstart] : params [y tight sw swItalicAdj noAdjTerminalY] : return { - .type 'interpolate' - .blender hookStartBlender - .y y - .tight tight - .sw sw - .swItalicAdj swItalicAdj - .noAdjTerminalY noAdjTerminalY - } + define [hookstart] : params [y tight sw swItalicAdj noAdjTerminalY] : begin + return : Interpolator hookStartBlender : object y tight sw swItalicAdj noAdjTerminalY + glyph-block-export hookend - define [hookend] : params [y tight sw swItalicAdj noAdjTerminalY] : return { - .type 'interpolate' - .blender hookEndBlender - .y y - .tight tight - .sw sw - .swItalicAdj swItalicAdj - .noAdjTerminalY noAdjTerminalY - } + define [hookend] : params [y tight sw swItalicAdj noAdjTerminalY] : begin + return : Interpolator hookEndBlender : object y tight sw swItalicAdj noAdjTerminalY glyph-block-export Ungizmo define [Ungizmo] : glyph-proc diff --git a/font-src/glyphs/letter/latin/s.ptl b/font-src/glyphs/letter/latin/s.ptl index e4bb8fd89..71a3b8e5e 100644 --- a/font-src/glyphs/letter/latin/s.ptl +++ b/font-src/glyphs/letter/latin/s.ptl @@ -357,7 +357,7 @@ glyph-block Letter-Latin-S : begin CreateAccentedComposition 'sTildeOver' 0x1D74 's' 'tildeOverWide' derive-composites 'SCedilla' 0x15E 'S' 'cedillaExtShapeBelowOArc' - derive-composites 'sCedilla' 0x15F 's' 'cedillaExtShapeBelowOArc' + derive-composites 'sCedilla' 0x15F 's' 'cedillaExtShapeBelowSOArc' create-glyph 'mathbb/S' 0x1D54A : glyph-proc include : MarkSet.capital diff --git a/font-src/glyphs/marks/below.ptl b/font-src/glyphs/marks/below.ptl index 25a80769c..d49a5ae52 100644 --- a/font-src/glyphs/marks/below.ptl +++ b/font-src/glyphs/marks/below.ptl @@ -54,6 +54,12 @@ glyph-block Mark-Below : begin set-base-anchor 'belowBrace' markMiddle belowMarkMid include : CedillaShape (Stroke + O) + create-glyph 'cedillaExtShapeBelowSOArc' : glyph-proc + set-width 0 + set-mark-anchor 'below' markMiddle 0 markMiddle belowMarkStack + set-base-anchor 'belowBrace' markMiddle belowMarkMid + include : CedillaShape ([AdviceStroke2 2 3 XH] + O) + create-glyph 'cedillaBelow' 0x327 : glyph-proc set-width 0 set-mark-anchor 'below' markMiddle 0 markMiddle belowMarkStack diff --git a/font-src/kits/spiro-kit.mjs b/font-src/kits/spiro-kit.mjs index fde2de5c7..e4bf98dcb 100644 --- a/font-src/kits/spiro-kit.mjs +++ b/font-src/kits/spiro-kit.mjs @@ -1,48 +1,62 @@ -import * as CurveUtil from "../support/geometry/curve-util.mjs"; -import { SpiroGeometry, DiSpiroGeometry } from "../support/geometry/index.mjs"; -import { BiKnotCollector } from "../support/geometry/spiro-expand.mjs"; -import { fallback, mix, bez3 } from "../support/utils.mjs"; +import { DiSpiroGeometry, SpiroGeometry } from "../support/geometry/index.mjs"; +import { + BiKnotCollector, + ControlKnot, + Interpolator, + TerminateInstruction +} from "../support/geometry/spiro-control.mjs"; +import { bez3, fallback, mix } from "../support/utils.mjs"; /////////////////////////////////////////////////////////////////////////////////////////////////// -class DispiroImpl { + +class SpiroImplBase { constructor(bindings, args) { this.bindings = bindings; this.args = args; } - applyToGlyph(glyph) { + + createCollector(glyph) { const gizmo = glyph.gizmo || this.bindings.GlobalTransform; - const collector = new BiKnotCollector(gizmo, this.bindings.Contrast); - const { knots, closed } = prepareSpiroKnots(this.args, collector); - for (const knot of knots) { - collector.pushKnot(knot.type, knot.x, knot.y); - if (knot.af) knot.af.call(collector); - } - const dsp = new DiSpiroProxy(closed, collector, knots); + + const collector = new BiKnotCollector(this.bindings.Contrast); + for (const control of this.args) collector.add(control); + collector.unwrap(); + + return { gizmo, collector }; + } +} + +class DispiroImpl extends SpiroImplBase { + constructor(bindings, args) { + super(bindings, args); + } + applyToGlyph(glyph) { + const { gizmo, collector } = this.createCollector(glyph); + const dsp = new DiSpiroProxy(gizmo, collector); glyph.includeGeometry(dsp.geometry); return dsp; } } -class SpiroOutlineImpl { +class SpiroOutlineImpl extends SpiroImplBase { constructor(bindings, args) { - this.bindings = bindings; - this.args = args; + super(bindings, args); } applyToGlyph(glyph) { - const gizmo = glyph.gizmo || this.bindings.GlobalTransform; - const g = new CurveUtil.BezToContoursSink(gizmo); - const { knots, closed } = prepareSpiroKnots(this.args, g); - return glyph.includeGeometry(new SpiroGeometry(gizmo, closed, knots)); + const { gizmo, collector } = this.createCollector(glyph); + return glyph.includeGeometry( + new SpiroGeometry(gizmo, collector.closed, collector.controls) + ); } } class DiSpiroProxy { - constructor(closed, collector, origKnots) { + constructor(gizmo, collector) { this.geometry = new DiSpiroGeometry( - collector.gizmo, + gizmo, collector.contrast, - closed, - collector.controlKnots + collector.closed, + collector.controls ); - this.m_origKnots = origKnots; + this.m_origKnots = collector.controls; } get knots() { return this.m_origKnots; @@ -54,69 +68,23 @@ class DiSpiroProxy { return this.geometry.expand().rhsUntransformed; } } -function prepareSpiroKnots(_knots, s) { - let knots = [..._knots]; - while (knots[0] && knots[0] instanceof Function) { - knots[0].call(s); - knots.splice(0, 1); - } - const closed = dropTailKnot(knots); - knots = flatten(s, knots); - return { knots, closed }; -} -function dropTailKnot(knots) { - let last = knots[knots.length - 1]; - if (last && (last.type === "close" || last.type === "end")) { - knots.length = knots.length - 1; - return last.type === "close"; - } else { - return false; - } -} -function flatten(s, knots0) { - let knots = []; - flattenImpl(knots, knots0); - let unwrapped = false; - for (let j = 0; j < knots.length; j = j + 1) - if (knots[j] && knots[j].type === "interpolate") { - const kBefore = knots[nCyclic(j - 1, knots.length)]; - const kAfter = knots[nCyclic(j + 1, knots.length)]; - knots[j] = knots[j].blender(kBefore, kAfter, knots[j]); - unwrapped = true; - } - if (unwrapped) return flatten(s, knots); - return knots; -} -function flattenImpl(sink, knots) { - for (const p of knots) { - if (p instanceof Array) flattenImpl(sink, p); - else sink.push(p); - } -} -function nCyclic(p, n) { - return (p + n + n) % n; -} + export function SetupBuilders(bindings) { - const { Contrast, GlobalTransform, Stroke, Superness } = bindings; - function validateCoord(x) { - if (!isFinite(x)) throw new TypeError("NaN detected"); - return x; - } + const { Stroke, Superness } = bindings; function KnotType(type) { - return (x, y, f) => ({ - type, - x: validateCoord(x), - y: validateCoord(y), - af: f - }); + return (x, y, f) => { + if (!isFinite(x)) throw new TypeError("NaN detected for X"); + if (!isFinite(y)) throw new TypeError("NaN detected for Y"); + return new ControlKnot(type, x, y, f); + }; } const g4 = KnotType("g4"); const g2 = KnotType("g2"); const corner = KnotType("corner"); const flat = KnotType("left"); const curl = KnotType("right"); - const close = f => ({ type: "close", af: f }); - const end = f => ({ type: "end", af: f }); + const close = f => new TerminateInstruction("close", f); + const end = f => new TerminateInstruction("end", f); const straight = { l: flat, r: curl }; { let directions = [ @@ -148,9 +116,13 @@ export function SetupBuilders(bindings) { } } } + function widths(l, r) { return function () { - return this.setWidth ? this.setWidth(validateCoord(l), validateCoord(r)) : void 0; + if (!isFinite(l)) throw new TypeError("NaN detected for left width"); + if (!isFinite(r)) throw new TypeError("NaN detected for right width"); + + if (this.setWidth) this.setWidth(l, r); }; } widths.lhs = function (w) { @@ -162,46 +134,49 @@ export function SetupBuilders(bindings) { widths.center = function (w) { return widths(fallback(w, Stroke) / 2, fallback(w, Stroke) / 2); }; + function heading(d) { return function () { - return this.headsTo ? this.headsTo(d) : void 0; + if (this.headsTo) this.headsTo(d); }; } widths.heading = function (l, r, d) { return function () { if (this.setWidth) this.setWidth(l, r); - return this.headsTo ? this.headsTo(d) : void 0; + if (this.headsTo) this.headsTo(d); }; }; widths.lhs.heading = function (w, d) { return function () { if (this.setWidth) this.setWidth(fallback(w, Stroke), 0); - return this.headsTo ? this.headsTo(d) : void 0; + if (this.headsTo) this.headsTo(d); }; }; widths.rhs.heading = function (w, d) { return function () { if (this.setWidth) this.setWidth(0, fallback(w, Stroke)); - return this.headsTo ? this.headsTo(d) : void 0; + if (this.headsTo) this.headsTo(d); }; }; widths.center.heading = function (w, d) { return function () { if (this.setWidth) this.setWidth(fallback(w, Stroke) / 2, fallback(w, Stroke) / 2); - return this.headsTo ? this.headsTo(d) : void 0; + if (this.headsTo) this.headsTo(d); }; }; + function disableContrast() { return function () { - return (this.contrast = 1); + if (this.setContrast) this.setContrast(1); }; } function unimportant() { - return this.setUnimportant ? this.setUnimportant() : void 0; + if (this.setUnimportant) this.setUnimportant(1); } function important() { return void 0; } + function afInterpolate(before, after, args) { return g4( mix(before.x, after.x, args.rx), @@ -272,23 +247,24 @@ export function SetupBuilders(bindings) { } function alsoThru(rx, ry, raf) { - return { type: "interpolate", rx, ry, raf, blender: afInterpolate }; + return Interpolator(afInterpolate, { rx, ry, raf }); } alsoThru.withOffset = function (rx, ry, deltaX, deltaY, raf) { - return { type: "interpolate", rx, ry, deltaX, deltaY, raf, blender: afInterpolateDelta }; + return Interpolator(afInterpolateDelta, { rx, ry, deltaX, deltaY, raf }); }; alsoThru.g2 = function (rx, ry, raf) { - return { type: "interpolate", rx, ry, raf, blender: afInterpolateG2 }; + return Interpolator(afInterpolateG2, { rx, ry, raf }); }; - function alsoThruThem(es, raf, ty) { - return { type: "interpolate", rs: es, raf, ty, blender: afInterpolateThem }; + function alsoThruThem(rs, raf, ty) { + return Interpolator(afInterpolateThem, { rs, raf, ty }); } - alsoThruThem.withOffset = function (es, raf, ty) { - return { type: "interpolate", rs: es, raf, ty, blender: afInterpolateThemWithDelta }; + alsoThruThem.withOffset = function (rs, raf, ty) { + return Interpolator(afInterpolateThemWithDelta, { rs, raf, ty }); }; - alsoThruThem.fromTWithOffset = function (es, raf, ty) { - return { type: "interpolate", rs: es, raf, ty, blender: afInterpolateThemFromTWithDelta }; + alsoThruThem.fromTWithOffset = function (rs, raf, ty) { + return Interpolator(afInterpolateThemFromTWithDelta, { rs, raf, ty }); }; + function bezControlsImpl(x1, y1, x2, y2, samples, raf, ty) { let rs = []; for (let j = 1; j < samples; j = j + 1) @@ -312,6 +288,7 @@ export function SetupBuilders(bindings) { raf ); } + let DEFAULT_STEPS = 6; let [buildHV, buildVH] = (function (cache) { function build(samples, _superness) { diff --git a/font-src/support/geometry/index.mjs b/font-src/support/geometry/index.mjs index 484886bb2..d79f087d4 100644 --- a/font-src/support/geometry/index.mjs +++ b/font-src/support/geometry/index.mjs @@ -114,8 +114,7 @@ export class SpiroGeometry extends GeometryBase { export class DiSpiroGeometry extends GeometryBase { constructor(gizmo, contrast, closed, biKnots) { super(); - this.m_biKnots = []; - for (const k of biKnots) this.m_biKnots.push(k.clone()); + this.m_biKnots = biKnots; // untransformed this.m_closed = closed; this.m_gizmo = gizmo; this.m_contrast = contrast; @@ -149,7 +148,7 @@ export class DiSpiroGeometry extends GeometryBase { this.m_gizmo, this.m_contrast, this.m_closed, - this.m_biKnots.map(k => k.clone()) + this.m_biKnots ); expander.initializeNormals(); expander.iterateNormals(); diff --git a/font-src/support/geometry/spiro-control.mjs b/font-src/support/geometry/spiro-control.mjs new file mode 100644 index 000000000..457b913f9 --- /dev/null +++ b/font-src/support/geometry/spiro-control.mjs @@ -0,0 +1,188 @@ +import * as Format from "../util/formatter.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.controls = []; // all the control items + this.closed = false; // whether the shape is closed + this.needsUnwrap = false; // whether there are interpolators + this.afterPreFunction = false; // whether we are really processing knots + } + add(c) { + if (c instanceof Function) { + if (this.afterPreFunction) throw new Error("Invalid spiro control sequence"); + c.call(this); + } else if (Array.isArray(c)) { + for (const item of c) this.add(item); + } else if (c instanceof ControlKnot) { + this.afterPreFunction = true; + this.pushKnot(c); + } else if (c instanceof TerminateInstruction) { + this.afterPreFunction = true; + if (c.type === "close") this.closed = true; + c.applyTo(this); + } else if (c instanceof InterpolatorBase) { + this.afterPreFunction = true; + this.controls.push(c); + this.needsUnwrap = true; + } else { + throw new Error("Invalid spiro control type"); + } + } + unwrap() { + while (this.needsUnwrap) { + const cs = [...this.controls]; + this.controls.length = 0; + this.needsUnwrap = false; + this.lastKnot = null; + this.unwrapImpl(cs); + } + for (const item of this.controls) { + if (!(item instanceof BiKnot)) throw new Error("Invalid control sequence"); + item.originalKnot = null; + } + } + unwrapImpl(cs) { + let tmp = []; + for (let j = 0; j < cs.length; j++) { + if (cs[j] instanceof InterpolatorBase) { + const kBefore = cs[nCyclic(j - 1, cs.length)]; + const kAfter = cs[nCyclic(j + 1, cs.length)]; + const blended = cs[j].blender(kBefore.originalKnot, kAfter.originalKnot, cs[j]); + tmp.push(blended); + } else { + tmp.push(cs[j].originalKnot); + } + } + + this.add(tmp); + } + + 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); + } + k.originalKnot = c; + + 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; + } +} + +class BiKnot { + constructor(type, x, y, d1, d2) { + this.type = type; + this.x = x; + this.y = y; + this.d1 = d1; + this.d2 = d2; + this.origTangent = null; + this.proposedNormal = null; + this.unimportant = 0; + this.originalKnot = 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; + } + toShapeString() { + return Format.tuple( + this.type, + Format.n(this.x), + Format.n(this.y), + this.d1 == null ? "" : Format.n(this.d1), + this.d2 == null ? "" : Format.n(this.d2), + this.origTangent + ? Format.tuple(Format.n(this.origTangent.x), Format.n(this.origTangent.y)) + : "", + this.proposedNormal + ? Format.tuple(Format.n(this.proposedNormal.x), Format.n(this.proposedNormal.y)) + : "", + this.unimportant + ); + } +} + +function nCyclic(p, n) { + return (p + n + n) % n; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +export class ControlKnot { + constructor(type, x, y, af) { + this.type = type; + this.x = x; + this.y = y; + this.af = af; + } + applyTo(ctx) { + if (this.af) this.af.call(ctx); + } +} +export class TerminateInstruction { + constructor(type, af) { + this.type = type; + this.af = af; + } + applyTo(ctx) { + if (this.af) throw new Error("Unreachable"); + // if (this.af) this.af.call(ctx); + } +} +export class InterpolatorBase { + constructor(blender) { + this.type = "interpolate"; + this.blender = blender; + } +} +export function Interpolator(blender, restParameters) { + const base = new InterpolatorBase(blender); + const interpolator = Object.create(base); + for (const prop in restParameters) interpolator[prop] = restParameters[prop]; + return interpolator; +} diff --git a/font-src/support/geometry/spiro-expand.mjs b/font-src/support/geometry/spiro-expand.mjs index ccf93f432..5f05cf6f6 100644 --- a/font-src/support/geometry/spiro-expand.mjs +++ b/font-src/support/geometry/spiro-expand.mjs @@ -1,111 +1,39 @@ import * as SpiroJs from "spiro"; -import * as Format from "../util/formatter.mjs"; import { linreg } from "../utils.mjs"; -class BiKnot { - constructor(type, x, y, d1, d2) { - this.type = type; - this.x = x; - this.y = y; - this.d1 = d1; - this.d2 = d2; - this.origTangent = null; - this.proposedNormal = null; - this.unimportant = 0; - } - 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; - } - toShapeString() { - return Format.tuple( - this.type, - Format.n(this.x), - Format.n(this.y), - this.d1 == null ? "" : Format.n(this.d1), - this.d2 == null ? "" : Format.n(this.d2), - this.origTangent - ? Format.tuple(Format.n(this.origTangent.x), Format.n(this.origTangent.y)) - : "", - this.proposedNormal - ? Format.tuple(Format.n(this.proposedNormal.x), Format.n(this.proposedNormal.y)) - : "", - this.unimportant - ); - } -} - -export class BiKnotCollector { - constructor(gizmo, contrast) { - this.gizmo = gizmo; - this.contrast = contrast; - this.controlKnots = []; - this.defaultD1 = 0; - this.defaultD2 = 0; - } - pushKnot(type, x, y) { - const tfZ = this.gizmo.applyXY(x, y); - const k0 = this.controlKnots[this.controlKnots.length - 1]; - if (k0) { - this.controlKnots.push(new BiKnot(type, tfZ.x, tfZ.y, k0.d1, k0.d2)); - } else { - this.controlKnots.push(new BiKnot(type, tfZ.x, tfZ.y, this.defaultD1, this.defaultD2)); - } - } - setWidth(l, r) { - const k0 = this.controlKnots[this.controlKnots.length - 1]; - if (k0) { - (k0.d1 = l), (k0.d2 = r); - } else { - (this.defaultD1 = l), (this.defaultD2 = r); - } - } - headsTo(direction) { - const transformedDirection = this.gizmo.applyOffset(direction); - const k0 = this.controlKnots[this.controlKnots.length - 1]; - if (k0) k0.proposedNormal = transformedDirection; - } - setType(type) { - const k0 = this.controlKnots[this.controlKnots.length - 1]; - if (k0) k0.type = type; - } - setUnimportant() { - const k0 = this.controlKnots[this.controlKnots.length - 1]; - if (k0) k0.unimportant = 1; - } -} +/////////////////////////////////////////////////////////////////////////////////////////////////// export class SpiroExpander { - constructor(gizmo, contrast, closed, cks) { - this.gizmo = gizmo; - this.contrast = contrast; - this.closed = closed; - this.controlKnots = cks; + constructor(gizmo, contrast, closed, biKnots) { + this.m_gizmo = gizmo; + this.m_contrast = contrast; + this.m_closed = closed; + this.m_biKnots = []; + for (const knot of biKnots) { + this.m_biKnots.push(knot.withGizmo(gizmo)); + } } initializeNormals() { - const normalRectifier = new NormalRectifier(this.controlKnots, this.gizmo); - SpiroJs.spiroToArcsOnContext(this.controlKnots, this.closed, normalRectifier); + const normalRectifier = new NormalRectifier(this.m_biKnots, this.m_gizmo); + SpiroJs.spiroToArcsOnContext(this.m_biKnots, this.m_closed, normalRectifier); } iterateNormals() { const centerBone = this.getPass2Knots(); - const normalRectifier = new NormalRectifier(this.controlKnots, this.gizmo); - SpiroJs.spiroToArcsOnContext(centerBone, this.closed, normalRectifier); + const normalRectifier = new NormalRectifier(this.m_biKnots, this.m_gizmo); + SpiroJs.spiroToArcsOnContext(centerBone, this.m_closed, normalRectifier); } getPass2Knots() { - const expanded = this.expand(this.contrast); + const expanded = this.expand(this.m_contrast); const middles = []; - for (let j = 0; j < this.controlKnots.length; j++) { - const lhs = this.gizmo.unapply(expanded.lhs[j]); - const rhs = this.gizmo.unapply(expanded.rhs[j]); + for (let j = 0; j < this.m_biKnots.length; j++) { + const lhs = this.m_gizmo.unapply(expanded.lhs[j]); + const rhs = this.m_gizmo.unapply(expanded.rhs[j]); middles[j] = { x: 0.5 * (lhs.x + rhs.x), y: 0.5 * (lhs.y + rhs.y), - type: this.controlKnots[j].type, - unimportant: this.controlKnots[j].unimportant + type: this.m_biKnots[j].type, + unimportant: this.m_biKnots[j].unimportant }; } return middles; @@ -114,8 +42,8 @@ export class SpiroExpander { const lhs = [], rhs = []; // Initialize knots - for (let j = 0; j < this.controlKnots.length; j++) { - const knot = this.controlKnots[j]; + for (let j = 0; j < this.m_biKnots.length; j++) { + const knot = this.m_biKnots[j]; lhs[j] = { type: knot.type, unimportant: knot.unimportant, @@ -130,16 +58,16 @@ export class SpiroExpander { }; } // Create important knots - for (let j = 0; j < this.controlKnots.length; j++) { - const knot = this.controlKnots[j]; + for (let j = 0; j < this.m_biKnots.length; j++) { + const knot = this.m_biKnots[j]; if (knot.unimportant) continue; let dx, dy; if (knot.proposedNormal) { dx = knot.proposedNormal.x; dy = knot.proposedNormal.y; } else { - dx = normalX(knot.origTangent, this.contrast); - dy = normalY(knot.origTangent, this.contrast); + dx = normalX(knot.origTangent, this.m_contrast); + dy = normalY(knot.origTangent, this.m_contrast); } lhs[j].x = knot.x + knot.d1 * dx; lhs[j].y = knot.y + knot.d1 * dy; @@ -151,34 +79,34 @@ export class SpiroExpander { const lhsUntransformed = [], rhsUntransformed = []; for (const z of lhs) { - const u = this.gizmo.unapply(z); + const u = this.m_gizmo.unapply(z); lhsUntransformed.push({ type: z.type, x: u.x, y: u.y }); } for (const z of rhs) { - const u = this.gizmo.unapply(z); + const u = this.m_gizmo.unapply(z); rhsUntransformed.push({ type: z.type, x: u.x, y: u.y }); } return { lhs, rhs, lhsUntransformed, rhsUntransformed }; } interpolateUnimportantKnots(lhs, rhs) { - for (let j = 0; j < this.controlKnots.length; j++) { - const knot = this.controlKnots[j]; + for (let j = 0; j < this.m_biKnots.length; j++) { + const knot = this.m_biKnots[j]; if (!knot.unimportant) continue; let jBefore, jAfter; - for (jBefore = j - 1; cyNth(this.controlKnots, jBefore).unimportant; jBefore--); - for (jAfter = j + 1; cyNth(this.controlKnots, jAfter).unimportant; jAfter++); - const knotBefore = this.gizmo.unapply(cyNth(this.controlKnots, jBefore)), - knotAfter = this.gizmo.unapply(cyNth(this.controlKnots, jAfter)), - ref = this.gizmo.unapply(knot), - lhsBefore = this.gizmo.unapply(cyNth(lhs, jBefore)), - lhsAfter = this.gizmo.unapply(cyNth(lhs, jAfter)), - rhsBefore = this.gizmo.unapply(cyNth(rhs, jBefore)), - rhsAfter = this.gizmo.unapply(cyNth(rhs, jAfter)); - const lhsTf = this.gizmo.applyXY( + for (jBefore = j - 1; cyNth(this.m_biKnots, jBefore).unimportant; jBefore--); + for (jAfter = j + 1; cyNth(this.m_biKnots, jAfter).unimportant; jAfter++); + const knotBefore = this.m_gizmo.unapply(cyNth(this.m_biKnots, jBefore)), + knotAfter = this.m_gizmo.unapply(cyNth(this.m_biKnots, jAfter)), + ref = this.m_gizmo.unapply(knot), + lhsBefore = this.m_gizmo.unapply(cyNth(lhs, jBefore)), + lhsAfter = this.m_gizmo.unapply(cyNth(lhs, jAfter)), + rhsBefore = this.m_gizmo.unapply(cyNth(rhs, jBefore)), + rhsAfter = this.m_gizmo.unapply(cyNth(rhs, jAfter)); + const lhsTf = this.m_gizmo.applyXY( linreg(knotBefore.x, lhsBefore.x, knotAfter.x, lhsAfter.x, ref.x), linreg(knotBefore.y, lhsBefore.y, knotAfter.y, lhsAfter.y, ref.y) ); - const rhsTf = this.gizmo.applyXY( + const rhsTf = this.m_gizmo.applyXY( linreg(knotBefore.x, rhsBefore.x, knotAfter.x, rhsAfter.x, ref.x), linreg(knotBefore.y, rhsBefore.y, knotAfter.y, rhsAfter.y, ref.y) ); @@ -189,33 +117,33 @@ export class SpiroExpander { } class NormalRectifier { constructor(stage1ControlKnots, gizmo) { - this.gizmo = gizmo; - this.controlKnots = stage1ControlKnots; - this.nKnotsProcessed = 0; + this.m_gizmo = gizmo; + this.m_biKnots = stage1ControlKnots; + this.m_nKnotsProcessed = 0; } beginShape() {} endShape() {} moveTo(x, y) { - this.nKnotsProcessed += 1; + this.m_nKnotsProcessed += 1; } arcTo(arc, x, y) { - if (this.nKnotsProcessed === 1) { - const d = this.gizmo.applyOffsetXY(arc.deriveX0, arc.deriveY0); + if (this.m_nKnotsProcessed === 1) { + const d = this.m_gizmo.applyOffsetXY(arc.deriveX0, arc.deriveY0); if (isTangentValid(d)) { - this.controlKnots[0].origTangent = d; + this.m_biKnots[0].origTangent = d; } else { throw new Error("NaN angle detected."); } } - if (this.controlKnots[this.nKnotsProcessed]) { - const d = this.gizmo.applyOffsetXY(arc.deriveX1, arc.deriveY1); + if (this.m_biKnots[this.m_nKnotsProcessed]) { + const d = this.m_gizmo.applyOffsetXY(arc.deriveX1, arc.deriveY1); if (isTangentValid(d)) { - this.controlKnots[this.nKnotsProcessed].origTangent = d; + this.m_biKnots[this.m_nKnotsProcessed].origTangent = d; } else { throw new Error("NaN angle detected."); } } - this.nKnotsProcessed += 1; + this.m_nKnotsProcessed += 1; } }