diff --git a/packages/font-glyphs/src/common/shapes.ptl b/packages/font-glyphs/src/common/shapes.ptl index a8228fc33..fd2ac0271 100644 --- a/packages/font-glyphs/src/common/shapes.ptl +++ b/packages/font-glyphs/src/common/shapes.ptl @@ -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 diff --git a/packages/font-glyphs/src/letter-like/fraktur/common.ptl b/packages/font-glyphs/src/letter-like/fraktur/common.ptl index 456b4d918..b1e2bb24f 100644 --- a/packages/font-glyphs/src/letter-like/fraktur/common.ptl +++ b/packages/font-glyphs/src/letter-like/fraktur/common.ptl @@ -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] diff --git a/packages/font-glyphs/src/letter/latin/lower-e.ptl b/packages/font-glyphs/src/letter/latin/lower-e.ptl index 12bfc8c3c..37aceddde 100644 --- a/packages/font-glyphs/src/letter/latin/lower-e.ptl +++ b/packages/font-glyphs/src/letter/latin/lower-e.ptl @@ -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) diff --git a/packages/font-glyphs/src/letter/shared.ptl b/packages/font-glyphs/src/letter/shared.ptl index 0bfbb56fd..cad4c1655 100644 --- a/packages/font-glyphs/src/letter/shared.ptl +++ b/packages/font-glyphs/src/letter/shared.ptl @@ -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 diff --git a/packages/font-glyphs/src/meta/aesthetics.ptl b/packages/font-glyphs/src/meta/aesthetics.ptl index a6ad7b1d6..4d7f78a10 100644 --- a/packages/font-glyphs/src/meta/aesthetics.ptl +++ b/packages/font-glyphs/src/meta/aesthetics.ptl @@ -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] diff --git a/packages/font-glyphs/src/meta/macros.ptl b/packages/font-glyphs/src/meta/macros.ptl index 0d08b6808..ad850367d 100644 --- a/packages/font-glyphs/src/meta/macros.ptl +++ b/packages/font-glyphs/src/meta/macros.ptl @@ -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] diff --git a/packages/font-kits/src/spiro-kit.mjs b/packages/font-kits/src/spiro-kit.mjs index ee24410c0..dc9b4ed07 100644 --- a/packages/font-kits/src/spiro-kit.mjs +++ b/packages/font-kits/src/spiro-kit.mjs @@ -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); diff --git a/packages/font-otl/src/gsub-cv-ss.ptl b/packages/font-otl/src/gsub-cv-ss.ptl index 2ed6f5666..b4fa2c795 100644 --- a/packages/font-otl/src/gsub-cv-ss.ptl +++ b/packages/font-otl/src/gsub-cv-ss.ptl @@ -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 diff --git a/packages/geometry/src/spiro-control.mjs b/packages/geometry/src/spiro-control.mjs index 9506b1fd2..399990de0 100644 --- a/packages/geometry/src/spiro-control.mjs +++ b/packages/geometry/src/spiro-control.mjs @@ -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); } } diff --git a/packages/geometry/src/spiro-expand.mjs b/packages/geometry/src/spiro-expand.mjs index beb497de3..fa7ca04e7 100644 --- a/packages/geometry/src/spiro-expand.mjs +++ b/packages/geometry/src/spiro-expand.mjs @@ -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; diff --git a/packages/geometry/src/spiro-pen-expand.mjs b/packages/geometry/src/spiro-pen-expand.mjs index 45af7dc2a..8456c3392 100644 --- a/packages/geometry/src/spiro-pen-expand.mjs +++ b/packages/geometry/src/spiro-pen-expand.mjs @@ -33,6 +33,7 @@ export class PenKnotCollector { setUnimportant() { if (this.m_lastKnot) this.m_lastKnot.profile = null; } + setImportant() {} // Not used setContrast() {} setProfile(profile) { diff --git a/packages/glyph/src/store.mjs b/packages/glyph/src/store.mjs index 80209f6ed..68e135cff 100644 --- a/packages/glyph/src/store.mjs +++ b/packages/glyph/src/store.mjs @@ -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(); }