Cleanup
This commit is contained in:
parent
b548e9c1cb
commit
83df8bea3c
8 changed files with 329 additions and 243 deletions
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
188
font-src/support/geometry/spiro-control.mjs
Normal file
188
font-src/support/geometry/spiro-control.mjs
Normal 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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue