Make basic operations to produce less closures (#2477)

This commit is contained in:
Belleve 2024-08-21 01:28:25 -10:00 committed by GitHub
parent 78b0df7e7d
commit 1c9c2e09a8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 307 additions and 227 deletions

View file

@ -2,7 +2,7 @@ $$include '../meta/macros.ptl'
import [mix linreg clamp fallback boole boolePn] from "@iosevka/util"
import [Transform] from "@iosevka/geometry/transform"
import [Interpolator WithKnotProxy] from "@iosevka/geometry/spiro-control"
import [FunctionInterpolator WithKnotProxy AfCombine] from "@iosevka/geometry/spiro-control"
import [RadicalGeometry StrokeGeometry RemoveHolesGeometry] from "@iosevka/geometry"
import [CMixCoord CopyBackKnotProxy] from "@iosevka/font-kits/derived-coordinates"
@ -133,7 +133,6 @@ glyph-block CommonShapes : begin
local mx ((l + r) / 2)
currentGlyph.gizmo = [if transformShiftOnly [Transform.Id] giz]
include : dispiro
begin [lambda : set this.gizmo currentGlyph.gizmo]
widths.rhs [fallback s Stroke]
g4 mx d [heading Leftward]
archv
@ -168,7 +167,6 @@ glyph-block CommonShapes : begin
local mx ((l + r) / 2)
currentGlyph.gizmo = [if transformShiftOnly [Transform.Id] giz]
include : spiro-outline
begin [lambda : set this.gizmo currentGlyph.gizmo]
g4 mx d
archv 32 2.0
g4 l my
@ -463,22 +461,16 @@ glyph-block CommonShapes : begin
local depth : v + skew0 * sw - sw
local shallowLimit : sw / 2
local skew : clamp 0 (1 / 2) : skew0 + [clamp 0 shallowLimit (shallowLimit - depth)] / rad
local faf toFinish.af
if doSwash
: then : begin
set toFinish.af : lambda [] : begin
if faf : faf.apply this arguments
if this.headsTo : this.headsTo {
.x (Contrast / [Math.hypot 1 skew] * [if dtu (-1) 1])
.y (skew / [Math.hypot 1 skew] * [if ltr 1 (-1)])
}
: else : begin
set toFinish.af : lambda [] : begin
if faf : faf.apply this arguments
if this.headsTo : this.headsTo{
.x (Contrast * [if dtu (-1) 1])
.y 0
}
local headDirection : if doSwash
object
x (Contrast / [Math.hypot 1 skew] * [if dtu (-1) 1])
y (skew / [Math.hypot 1 skew] * [if ltr 1 (-1)])
object
x (Contrast * [if dtu (-1) 1])
y 0
set toFinish.af : new AfCombine toFinish.af [heading headDirection]
# Create the arc knots
local segBefore {}
@ -487,14 +479,14 @@ glyph-block CommonShapes : begin
local fraction : j / nHookSegments
local mixRatioAdjust : Math.max (1 / 2) : (1 / 2) + [if doSwash 1 (1 / 8)] * (mixRatio - (1 / 2))
local fractionAfter : fraction * (1 - mixRatioAdjust) / mixRatioAdjust
local myfinal : _SuperXY ((1 - mixRatioAdjust) / mixRatioAdjust) superness
local myfinal : 1 - [archv.yFromX ((1 - mixRatioAdjust) / mixRatioAdjust) superness]
segBefore.push : g4
mix mx toStraight.x fraction
mix y toStraight.y (1 - [_SuperXY fraction superness])
mix y toStraight.y [archv.yFromX fraction superness]
begin unimportant
segAfter.push : g4
mix mx toFinish.x fraction
mix y toFinish.y ((1 - [_SuperXY fractionAfter superness]) / (1 - myfinal))
mix y toFinish.y ([archv.yFromX fractionAfter superness] / (1 - myfinal))
begin unimportant
if isStart
@ -525,7 +517,7 @@ glyph-block CommonShapes : begin
local-parameter : noSwash -- false
local-parameter : o -- O
local args : object [yRef y] sw swTerminal isTail noSwash [overshoot o]
return : WithKnotProxy [hookProxy args] : Interpolator hookStartBlender args
return : WithKnotProxy [hookProxy args] : new FunctionInterpolator hookStartBlender args
glyph-block-export hookend
define flex-params [hookend] : begin
@ -536,7 +528,7 @@ glyph-block CommonShapes : begin
local-parameter : noSwash -- false
local-parameter : o -- O
local args : object [yRef y] sw swTerminal isTail noSwash [overshoot o]
return : WithKnotProxy [hookProxy args] : Interpolator hookEndBlender args
return : WithKnotProxy [hookProxy args] : new FunctionInterpolator hookEndBlender args
glyph-block-export arch
define arch : namespace
@ -638,7 +630,8 @@ glyph-block CommonShapes : begin
local-parameter : blendPre -- [if anglePre nothing [arcvh]]
local-parameter : blendPost -- [if anglePost nothing [archv]]
local args : object [lhs true] y p sw compact o swBefore swAfter mockPre mockPost blendPre blendPost anglePre anglePost
return : WithKnotProxy [archBlenderProxy args] : Interpolator archBlender args
return : WithKnotProxy [archBlenderProxy args]
new FunctionInterpolator archBlender args
export : define flex-params [rhs] : begin
local-parameter : y
@ -655,7 +648,8 @@ glyph-block CommonShapes : begin
local-parameter : blendPre -- [if anglePre nothing [arcvh]]
local-parameter : blendPost -- [if anglePost nothing [archv]]
local args : object [lhs false] y p sw compact o swBefore swAfter mockPre mockPost blendPre blendPost anglePre anglePost
return : WithKnotProxy [archBlenderProxy args] : Interpolator archBlender args
return : WithKnotProxy [archBlenderProxy args]
new FunctionInterpolator archBlender args
foreach side {lhs rhs} : begin
set side.centerAt : object

View file

@ -4,7 +4,7 @@ import [mix fallback] from "@iosevka/util"
import [SpiroPenGeometry] from "@iosevka/geometry"
import [Vec2] from "@iosevka/geometry/point"
import [Box] from "@iosevka/geometry/box"
import [Interpolator] from "@iosevka/geometry/spiro-control"
import [AfBase FunctionInterpolator] from "@iosevka/geometry/spiro-control"
import [PenKnotCollector] from "@iosevka/geometry/spiro-pen-expand"
glyph-module
@ -61,8 +61,13 @@ glyph-block LetterLike-Fraktur-Common : begin
# Directive to change the profile
glyph-block-export change-pen
define [change-pen newPen] : function : begin
this.setProfile : newPen.getPenShape this.gizmo
define [change-pen newPen] : new AfChangePen newPen
class AfChangePen : inherits AfBase
public [new newPen] : begin
this.newPen = newPen
public [applyTo target] : begin
target.setProfile : this.newPen.getPenShape target.gizmo
# A pen profile describes a virtual flat-tip pen. We use a 45-degree arrangement to
# simplify the math.
@ -152,26 +157,26 @@ glyph-block LetterLike-Fraktur-Common : begin
export : define DepthX : 1 * DecoSizeX
export : define LTDecoSize : 0.75 * DecoSizeX
export : define [h o] : Interpolator hBlender [object o]
export : define [h o] : new FunctionInterpolator hBlender [object o]
define [hBlender before after args] : begin
return : list
g2 [mix before.x after.x 0.375] (after.y + [fallback args.o 0])
g2 [mix before.x after.x 0.625] (before.y - [fallback args.o 0])
export : define [vc waveDepth] : Interpolator vcBlender [object waveDepth]
export : define [vc waveDepth] : new FunctionInterpolator vcBlender [object waveDepth]
define [vcBlender before after args] : begin
local [object waveDepth] args
return : list
g2 (before.x + 0.5 * waveDepth) [mix before.y after.y 0.375]
g2 (after.x - 0.5 * waveDepth) [mix before.y after.y 0.625]
export : define [v] : Interpolator vBlender
export : define [v] : new FunctionInterpolator vBlender
define [vBlender before after] : begin
return : list
g2 after.x [mix before.y after.y 0.375]
g2 before.x [mix before.y after.y 0.625]
export : define [vDistAfter d] : Interpolator vDistAfterBlender [object d]
export : define [vDistAfter d] : new FunctionInterpolator vDistAfterBlender [object d]
define [vDistAfterBlender before after args] : begin
return : list
g2 after.x [mix before.y after.y 0.375]

View file

@ -166,7 +166,7 @@ glyph-block Letter-Latin-Lower-E : begin
dispiro
g4 lastKnot.x lastKnot.y [widths.rhs fine]
g4 (lastKnot.x - beginCoSlope * TINY) (lastKnot.y - TINY)
alsoThruThem.fromTWithOffset {(1/3) (2/3)} : object
alsoThruThem.computed { (1/3) (2/3) } : object
rx : function [rt] rt
deltaX : function [rt] 0
ry : function [rt] : 1/24 + rt + (1/2 - rt) * (3/8)

View file

@ -1,7 +1,7 @@
$$include '../meta/macros.ptl'
import [mix clamp fallback] from "@iosevka/util"
import [Interpolator] from "@iosevka/geometry/spiro-control"
import [FunctionInterpolator] from "@iosevka/geometry/spiro-control"
import [Dotless CvDecompose] from "@iosevka/glyph/relation"
import [RightDependentTrigger RightDependentLink DependentSelector] from "@iosevka/glyph/relation"
import [DesignParameters] from "../meta/aesthetics.mjs"
@ -211,7 +211,7 @@ glyph-block Letter-Shared-Shapes : begin
local-parameter : swBefore -- Stroke
local-parameter : terminalSlopeAdj -- 0.5
return : Interpolator normalBlender
return : new FunctionInterpolator normalBlender
object [flat false] fine bottom xOuter x2 y2 yLoopTop swBefore terminalSlopeAdj
export : define flex-params [f] : begin
@ -223,7 +223,7 @@ glyph-block Letter-Shared-Shapes : begin
local-parameter : swBefore -- Stroke
local-parameter : terminalSlopeAdj -- 0.5
return : Interpolator normalBlender
return : new FunctionInterpolator normalBlender
object [flat true] fine bottom xOuter x2 yLoopTop swBefore terminalSlopeAdj
glyph-block-export HCurlyTail

View file

@ -177,10 +177,6 @@ export : define [calculateMetrics para] : begin
define GeometryStroke : AdviceStroke 4
define ShoulderFine : Math.min (Stroke * para.shoulderFineMin) [AdviceStroke 16]
define [_SuperXY x superness] : Math.pow
1 - [Math.pow x [fallback superness DesignParameters.superness]]
1 / [fallback superness DesignParameters.superness]
define [AdviceGlottalStopArchDepth y sign] : begin
return : ((y - Stroke) * 0.24 + Stroke * 0.625) + sign * TanSlope * SmoothAdjust
@ -199,7 +195,7 @@ export : define [calculateMetrics para] : begin
EssUpper EssLower EssQuestion HalfStroke RightSB Middle DotRadius PeriodRadius SideJut
ArchDepthA ArchDepthB SmallArchDepthA SmallArchDepthB CorrectionOMidX CorrectionOMidS
compositeBaseAnchors AdviceStroke AdviceStroke2 OverlayStroke OperatorStroke GeometryStroke
ShoulderFine _SuperXY AdviceGlottalStopArchDepth StrokeWidthBlend ArchDepthAOf ArchDepthBOf
ShoulderFine AdviceGlottalStopArchDepth StrokeWidthBlend ArchDepthAOf ArchDepthBOf
SmoothAdjust MidJutSide MidJutCenter YSmoothMidR YSmoothMidL HSwToV NarrowUnicodeT
WideUnicodeT VERY-FAR TINY]

View file

@ -284,10 +284,10 @@ define-macro glyph-block : syntax-rules
WideWidth2 WideWidth3 WideWidth4 EssUpper EssLower EssQuestion HalfStroke RightSB
Middle DotRadius PeriodRadius SideJut ArchDepthA ArchDepthB SmallArchDepthA
SmallArchDepthB CorrectionOMidX CorrectionOMidS AdviceStroke AdviceStroke2
OverlayStroke OperatorStroke GeometryStroke ShoulderFine _SuperXY
AdviceGlottalStopArchDepth StrokeWidthBlend ArchDepthAOf ArchDepthBOf SmoothAdjust
MidJutSide MidJutCenter compositeBaseAnchors YSmoothMidR YSmoothMidL HSwToV
NarrowUnicodeT WideUnicodeT VERY-FAR TINY]
OverlayStroke OperatorStroke GeometryStroke ShoulderFine AdviceGlottalStopArchDepth
StrokeWidthBlend ArchDepthAOf ArchDepthBOf SmoothAdjust MidJutSide MidJutCenter
compositeBaseAnchors YSmoothMidR YSmoothMidL HSwToV NarrowUnicodeT WideUnicodeT
VERY-FAR TINY]
define spiroFnImports `[g4 g2 corner flat curl virt close end straight g2c cg2 flatc ccurl
widths disable-contrast heading unimportant important alsoThru alsoThruThem bezControls
quadControls archv arcvh dispiro spiro-outline spiro-collect]

View file

@ -1,6 +1,7 @@
import { DiSpiroGeometry, SpiroGeometry } from "@iosevka/geometry";
import {
Interpolator,
AfBase,
InterpolatorBase,
SpiroFlattener,
TerminateInstruction,
UserCloseKnotPair,
@ -167,12 +168,21 @@ export function SetupBuilders(bindings) {
}
}
class AfSetWidths extends AfBase {
constructor(l, r) {
super();
this.l = l;
this.r = r;
}
applyTo(target) {
target.setWidth(this.l, this.r);
}
}
function widths(l, r) {
if (!isFinite(l)) throw new TypeError("NaN detected for left width");
if (!isFinite(r)) throw new TypeError("NaN detected for right width");
return function () {
if (this.setWidth) this.setWidth(l, r);
};
return new AfSetWidths(l, r);
}
widths.lhs = function (w) {
w = fallback(w, Stroke);
@ -190,154 +200,172 @@ export function SetupBuilders(bindings) {
return widths(w / 2, w / 2);
};
class AfHeading extends AfBase {
constructor(d) {
super();
this.d = d;
}
applyTo(target) {
target.headsTo(this.d);
}
}
function heading(d) {
if (!isFinite(d.x) || !isFinite(d.y))
throw new TypeError("NaN detected for heading directions");
return function () {
if (this.headsTo) this.headsTo(d);
};
return new AfHeading(d);
}
class AfWidthsHeading extends AfBase {
constructor(l, r, d) {
super();
this.l = l;
this.r = r;
this.d = d;
}
applyTo(target) {
target.setWidth(this.l, this.r);
target.headsTo(this.d);
}
}
widths.heading = function (l, r, d) {
if (!isFinite(l)) throw new TypeError("NaN detected for left width");
if (!isFinite(r)) throw new TypeError("NaN detected for left width");
if (!isFinite(d.x) || !isFinite(d.y))
throw new TypeError("NaN detected for heading directions");
return function () {
if (this.setWidth) this.setWidth(l, r);
if (this.headsTo) this.headsTo(d);
};
return new AfWidthsHeading(l, r, d);
};
widths.lhs.heading = function (w, d) {
w = fallback(w, Stroke);
if (!isFinite(w)) throw new TypeError("NaN detected for left width");
if (!isFinite(d.x) || !isFinite(d.y))
throw new TypeError("NaN detected for heading directions");
return function () {
if (this.setWidth) this.setWidth(w, 0);
if (this.headsTo) this.headsTo(d);
};
return new AfWidthsHeading(w, 0, d);
};
widths.rhs.heading = function (w, d) {
w = fallback(w, Stroke);
if (!isFinite(w)) throw new TypeError("NaN detected for left width");
if (!isFinite(d.x) || !isFinite(d.y))
throw new TypeError("NaN detected for heading directions");
return function () {
if (this.setWidth) this.setWidth(0, w);
if (this.headsTo) this.headsTo(d);
};
return new AfWidthsHeading(0, w, d);
};
widths.center.heading = function (w, d) {
w = fallback(w, Stroke);
if (!isFinite(w)) throw new TypeError("NaN detected for left width");
if (!isFinite(d.x) || !isFinite(d.y))
throw new TypeError("NaN detected for heading directions");
return function () {
if (this.setWidth) this.setWidth(w / 2, w / 2);
if (this.headsTo) this.headsTo(d);
};
return new AfWidthsHeading(w / 2, w / 2, d);
};
class AfDisableContrast extends AfBase {
applyTo(target) {
target.setContrast(1);
}
}
function disableContrast() {
return function () {
if (this.setContrast) this.setContrast(1);
};
}
function unimportant() {
if (this.setUnimportant) this.setUnimportant(1);
}
function important() {
return void 0;
return new AfDisableContrast();
}
function afInterpolate(before, after, args) {
return g4(
mix(before.x, after.x, args.rx),
mix(before.y, after.y, args.ry),
fallback(args.raf, unimportant),
);
class AfUnimportant extends AfBase {
applyTo(target) {
target.setUnimportant();
}
}
function afInterpolateDelta(before, after, args) {
return g4(
mix(before.x, after.x, args.rx) + args.deltaX,
mix(before.y, after.y, args.ry) + args.deltaY,
fallback(args.raf, unimportant),
);
const unimportant = new AfUnimportant();
class AfImportant extends AfBase {
applyTo(target) {
target.setImportant();
}
}
function afInterpolateG2(before, after, args) {
return g2(
mix(before.x, after.x, args.rx),
mix(before.y, after.y, args.ry),
fallback(args.raf, unimportant),
);
}
function afInterpolateThem(before, after, args) {
let innerKnots = [];
for (const [rx, ry, rt] of args.rs) {
innerKnots.push(
fallback(args.ty, g2)(
mix(before.x, after.x, rx),
mix(before.y, after.y, ry),
args.raf && args.raf.blend && rt !== void 0
? args.raf.blend(rt)
: args.raf
? args.raf
: unimportant,
),
const important = new AfImportant();
/// Simple (single mix) interpolator
class SimpleMixInterpolator extends InterpolatorBase {
constructor(ty, rx, ry, deltaX, deltaY, raf) {
super();
this.ty = ty;
this.rx = rx;
this.ry = ry;
this.deltaX = deltaX;
this.deltaY = deltaY;
this.raf = fallback(raf, unimportant);
}
resolveInterpolation(before, after) {
return this.ty(
mix(before.x, after.x, this.rx) + this.deltaX,
mix(before.y, after.y, this.ry) + this.deltaY,
this.raf,
);
}
return innerKnots;
}
function afInterpolateThemWithDelta(before, after, args) {
let innerKnots = [];
for (const [rx, ry, deltaX, deltaY, rt] of args.rs) {
innerKnots.push(
fallback(args.ty, g2)(
mix(before.x, after.x, rx) + deltaX,
mix(before.y, after.y, ry) + deltaY,
args.raf && args.raf.blend && rt !== void 0
? args.raf.blend(rt)
: args.raf
? args.raf
: unimportant,
),
);
}
return innerKnots;
}
function afInterpolateThemFromTWithDelta(before, after, args) {
let innerKnots = [];
for (const rt of args.rs) {
innerKnots.push(
fallback(args.ty, g2)(
mix(before.x, after.x, args.raf.rx(rt)) + args.raf.deltaX(rt),
mix(before.y, after.y, args.raf.ry(rt)) + args.raf.deltaY(rt),
args.raf.modifier(rt),
),
);
}
return innerKnots;
}
function alsoThru(rx, ry, raf) {
return Interpolator(afInterpolate, { rx, ry, raf });
return new SimpleMixInterpolator(g4, rx, ry, 0, 0, raf);
}
alsoThru.withOffset = function (rx, ry, deltaX, deltaY, raf) {
return Interpolator(afInterpolateDelta, { rx, ry, deltaX, deltaY, raf });
return new SimpleMixInterpolator(g4, rx, ry, deltaX, deltaY, raf);
};
alsoThru.g2 = function (rx, ry, raf) {
return Interpolator(afInterpolateG2, { rx, ry, raf });
return new SimpleMixInterpolator(g2, rx, ry, 0, 0, raf);
};
function alsoThruThem(rs, raf, ty) {
return Interpolator(afInterpolateThem, { rs, raf, ty });
/// Multi-mix interpolator
class MultiMixInterpolator extends InterpolatorBase {
constructor(rs, raf, ty) {
super();
this.rs = rs;
this.raf = raf;
this.ty = fallback(ty, g2);
}
resolveInterpolation(before, after) {
let innerKnots = [];
for (const [rx, ry, rt] of this.rs) {
const x = mix(before.x, after.x, rx);
const y = mix(before.y, after.y, ry);
const af =
this.raf && this.raf.blend && rt !== void 0
? this.raf.blend(rt)
: this.raf
? this.raf
: unimportant;
innerKnots.push(this.ty(x, y, af));
}
return innerKnots;
}
}
alsoThruThem.withOffset = function (rs, raf, ty) {
return Interpolator(afInterpolateThemWithDelta, { rs, raf, ty });
};
alsoThruThem.fromTWithOffset = function (rs, raf, ty) {
return Interpolator(afInterpolateThemFromTWithDelta, { rs, raf, ty });
function alsoThruThem(rs, raf, ty) {
return new MultiMixInterpolator(rs, raf, ty);
}
/// Multi-mix interpolator that use function set to compute proportion/deltas
class MultiMixComputeInterpolator extends InterpolatorBase {
constructor(rs, raf, ty) {
super();
this.rs = rs;
this.raf = raf;
this.ty = fallback(ty, g2);
}
resolveInterpolation(before, after) {
let innerKnots = [];
for (const rt of this.rs) {
innerKnots.push(
this.ty(
mix(before.x, after.x, this.raf.rx(rt)) + this.raf.deltaX(rt),
mix(before.y, after.y, this.raf.ry(rt)) + this.raf.deltaY(rt),
this.raf.modifier(rt),
),
);
}
return innerKnots;
}
}
alsoThruThem.computed = function (rs, raf, ty) {
return new MultiMixComputeInterpolator(rs, raf, ty);
};
// Bezier control interpolator
function bezControlsImpl(x1, y1, x2, y2, samples, raf, ty) {
let rs = [];
for (let j = 1; j < samples; j = j + 1)
@ -362,44 +390,64 @@ export function SetupBuilders(bindings) {
);
}
let DEFAULT_STEPS = 6;
let [buildHV, buildVH] = (function (cache) {
function build(samples, _superness) {
const superness = fallback(_superness, Superness);
let hv = [];
let vh = [];
for (let j = 1; j < samples; j = j + 1) {
const theta = (((j + 1) / (samples + 2)) * Math.PI) / 2;
const c = Math.pow(Math.cos(theta), 2 / superness);
const s = Math.pow(Math.sin(theta), 2 / superness);
hv.push([s, 1 - c]);
vh.push([1 - c, s]);
// ArcHV and ArcVH interpolators
class ArcHvInterpolator extends InterpolatorBase {
constructor(steps, superness) {
super();
this.steps = steps;
this.superness = superness;
}
resolveInterpolation(before, after) {
let innerKnots = [];
for (let j = 1; j < this.steps; j++) {
const theta = (((j + 1) / (this.steps + 2)) * Math.PI) / 2;
const c = Math.pow(Math.cos(theta), 2 / this.superness);
const s = Math.pow(Math.sin(theta), 2 / this.superness);
const x = mix(before.x, after.x, s);
const y = mix(before.y, after.y, 1 - c);
innerKnots.push(g2(x, y, unimportant));
}
return { hv, vh: vh };
return innerKnots;
}
function buildHVImpl(samples, _superness) {
if (_superness) return build(samples, _superness).hv;
if (!cache[samples]) cache[samples] = build(samples, _superness);
return cache[samples].hv;
}
class ArcVhInterpolator extends InterpolatorBase {
constructor(steps, superness) {
super();
this.steps = steps;
this.superness = superness;
}
function buildVHImpl(samples, _superness) {
if (_superness) return build(samples, _superness).vh;
if (!cache[samples]) cache[samples] = build(samples, _superness);
return cache[samples].vh;
resolveInterpolation(before, after) {
let innerKnots = [];
for (let j = 1; j < this.steps; j++) {
const theta = (((j + 1) / (this.steps + 2)) * Math.PI) / 2;
const c = Math.pow(Math.cos(theta), 2 / this.superness);
const s = Math.pow(Math.sin(theta), 2 / this.superness);
const x = mix(before.x, after.x, 1 - c);
const y = mix(before.y, after.y, s);
innerKnots.push(g2(x, y, unimportant));
}
return innerKnots;
}
return [buildHVImpl, buildVHImpl];
})([]);
}
let DEFAULT_STEPS = 6;
function archv(samples, superness) {
return alsoThruThem(buildHV(fallback(samples, DEFAULT_STEPS), superness));
return new ArcHvInterpolator(
fallback(samples, DEFAULT_STEPS),
fallback(superness, Superness),
);
}
archv.superness = function (s) {
return archv(DEFAULT_STEPS, s);
return new ArcHvInterpolator(DEFAULT_STEPS, s);
};
function arcvh(samples, superness) {
return alsoThruThem(buildVH(fallback(samples, DEFAULT_STEPS), superness));
return new ArcVhInterpolator(
fallback(samples, DEFAULT_STEPS),
fallback(superness, Superness),
);
}
arcvh.superness = function (s) {
return arcvh(DEFAULT_STEPS, s);
return new ArcVhInterpolator(DEFAULT_STEPS, s);
};
archv.yFromX = function (px, _s) {
const s = fallback(_s, Superness);

View file

@ -58,15 +58,23 @@ class CvLookupManager
return lookup
public [linkDeps] : begin
if (this.decompositionLookup && this.altrenatesLookup) : begin
this.table.setDependency this.decompositionLookup this.altrenatesLookup
if this.decompositionLookup : begin
if this.altrenatesLookup : begin
this.table.setDependency this.decompositionLookup this.altrenatesLookup
foreach lookupSS [items-of this.singleSubstLookups] : if lookupSS : begin
this.table.setDependency this.decompositionLookup lookupSS
foreach lookupCP [items-of this.cherryPickingLookups] : if lookupCP : begin
this.table.setDependency this.decompositionLookup lookupCP
if this.altrenatesLookup : begin
foreach lookupSS [items-of this.singleSubstLookups] : if lookupSS : begin
this.table.setDependency this.altrenatesLookup lookupSS
foreach lookupCP [items-of this.cherryPickingLookups] : if lookupCP : begin
this.table.setDependency lookupCP this.altrenatesLookup
foreach lookupSS [items-of this.singleSubstLookups] : if lookupSS : begin
this.table.setDependency lookupCP lookupSS
foreach lookupCP [items-of this.cherryPickingLookups] : if lookupCP : begin
foreach lookupSS [items-of this.singleSubstLookups] : if lookupSS : begin
this.table.setDependency lookupCP lookupSS
public [linkCrossDeps other] : begin
if (this.altrenatesLookup && other.altrenatesLookup) : begin
@ -101,41 +109,35 @@ export : define [buildCVSS gsub para glyphStore] : begin
local lookup : [cvs.get gr.tag].createDecompositionSubst
if [not lookup.substitutions.(gn)] : set lookup.substitutions.(gn) parts
do "cvxx"
local cvGrs {}
foreach {name prime} para.variants.primes : foreach {vn variant} prime.variants : begin
if (variant.tag && variant.rank) : cvGrs.push variant
cvGrs.sort AnyCv.compare
do "cvxx / ssxx"
local grSetUsedBySs : new Set
foreach {name composition} para.variants.composites : if composition.tag : begin
define feature : gsub.addCommonFeature : gsub.createFeature composition.tag
foreach {gn glyph} [glyphStore.namedEntries] : if [not : CvDecompose.get glyph] : begin
foreach gr [items-of cvGrs] : begin
local subst : gr.get glyph
if (subst && subst != gn) : begin
local cvAlt : [cvs.get gr.tag].createAlternateSubst
if [not cvAlt.substitutions.(gn)] : set cvAlt.substitutions.(gn) { }
set cvAlt.substitutions.(gn).(gr.rank - 1) : glyphStore.ensureExists subst
define decomp : composition.decompose para para.variants.selectorTree
local ssGrs {}
foreach { prime pv } [items-of decomp] : if (pv.tag && pv.rank) : begin
ssGrs.push pv
local dl [cvs.get pv.tag].decompositionLookup
if dl : feature.addLookup dl
ssGrs.sort AnyCv.compare
do "ssxx" : foreach {name composition} para.variants.composites : if composition.tag : begin
define feature : gsub.addCommonFeature : gsub.createFeature composition.tag
define decomp : composition.decompose para para.variants.selectorTree
local ssGrs {}
foreach { prime pv } [items-of decomp] : if (pv.tag && pv.rank) : begin
ssGrs.push pv
local dl [cvs.get pv.tag].decompositionLookup
if dl : feature.addLookup dl
ssGrs.sort AnyCv.compare
foreach gr [items-of ssGrs] : begin
local cvSingle : [cvs.get gr.tag].createSingleSubstFor gr.rank
feature.addLookup cvSingle
foreach {gn glyph} [glyphStore.namedEntries] : if [not : CvDecompose.get glyph] : begin
foreach gr [items-of ssGrs] : begin
local cvSingle : [cvs.get gr.tag].createSingleSubstFor gr.rank
feature.addLookup cvSingle
grSetUsedBySs.add gr
foreach {gn glyph} [glyphStore.namedEntriesWithFilter nonDecomposable] : begin
foreach gr [items-of : AnyCv.query glyph] : begin
local subst : gr.get glyph
if (subst && subst != gn) : begin
local cvSingle : [cvs.get gr.tag].createSingleSubstFor gr.rank
set cvSingle.substitutions.(gn) : glyphStore.ensureExists subst
if (gr.tag && gr.rank) : begin
local cvAlt : [cvs.get gr.tag].createAlternateSubst
if [not cvAlt.substitutions.(gn)] : set cvAlt.substitutions.(gn) { }
set cvAlt.substitutions.(gn).(gr.rank - 1) : glyphStore.ensureExists subst
if [grSetUsedBySs.has gr] : begin
local cvSingle : [cvs.get gr.tag].createSingleSubstFor gr.rank
set cvSingle.substitutions.(gn) : glyphStore.ensureExists subst
do "CV cherry picking"
foreach {name prime} para.variants.primes : if prime.cherryPicking : begin
@ -147,7 +149,7 @@ export : define [buildCVSS gsub para glyphStore] : begin
if cv.decompositionLookup : feature.addLookup cv.decompositionLookup
feature.addLookup lookup
foreach {gn glyph} [glyphStore.namedEntries] : if [not : CvDecompose.get glyph] : begin
foreach {gn glyph} [glyphStore.namedEntriesWithFilter nonDecomposable] : begin
local subst : gr.get glyph
if (subst && subst != gn) : begin
set lookup.substitutions.(gn) : glyphStore.ensureExists subst
@ -172,3 +174,4 @@ export : define [buildCVSS gsub para glyphStore] : begin
return cvs
define [objectIsNotEmpty obj] : obj && [Object.keys obj].length
define [nonDecomposable gn g] : not : CvDecompose.get g

View file

@ -1,7 +1,7 @@
// This class is used to "flatten" the spiro controls into a plain list of UserControlKnot
export class SpiroFlattener {
constructor() {
this.preControlFunctions = [];
this.preControls = [];
this.controls = [];
this.postControls = [];
}
@ -9,13 +9,21 @@ export class SpiroFlattener {
add(c) {
if (Array.isArray(c)) {
for (const item of c) this.add(item);
} else if (c instanceof Function) {
if (!this.controls.length) this.preControlFunctions.push(c);
else throw new Error("Invalid spiro control sequence");
} else if (c instanceof AfBase) {
if (this.controls.length) {
throw new Error(
"Invalid spiro control sequence: pre-control functions must be added first",
);
}
this.preControls.push(c);
} else if (c instanceof TerminateInstruction) {
this.postControls.push(c);
} else {
if (this.postControls.length) throw new Error("Invalid spiro control sequence");
if (this.postControls.length) {
throw new Error(
"Invalid spiro control sequence: post-control functions must be added last",
);
}
this.controls.push(c);
}
}
@ -34,7 +42,7 @@ export class SpiroFlattener {
}
pipe(collector) {
for (const fn of this.preControlFunctions) fn.call(collector);
for (const fn of this.preControls) fn.applyTo(collector);
for (const control of this.controls) collector.pushKnot(control);
for (const postControl of this.postControls) postControl.applyTo(collector);
collector.finish();
@ -214,6 +222,27 @@ class CoordinatePropagator {
///////////////////////////////////////////////////////////////////////////////////////////////////
/** The "amendmend function" */
export class AfBase {
applyTo() {
throw new Error("Unimplemented");
}
}
export class AfCombine extends AfBase {
constructor(af1, af2) {
super();
this.af1 = af1;
this.af2 = af2;
}
applyTo(target) {
if (this.af1) this.af1.applyTo(target);
if (this.af2) this.af2.applyTo(target);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
const RES_DEP_STAGE_COORDINATE_PROPOGATION_X = 0;
const RES_DEP_STAGE_COORDINATE_PROPOGATION_Y = 1;
const RES_DEP_STAGE_INTERPOLATION = 2;
@ -241,7 +270,7 @@ export class UserControlKnot {
this.af = af;
}
applyTo(ctx) {
if (this.af) this.af.call(ctx);
if (this.af) this.af.applyTo(ctx);
}
getDependency(stage) {
@ -261,7 +290,6 @@ export class UserControlKnot {
return this;
}
resolveCoordiantePropogation(ic, pre, post) {
// console.log(this, ic, pre, post);
switch (ic) {
case 0:
this.x = this.x.resolveX(pre, this, post);
@ -388,7 +416,7 @@ export class DecorInterpolator extends InterpolatorBase {
}
}
class FunctionInterpolator extends InterpolatorBase {
export class FunctionInterpolator extends InterpolatorBase {
constructor(blendFn, extraArgs) {
super();
this.blendFn = blendFn;
@ -430,10 +458,6 @@ export class KnotProxyInterpolator extends InterpolatorBase {
return this.actual.resolveInterpolation(pre, post);
}
}
export function Interpolator(blender, restParameters) {
return new FunctionInterpolator(blender, restParameters);
}
export function WithKnotProxy(proxy, actual) {
return new KnotProxyInterpolator(proxy, actual);
}
@ -448,7 +472,6 @@ export class TerminateInstruction {
applyTo(ctx) {
if (this.type === "close") ctx.closed = true;
if (this.af) throw new Error("Unreachable");
// if (this.af) this.af.call(ctx);
}
}

View file

@ -55,6 +55,11 @@ export class BiKnotCollector {
this.lastKnot.proposedNormal = direction;
}
}
setImportant() {
if (this.lastKnot) {
this.lastKnot.unimportant = 0;
}
}
setUnimportant() {
if (this.lastKnot) {
this.lastKnot.unimportant = 1;

View file

@ -33,6 +33,7 @@ export class PenKnotCollector {
setUnimportant() {
if (this.m_lastKnot) this.m_lastKnot.profile = null;
}
setImportant() {} // Not used
setContrast() {}
setProfile(profile) {

View file

@ -14,6 +14,11 @@ export class GlyphStore {
namedEntries() {
return this.nameForward.entries();
}
*namedEntriesWithFilter(fn) {
for (const [name, g] of this.nameForward.entries()) {
if (fn(name, g)) yield [name, g];
}
}
glyphNames() {
return this.nameForward.keys();
}