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";
|
import { encode, decode } from "@msgpack/msgpack";
|
||||||
|
|
||||||
const Edition = 24;
|
const Edition = 25;
|
||||||
const MAX_AGE = 16;
|
const MAX_AGE = 16;
|
||||||
class GfEntry {
|
class GfEntry {
|
||||||
constructor(age, value) {
|
constructor(age, value) {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import [Transform] from"../../support/geometry/transform.mjs"
|
||||||
|
|
||||||
import [mix linreg clamp fallback] from"../../support/utils.mjs"
|
import [mix linreg clamp fallback] from"../../support/utils.mjs"
|
||||||
import [Radical] from"../../support/gr.mjs"
|
import [Radical] from"../../support/gr.mjs"
|
||||||
|
import [Interpolator] from"../../support/geometry/spiro-control.mjs"
|
||||||
|
|
||||||
glyph-module
|
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
|
return : HookShape before after false args.y args.tight args.sw args.swItalicAdj args.noAdjTerminalY
|
||||||
|
|
||||||
glyph-block-export hookstart
|
glyph-block-export hookstart
|
||||||
define [hookstart] : params [y tight sw swItalicAdj noAdjTerminalY] : return {
|
define [hookstart] : params [y tight sw swItalicAdj noAdjTerminalY] : begin
|
||||||
.type 'interpolate'
|
return : Interpolator hookStartBlender : object y tight sw swItalicAdj noAdjTerminalY
|
||||||
.blender hookStartBlender
|
|
||||||
.y y
|
|
||||||
.tight tight
|
|
||||||
.sw sw
|
|
||||||
.swItalicAdj swItalicAdj
|
|
||||||
.noAdjTerminalY noAdjTerminalY
|
|
||||||
}
|
|
||||||
glyph-block-export hookend
|
glyph-block-export hookend
|
||||||
define [hookend] : params [y tight sw swItalicAdj noAdjTerminalY] : return {
|
define [hookend] : params [y tight sw swItalicAdj noAdjTerminalY] : begin
|
||||||
.type 'interpolate'
|
return : Interpolator hookEndBlender : object y tight sw swItalicAdj noAdjTerminalY
|
||||||
.blender hookEndBlender
|
|
||||||
.y y
|
|
||||||
.tight tight
|
|
||||||
.sw sw
|
|
||||||
.swItalicAdj swItalicAdj
|
|
||||||
.noAdjTerminalY noAdjTerminalY
|
|
||||||
}
|
|
||||||
|
|
||||||
glyph-block-export Ungizmo
|
glyph-block-export Ungizmo
|
||||||
define [Ungizmo] : glyph-proc
|
define [Ungizmo] : glyph-proc
|
||||||
|
|
|
@ -357,7 +357,7 @@ glyph-block Letter-Latin-S : begin
|
||||||
CreateAccentedComposition 'sTildeOver' 0x1D74 's' 'tildeOverWide'
|
CreateAccentedComposition 'sTildeOver' 0x1D74 's' 'tildeOverWide'
|
||||||
|
|
||||||
derive-composites 'SCedilla' 0x15E 'S' 'cedillaExtShapeBelowOArc'
|
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
|
create-glyph 'mathbb/S' 0x1D54A : glyph-proc
|
||||||
include : MarkSet.capital
|
include : MarkSet.capital
|
||||||
|
|
|
@ -54,6 +54,12 @@ glyph-block Mark-Below : begin
|
||||||
set-base-anchor 'belowBrace' markMiddle belowMarkMid
|
set-base-anchor 'belowBrace' markMiddle belowMarkMid
|
||||||
include : CedillaShape (Stroke + O)
|
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
|
create-glyph 'cedillaBelow' 0x327 : glyph-proc
|
||||||
set-width 0
|
set-width 0
|
||||||
set-mark-anchor 'below' markMiddle 0 markMiddle belowMarkStack
|
set-mark-anchor 'below' markMiddle 0 markMiddle belowMarkStack
|
||||||
|
|
|
@ -1,48 +1,62 @@
|
||||||
import * as CurveUtil from "../support/geometry/curve-util.mjs";
|
import { DiSpiroGeometry, SpiroGeometry } from "../support/geometry/index.mjs";
|
||||||
import { SpiroGeometry, DiSpiroGeometry } from "../support/geometry/index.mjs";
|
import {
|
||||||
import { BiKnotCollector } from "../support/geometry/spiro-expand.mjs";
|
BiKnotCollector,
|
||||||
import { fallback, mix, bez3 } from "../support/utils.mjs";
|
ControlKnot,
|
||||||
|
Interpolator,
|
||||||
|
TerminateInstruction
|
||||||
|
} from "../support/geometry/spiro-control.mjs";
|
||||||
|
import { bez3, fallback, mix } from "../support/utils.mjs";
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
class DispiroImpl {
|
|
||||||
|
class SpiroImplBase {
|
||||||
constructor(bindings, args) {
|
constructor(bindings, args) {
|
||||||
this.bindings = bindings;
|
this.bindings = bindings;
|
||||||
this.args = args;
|
this.args = args;
|
||||||
}
|
}
|
||||||
applyToGlyph(glyph) {
|
|
||||||
|
createCollector(glyph) {
|
||||||
const gizmo = glyph.gizmo || this.bindings.GlobalTransform;
|
const gizmo = glyph.gizmo || this.bindings.GlobalTransform;
|
||||||
const collector = new BiKnotCollector(gizmo, this.bindings.Contrast);
|
|
||||||
const { knots, closed } = prepareSpiroKnots(this.args, collector);
|
const collector = new BiKnotCollector(this.bindings.Contrast);
|
||||||
for (const knot of knots) {
|
for (const control of this.args) collector.add(control);
|
||||||
collector.pushKnot(knot.type, knot.x, knot.y);
|
collector.unwrap();
|
||||||
if (knot.af) knot.af.call(collector);
|
|
||||||
|
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);
|
glyph.includeGeometry(dsp.geometry);
|
||||||
return dsp;
|
return dsp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
class SpiroOutlineImpl {
|
class SpiroOutlineImpl extends SpiroImplBase {
|
||||||
constructor(bindings, args) {
|
constructor(bindings, args) {
|
||||||
this.bindings = bindings;
|
super(bindings, args);
|
||||||
this.args = args;
|
|
||||||
}
|
}
|
||||||
applyToGlyph(glyph) {
|
applyToGlyph(glyph) {
|
||||||
const gizmo = glyph.gizmo || this.bindings.GlobalTransform;
|
const { gizmo, collector } = this.createCollector(glyph);
|
||||||
const g = new CurveUtil.BezToContoursSink(gizmo);
|
return glyph.includeGeometry(
|
||||||
const { knots, closed } = prepareSpiroKnots(this.args, g);
|
new SpiroGeometry(gizmo, collector.closed, collector.controls)
|
||||||
return glyph.includeGeometry(new SpiroGeometry(gizmo, closed, knots));
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
class DiSpiroProxy {
|
class DiSpiroProxy {
|
||||||
constructor(closed, collector, origKnots) {
|
constructor(gizmo, collector) {
|
||||||
this.geometry = new DiSpiroGeometry(
|
this.geometry = new DiSpiroGeometry(
|
||||||
collector.gizmo,
|
gizmo,
|
||||||
collector.contrast,
|
collector.contrast,
|
||||||
closed,
|
collector.closed,
|
||||||
collector.controlKnots
|
collector.controls
|
||||||
);
|
);
|
||||||
this.m_origKnots = origKnots;
|
this.m_origKnots = collector.controls;
|
||||||
}
|
}
|
||||||
get knots() {
|
get knots() {
|
||||||
return this.m_origKnots;
|
return this.m_origKnots;
|
||||||
|
@ -54,69 +68,23 @@ class DiSpiroProxy {
|
||||||
return this.geometry.expand().rhsUntransformed;
|
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) {
|
export function SetupBuilders(bindings) {
|
||||||
const { Contrast, GlobalTransform, Stroke, Superness } = bindings;
|
const { Stroke, Superness } = bindings;
|
||||||
function validateCoord(x) {
|
|
||||||
if (!isFinite(x)) throw new TypeError("NaN detected");
|
|
||||||
return x;
|
|
||||||
}
|
|
||||||
function KnotType(type) {
|
function KnotType(type) {
|
||||||
return (x, y, f) => ({
|
return (x, y, f) => {
|
||||||
type,
|
if (!isFinite(x)) throw new TypeError("NaN detected for X");
|
||||||
x: validateCoord(x),
|
if (!isFinite(y)) throw new TypeError("NaN detected for Y");
|
||||||
y: validateCoord(y),
|
return new ControlKnot(type, x, y, f);
|
||||||
af: f
|
};
|
||||||
});
|
|
||||||
}
|
}
|
||||||
const g4 = KnotType("g4");
|
const g4 = KnotType("g4");
|
||||||
const g2 = KnotType("g2");
|
const g2 = KnotType("g2");
|
||||||
const corner = KnotType("corner");
|
const corner = KnotType("corner");
|
||||||
const flat = KnotType("left");
|
const flat = KnotType("left");
|
||||||
const curl = KnotType("right");
|
const curl = KnotType("right");
|
||||||
const close = f => ({ type: "close", af: f });
|
const close = f => new TerminateInstruction("close", f);
|
||||||
const end = f => ({ type: "end", af: f });
|
const end = f => new TerminateInstruction("end", f);
|
||||||
const straight = { l: flat, r: curl };
|
const straight = { l: flat, r: curl };
|
||||||
{
|
{
|
||||||
let directions = [
|
let directions = [
|
||||||
|
@ -148,9 +116,13 @@ export function SetupBuilders(bindings) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function widths(l, r) {
|
function widths(l, r) {
|
||||||
return function () {
|
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) {
|
widths.lhs = function (w) {
|
||||||
|
@ -162,46 +134,49 @@ export function SetupBuilders(bindings) {
|
||||||
widths.center = function (w) {
|
widths.center = function (w) {
|
||||||
return widths(fallback(w, Stroke) / 2, fallback(w, Stroke) / 2);
|
return widths(fallback(w, Stroke) / 2, fallback(w, Stroke) / 2);
|
||||||
};
|
};
|
||||||
|
|
||||||
function heading(d) {
|
function heading(d) {
|
||||||
return function () {
|
return function () {
|
||||||
return this.headsTo ? this.headsTo(d) : void 0;
|
if (this.headsTo) this.headsTo(d);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
widths.heading = function (l, r, d) {
|
widths.heading = function (l, r, d) {
|
||||||
return function () {
|
return function () {
|
||||||
if (this.setWidth) this.setWidth(l, r);
|
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) {
|
widths.lhs.heading = function (w, d) {
|
||||||
return function () {
|
return function () {
|
||||||
if (this.setWidth) this.setWidth(fallback(w, Stroke), 0);
|
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) {
|
widths.rhs.heading = function (w, d) {
|
||||||
return function () {
|
return function () {
|
||||||
if (this.setWidth) this.setWidth(0, fallback(w, Stroke));
|
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) {
|
widths.center.heading = function (w, d) {
|
||||||
return function () {
|
return function () {
|
||||||
if (this.setWidth) this.setWidth(fallback(w, Stroke) / 2, fallback(w, Stroke) / 2);
|
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() {
|
function disableContrast() {
|
||||||
return function () {
|
return function () {
|
||||||
return (this.contrast = 1);
|
if (this.setContrast) this.setContrast(1);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function unimportant() {
|
function unimportant() {
|
||||||
return this.setUnimportant ? this.setUnimportant() : void 0;
|
if (this.setUnimportant) this.setUnimportant(1);
|
||||||
}
|
}
|
||||||
function important() {
|
function important() {
|
||||||
return void 0;
|
return void 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function afInterpolate(before, after, args) {
|
function afInterpolate(before, after, args) {
|
||||||
return g4(
|
return g4(
|
||||||
mix(before.x, after.x, args.rx),
|
mix(before.x, after.x, args.rx),
|
||||||
|
@ -272,23 +247,24 @@ export function SetupBuilders(bindings) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function alsoThru(rx, ry, raf) {
|
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) {
|
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) {
|
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) {
|
function alsoThruThem(rs, raf, ty) {
|
||||||
return { type: "interpolate", rs: es, raf, ty, blender: afInterpolateThem };
|
return Interpolator(afInterpolateThem, { rs, raf, ty });
|
||||||
}
|
}
|
||||||
alsoThruThem.withOffset = function (es, raf, ty) {
|
alsoThruThem.withOffset = function (rs, raf, ty) {
|
||||||
return { type: "interpolate", rs: es, raf, ty, blender: afInterpolateThemWithDelta };
|
return Interpolator(afInterpolateThemWithDelta, { rs, raf, ty });
|
||||||
};
|
};
|
||||||
alsoThruThem.fromTWithOffset = function (es, raf, ty) {
|
alsoThruThem.fromTWithOffset = function (rs, raf, ty) {
|
||||||
return { type: "interpolate", rs: es, raf, ty, blender: afInterpolateThemFromTWithDelta };
|
return Interpolator(afInterpolateThemFromTWithDelta, { rs, raf, ty });
|
||||||
};
|
};
|
||||||
|
|
||||||
function bezControlsImpl(x1, y1, x2, y2, samples, raf, ty) {
|
function bezControlsImpl(x1, y1, x2, y2, samples, raf, ty) {
|
||||||
let rs = [];
|
let rs = [];
|
||||||
for (let j = 1; j < samples; j = j + 1)
|
for (let j = 1; j < samples; j = j + 1)
|
||||||
|
@ -312,6 +288,7 @@ export function SetupBuilders(bindings) {
|
||||||
raf
|
raf
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let DEFAULT_STEPS = 6;
|
let DEFAULT_STEPS = 6;
|
||||||
let [buildHV, buildVH] = (function (cache) {
|
let [buildHV, buildVH] = (function (cache) {
|
||||||
function build(samples, _superness) {
|
function build(samples, _superness) {
|
||||||
|
|
|
@ -114,8 +114,7 @@ export class SpiroGeometry extends GeometryBase {
|
||||||
export class DiSpiroGeometry extends GeometryBase {
|
export class DiSpiroGeometry extends GeometryBase {
|
||||||
constructor(gizmo, contrast, closed, biKnots) {
|
constructor(gizmo, contrast, closed, biKnots) {
|
||||||
super();
|
super();
|
||||||
this.m_biKnots = [];
|
this.m_biKnots = biKnots; // untransformed
|
||||||
for (const k of biKnots) this.m_biKnots.push(k.clone());
|
|
||||||
this.m_closed = closed;
|
this.m_closed = closed;
|
||||||
this.m_gizmo = gizmo;
|
this.m_gizmo = gizmo;
|
||||||
this.m_contrast = contrast;
|
this.m_contrast = contrast;
|
||||||
|
@ -149,7 +148,7 @@ export class DiSpiroGeometry extends GeometryBase {
|
||||||
this.m_gizmo,
|
this.m_gizmo,
|
||||||
this.m_contrast,
|
this.m_contrast,
|
||||||
this.m_closed,
|
this.m_closed,
|
||||||
this.m_biKnots.map(k => k.clone())
|
this.m_biKnots
|
||||||
);
|
);
|
||||||
expander.initializeNormals();
|
expander.initializeNormals();
|
||||||
expander.iterateNormals();
|
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 SpiroJs from "spiro";
|
||||||
|
|
||||||
import * as Format from "../util/formatter.mjs";
|
|
||||||
import { linreg } from "../utils.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 {
|
export class SpiroExpander {
|
||||||
constructor(gizmo, contrast, closed, cks) {
|
constructor(gizmo, contrast, closed, biKnots) {
|
||||||
this.gizmo = gizmo;
|
this.m_gizmo = gizmo;
|
||||||
this.contrast = contrast;
|
this.m_contrast = contrast;
|
||||||
this.closed = closed;
|
this.m_closed = closed;
|
||||||
this.controlKnots = cks;
|
this.m_biKnots = [];
|
||||||
|
for (const knot of biKnots) {
|
||||||
|
this.m_biKnots.push(knot.withGizmo(gizmo));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
initializeNormals() {
|
initializeNormals() {
|
||||||
const normalRectifier = new NormalRectifier(this.controlKnots, this.gizmo);
|
const normalRectifier = new NormalRectifier(this.m_biKnots, this.m_gizmo);
|
||||||
SpiroJs.spiroToArcsOnContext(this.controlKnots, this.closed, normalRectifier);
|
SpiroJs.spiroToArcsOnContext(this.m_biKnots, this.m_closed, normalRectifier);
|
||||||
}
|
}
|
||||||
iterateNormals() {
|
iterateNormals() {
|
||||||
const centerBone = this.getPass2Knots();
|
const centerBone = this.getPass2Knots();
|
||||||
const normalRectifier = new NormalRectifier(this.controlKnots, this.gizmo);
|
const normalRectifier = new NormalRectifier(this.m_biKnots, this.m_gizmo);
|
||||||
SpiroJs.spiroToArcsOnContext(centerBone, this.closed, normalRectifier);
|
SpiroJs.spiroToArcsOnContext(centerBone, this.m_closed, normalRectifier);
|
||||||
}
|
}
|
||||||
getPass2Knots() {
|
getPass2Knots() {
|
||||||
const expanded = this.expand(this.contrast);
|
const expanded = this.expand(this.m_contrast);
|
||||||
const middles = [];
|
const middles = [];
|
||||||
for (let j = 0; j < this.controlKnots.length; j++) {
|
for (let j = 0; j < this.m_biKnots.length; j++) {
|
||||||
const lhs = this.gizmo.unapply(expanded.lhs[j]);
|
const lhs = this.m_gizmo.unapply(expanded.lhs[j]);
|
||||||
const rhs = this.gizmo.unapply(expanded.rhs[j]);
|
const rhs = this.m_gizmo.unapply(expanded.rhs[j]);
|
||||||
middles[j] = {
|
middles[j] = {
|
||||||
x: 0.5 * (lhs.x + rhs.x),
|
x: 0.5 * (lhs.x + rhs.x),
|
||||||
y: 0.5 * (lhs.y + rhs.y),
|
y: 0.5 * (lhs.y + rhs.y),
|
||||||
type: this.controlKnots[j].type,
|
type: this.m_biKnots[j].type,
|
||||||
unimportant: this.controlKnots[j].unimportant
|
unimportant: this.m_biKnots[j].unimportant
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return middles;
|
return middles;
|
||||||
|
@ -114,8 +42,8 @@ export class SpiroExpander {
|
||||||
const lhs = [],
|
const lhs = [],
|
||||||
rhs = [];
|
rhs = [];
|
||||||
// Initialize knots
|
// Initialize knots
|
||||||
for (let j = 0; j < this.controlKnots.length; j++) {
|
for (let j = 0; j < this.m_biKnots.length; j++) {
|
||||||
const knot = this.controlKnots[j];
|
const knot = this.m_biKnots[j];
|
||||||
lhs[j] = {
|
lhs[j] = {
|
||||||
type: knot.type,
|
type: knot.type,
|
||||||
unimportant: knot.unimportant,
|
unimportant: knot.unimportant,
|
||||||
|
@ -130,16 +58,16 @@ export class SpiroExpander {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// Create important knots
|
// Create important knots
|
||||||
for (let j = 0; j < this.controlKnots.length; j++) {
|
for (let j = 0; j < this.m_biKnots.length; j++) {
|
||||||
const knot = this.controlKnots[j];
|
const knot = this.m_biKnots[j];
|
||||||
if (knot.unimportant) continue;
|
if (knot.unimportant) continue;
|
||||||
let dx, dy;
|
let dx, dy;
|
||||||
if (knot.proposedNormal) {
|
if (knot.proposedNormal) {
|
||||||
dx = knot.proposedNormal.x;
|
dx = knot.proposedNormal.x;
|
||||||
dy = knot.proposedNormal.y;
|
dy = knot.proposedNormal.y;
|
||||||
} else {
|
} else {
|
||||||
dx = normalX(knot.origTangent, this.contrast);
|
dx = normalX(knot.origTangent, this.m_contrast);
|
||||||
dy = normalY(knot.origTangent, this.contrast);
|
dy = normalY(knot.origTangent, this.m_contrast);
|
||||||
}
|
}
|
||||||
lhs[j].x = knot.x + knot.d1 * dx;
|
lhs[j].x = knot.x + knot.d1 * dx;
|
||||||
lhs[j].y = knot.y + knot.d1 * dy;
|
lhs[j].y = knot.y + knot.d1 * dy;
|
||||||
|
@ -151,34 +79,34 @@ export class SpiroExpander {
|
||||||
const lhsUntransformed = [],
|
const lhsUntransformed = [],
|
||||||
rhsUntransformed = [];
|
rhsUntransformed = [];
|
||||||
for (const z of lhs) {
|
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 });
|
lhsUntransformed.push({ type: z.type, x: u.x, y: u.y });
|
||||||
}
|
}
|
||||||
for (const z of rhs) {
|
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 });
|
rhsUntransformed.push({ type: z.type, x: u.x, y: u.y });
|
||||||
}
|
}
|
||||||
return { lhs, rhs, lhsUntransformed, rhsUntransformed };
|
return { lhs, rhs, lhsUntransformed, rhsUntransformed };
|
||||||
}
|
}
|
||||||
interpolateUnimportantKnots(lhs, rhs) {
|
interpolateUnimportantKnots(lhs, rhs) {
|
||||||
for (let j = 0; j < this.controlKnots.length; j++) {
|
for (let j = 0; j < this.m_biKnots.length; j++) {
|
||||||
const knot = this.controlKnots[j];
|
const knot = this.m_biKnots[j];
|
||||||
if (!knot.unimportant) continue;
|
if (!knot.unimportant) continue;
|
||||||
let jBefore, jAfter;
|
let jBefore, jAfter;
|
||||||
for (jBefore = j - 1; cyNth(this.controlKnots, jBefore).unimportant; jBefore--);
|
for (jBefore = j - 1; cyNth(this.m_biKnots, jBefore).unimportant; jBefore--);
|
||||||
for (jAfter = j + 1; cyNth(this.controlKnots, jAfter).unimportant; jAfter++);
|
for (jAfter = j + 1; cyNth(this.m_biKnots, jAfter).unimportant; jAfter++);
|
||||||
const knotBefore = this.gizmo.unapply(cyNth(this.controlKnots, jBefore)),
|
const knotBefore = this.m_gizmo.unapply(cyNth(this.m_biKnots, jBefore)),
|
||||||
knotAfter = this.gizmo.unapply(cyNth(this.controlKnots, jAfter)),
|
knotAfter = this.m_gizmo.unapply(cyNth(this.m_biKnots, jAfter)),
|
||||||
ref = this.gizmo.unapply(knot),
|
ref = this.m_gizmo.unapply(knot),
|
||||||
lhsBefore = this.gizmo.unapply(cyNth(lhs, jBefore)),
|
lhsBefore = this.m_gizmo.unapply(cyNth(lhs, jBefore)),
|
||||||
lhsAfter = this.gizmo.unapply(cyNth(lhs, jAfter)),
|
lhsAfter = this.m_gizmo.unapply(cyNth(lhs, jAfter)),
|
||||||
rhsBefore = this.gizmo.unapply(cyNth(rhs, jBefore)),
|
rhsBefore = this.m_gizmo.unapply(cyNth(rhs, jBefore)),
|
||||||
rhsAfter = this.gizmo.unapply(cyNth(rhs, jAfter));
|
rhsAfter = this.m_gizmo.unapply(cyNth(rhs, jAfter));
|
||||||
const lhsTf = this.gizmo.applyXY(
|
const lhsTf = this.m_gizmo.applyXY(
|
||||||
linreg(knotBefore.x, lhsBefore.x, knotAfter.x, lhsAfter.x, ref.x),
|
linreg(knotBefore.x, lhsBefore.x, knotAfter.x, lhsAfter.x, ref.x),
|
||||||
linreg(knotBefore.y, lhsBefore.y, knotAfter.y, lhsAfter.y, ref.y)
|
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.x, rhsBefore.x, knotAfter.x, rhsAfter.x, ref.x),
|
||||||
linreg(knotBefore.y, rhsBefore.y, knotAfter.y, rhsAfter.y, ref.y)
|
linreg(knotBefore.y, rhsBefore.y, knotAfter.y, rhsAfter.y, ref.y)
|
||||||
);
|
);
|
||||||
|
@ -189,33 +117,33 @@ export class SpiroExpander {
|
||||||
}
|
}
|
||||||
class NormalRectifier {
|
class NormalRectifier {
|
||||||
constructor(stage1ControlKnots, gizmo) {
|
constructor(stage1ControlKnots, gizmo) {
|
||||||
this.gizmo = gizmo;
|
this.m_gizmo = gizmo;
|
||||||
this.controlKnots = stage1ControlKnots;
|
this.m_biKnots = stage1ControlKnots;
|
||||||
this.nKnotsProcessed = 0;
|
this.m_nKnotsProcessed = 0;
|
||||||
}
|
}
|
||||||
beginShape() {}
|
beginShape() {}
|
||||||
endShape() {}
|
endShape() {}
|
||||||
moveTo(x, y) {
|
moveTo(x, y) {
|
||||||
this.nKnotsProcessed += 1;
|
this.m_nKnotsProcessed += 1;
|
||||||
}
|
}
|
||||||
arcTo(arc, x, y) {
|
arcTo(arc, x, y) {
|
||||||
if (this.nKnotsProcessed === 1) {
|
if (this.m_nKnotsProcessed === 1) {
|
||||||
const d = this.gizmo.applyOffsetXY(arc.deriveX0, arc.deriveY0);
|
const d = this.m_gizmo.applyOffsetXY(arc.deriveX0, arc.deriveY0);
|
||||||
if (isTangentValid(d)) {
|
if (isTangentValid(d)) {
|
||||||
this.controlKnots[0].origTangent = d;
|
this.m_biKnots[0].origTangent = d;
|
||||||
} else {
|
} else {
|
||||||
throw new Error("NaN angle detected.");
|
throw new Error("NaN angle detected.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.controlKnots[this.nKnotsProcessed]) {
|
if (this.m_biKnots[this.m_nKnotsProcessed]) {
|
||||||
const d = this.gizmo.applyOffsetXY(arc.deriveX1, arc.deriveY1);
|
const d = this.m_gizmo.applyOffsetXY(arc.deriveX1, arc.deriveY1);
|
||||||
if (isTangentValid(d)) {
|
if (isTangentValid(d)) {
|
||||||
this.controlKnots[this.nKnotsProcessed].origTangent = d;
|
this.m_biKnots[this.m_nKnotsProcessed].origTangent = d;
|
||||||
} else {
|
} else {
|
||||||
throw new Error("NaN angle detected.");
|
throw new Error("NaN angle detected.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.nKnotsProcessed += 1;
|
this.m_nKnotsProcessed += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue