Make basic operations to produce less closures (#2477)
This commit is contained in:
parent
78b0df7e7d
commit
1c9c2e09a8
12 changed files with 307 additions and 227 deletions
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -33,6 +33,7 @@ export class PenKnotCollector {
|
|||
setUnimportant() {
|
||||
if (this.m_lastKnot) this.m_lastKnot.profile = null;
|
||||
}
|
||||
setImportant() {} // Not used
|
||||
setContrast() {}
|
||||
|
||||
setProfile(profile) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue