This commit is contained in:
be5invis 2023-03-09 05:12:06 -08:00
parent b548e9c1cb
commit 83df8bea3c
8 changed files with 329 additions and 243 deletions

View file

@ -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) {

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 collector = new BiKnotCollector(this.bindings.Contrast);
for (const control of this.args) collector.add(control);
collector.unwrap();
return { gizmo, collector };
}
const dsp = new DiSpiroProxy(closed, collector, knots);
}
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) {

View file

@ -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();

View file

@ -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;
}

View file

@ -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;
}
}