diff --git a/packages/font-kits/src/spiro-kit.mjs b/packages/font-kits/src/spiro-kit.mjs index ff6dcb04b..241f1fe22 100644 --- a/packages/font-kits/src/spiro-kit.mjs +++ b/packages/font-kits/src/spiro-kit.mjs @@ -1,7 +1,7 @@ import { DiSpiroGeometry, SpiroGeometry } from "@iosevka/geometry"; import { BiKnotCollector, - ControlKnot, + UserControlKnot, Interpolator, TerminateInstruction } from "@iosevka/geometry/spiro-control"; @@ -44,7 +44,11 @@ class SpiroOutlineImpl extends SpiroImplBase { applyToGlyph(glyph) { const { gizmo, collector } = this.createCollector(glyph); return glyph.includeGeometry( - new SpiroGeometry(gizmo, collector.closed, collector.controls) + new SpiroGeometry( + gizmo, + collector.closed, + collector.controls.map(k => k.toMono()) + ) ); } } @@ -75,7 +79,7 @@ export function SetupBuilders(bindings) { return (x, y, f) => { if (!isFinite(x)) throw new TypeError("NaN detected for X"); if (!isFinite(y)) throw new TypeError("NaN detected for Y"); - return new ControlKnot(type, x, y, f); + return new UserControlKnot(type, x, y, f); }; } const g4 = KnotType("g4"); @@ -233,8 +237,8 @@ export function SetupBuilders(bindings) { args.raf && args.raf.blend && rt !== void 0 ? args.raf.blend(rt) : args.raf - ? args.raf - : unimportant + ? args.raf + : unimportant ) ); } @@ -250,8 +254,8 @@ export function SetupBuilders(bindings) { args.raf && args.raf.blend && rt !== void 0 ? args.raf.blend(rt) : args.raf - ? args.raf - : unimportant + ? args.raf + : unimportant ) ); } diff --git a/packages/geometry-cache/src/index.mjs b/packages/geometry-cache/src/index.mjs index 51c6aa885..dbf5dd859 100644 --- a/packages/geometry-cache/src/index.mjs +++ b/packages/geometry-cache/src/index.mjs @@ -4,7 +4,7 @@ import zlib from "zlib"; import * as CurveUtil from "@iosevka/geometry/curve-util"; import { encode, decode } from "@msgpack/msgpack"; -const Edition = 34; +const Edition = 35; const MAX_AGE = 16; class GfEntry { constructor(age, value) { diff --git a/packages/geometry/src/index.mjs b/packages/geometry/src/index.mjs index dc0cd2b10..95832a6e4 100644 --- a/packages/geometry/src/index.mjs +++ b/packages/geometry/src/index.mjs @@ -8,8 +8,8 @@ import * as CurveUtil from "./curve-util.mjs"; import { Point } from "./point.mjs"; import { QuadifySink } from "./quadify.mjs"; import { SpiroExpander } from "./spiro-expand.mjs"; -import { Transform } from "./transform.mjs"; import { strokeArcs } from "./stroke.mjs"; +import { Transform } from "./transform.mjs"; export const CPLX_NON_EMPTY = 0x01; // A geometry tree that is not empty export const CPLX_NON_SIMPLE = 0x02; // A geometry tree that contains non-simple contours @@ -79,10 +79,7 @@ export class ContourSetGeometry extends GeometryBase { export class SpiroGeometry extends GeometryBase { constructor(gizmo, closed, knots) { super(); - this.m_knots = []; - for (const k of knots) { - this.m_knots.push({ type: k.type, x: k.x, y: k.y }); - } + this.m_knots = knots; this.m_closed = closed; this.m_gizmo = gizmo; this.m_cachedContours = null; @@ -120,7 +117,7 @@ export class SpiroGeometry extends GeometryBase { "SpiroGeometry", Format.gizmo(this.m_gizmo), this.m_closed, - Format.list(this.m_knots.map(Format.typedPoint)) + Format.list(this.m_knots.map(k => k.toShapeString())) ); } } @@ -140,17 +137,20 @@ export class DiSpiroGeometry extends GeometryBase { const expandResult = this.expand(); const lhs = [...expandResult.lhsUntransformed]; const rhs = [...expandResult.rhsUntransformed]; + // Reverse the RHS + for (const k of rhs) k.reverseType(); + rhs.reverse(); let rawGeometry; if (this.m_closed) { rawGeometry = new CombineGeometry([ new SpiroGeometry(this.m_gizmo, true, lhs), - new SpiroGeometry(this.m_gizmo, true, rhs.reverse()) + new SpiroGeometry(this.m_gizmo, true, rhs) ]); } else { lhs[0].type = lhs[lhs.length - 1].type = "corner"; rhs[0].type = rhs[rhs.length - 1].type = "corner"; - const allKnots = lhs.concat(rhs.reverse()); + const allKnots = lhs.concat(rhs); rawGeometry = new SpiroGeometry(this.m_gizmo, true, allKnots); } this.m_cachedContours = rawGeometry.asContours(); diff --git a/packages/geometry/src/spiro-control.mjs b/packages/geometry/src/spiro-control.mjs index 24df788ab..1aaca9f01 100644 --- a/packages/geometry/src/spiro-control.mjs +++ b/packages/geometry/src/spiro-control.mjs @@ -20,7 +20,7 @@ export class BiKnotCollector { c.call(this); } else if (Array.isArray(c)) { for (const item of c) this.add(item); - } else if (c instanceof ControlKnot) { + } else if (c instanceof UserControlKnot) { this.afterPreFunction = true; this.pushKnot(c); } else if (c instanceof TerminateInstruction) { @@ -102,6 +102,33 @@ export class BiKnotCollector { } } +/////////////////////////////////////////////////////////////////////////////////////////////////// + +export class MonoKnot { + constructor(type, unimportant, x, y) { + this.type = type; + this.x = x; + this.y = y; + this.unimportant = unimportant; + } + clone() { + const k1 = new MonoKnot(this.type, this.x, this.y, this.unimportant); + return k1; + } + toShapeString() { + return Format.tuple(this.type, this.unimportant, Format.n(this.x), Format.n(this.y)); + } + reverseType() { + if (this.type === "left") { + this.type = "right"; + } else if (this.type === "right") { + this.type = "left"; + } + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + class BiKnot { constructor(type, x, y, d1, d2) { this.type = type; @@ -144,6 +171,9 @@ class BiKnot { : "" ); } + toMono() { + return new MonoKnot(this.type, this.unimportant, this.x, this.y); + } } function nCyclic(p, n) { @@ -152,7 +182,7 @@ function nCyclic(p, n) { /////////////////////////////////////////////////////////////////////////////////////////////////// -export class ControlKnot { +export class UserControlKnot { constructor(type, x, y, af) { this.type = type; this.x = x; @@ -185,10 +215,3 @@ export function Interpolator(blender, restParameters) { for (const prop in restParameters) interpolator[prop] = restParameters[prop]; return interpolator; } - -export class ImportanceControlKnot extends ControlKnot { - constructor(type, x, y, unimportant) { - super(type, x, y, null); - this.unimportant = unimportant; - } -} diff --git a/packages/geometry/src/spiro-expand.mjs b/packages/geometry/src/spiro-expand.mjs index 7a4198882..0cc0ce7f9 100644 --- a/packages/geometry/src/spiro-expand.mjs +++ b/packages/geometry/src/spiro-expand.mjs @@ -2,7 +2,7 @@ import { linreg, mix } from "@iosevka/util"; import * as SpiroJs from "spiro"; import { Vec2 } from "./point.mjs"; -import { ControlKnot } from "./spiro-control.mjs"; +import { MonoKnot } from "./spiro-control.mjs"; /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -30,8 +30,9 @@ export class SpiroExpander { for (let j = 0; j < this.m_biKnotsT.length; j++) { const lhs = expanded.lhs[j]; const rhs = expanded.rhs[j]; - middles[j] = new ControlKnot( + middles[j] = new MonoKnot( this.m_biKnotsT[j].type, + this.m_biKnotsT[j].unimportant, mix(lhs.x, rhs.x, 0.5), mix(lhs.y, rhs.y, 0.5) ); @@ -39,17 +40,17 @@ export class SpiroExpander { return middles; } expand() { - const lhs = [], - rhs = [], - lhsUntransformed = [], - rhsUntransformed = []; + const lhsT = [], // transformed LHS + rhsT = [], // transformed RHS + lhsU = [], // untransformed LHS + rhsU = []; // untransformed RHS for (let j = 0; j < this.m_biKnotsT.length; j++) { - const knot = this.m_biKnotsT[j]; - lhs[j] = new ControlKnot(knot.type, 0, 0); - rhs[j] = new ControlKnot(reverseKnotType(knot.type), 0, 0); - lhsUntransformed[j] = new ControlKnot(knot.type, 0, 0); - rhsUntransformed[j] = new ControlKnot(reverseKnotType(knot.type), 0, 0); + const bk = this.m_biKnotsT[j]; + lhsT[j] = new MonoKnot(bk.type, bk.unimportant, 0, 0); + rhsT[j] = new MonoKnot(bk.type, bk.unimportant, 0, 0); + lhsU[j] = new MonoKnot(bk.type, bk.unimportant, 0, 0); + rhsU[j] = new MonoKnot(bk.type, bk.unimportant, 0, 0); } for (let j = 0; j < this.m_biKnotsT.length; j++) { @@ -63,17 +64,17 @@ export class SpiroExpander { dx = normalX(knotT.origTangent, this.m_contrast); dy = normalY(knotT.origTangent, this.m_contrast); } - lhs[j].x = knotT.x + knotT.d1 * dx; - lhs[j].y = knotT.y + knotT.d1 * dy; - rhs[j].x = knotT.x - knotT.d2 * dx; - rhs[j].y = knotT.y - knotT.d2 * dy; + lhsT[j].x = knotT.x + knotT.d1 * dx; + lhsT[j].y = knotT.y + knotT.d1 * dy; + rhsT[j].x = knotT.x - knotT.d2 * dx; + rhsT[j].y = knotT.y - knotT.d2 * dy; - this.m_gizmo.unapplyToSink(lhs[j], lhsUntransformed[j]); - this.m_gizmo.unapplyToSink(rhs[j], rhsUntransformed[j]); + this.m_gizmo.unapplyToSink(lhsT[j], lhsU[j]); + this.m_gizmo.unapplyToSink(rhsT[j], rhsU[j]); } - this.interpolateUnimportantKnots(lhs, rhs, lhsUntransformed, rhsUntransformed); - return { lhs, rhs, lhsUntransformed, rhsUntransformed }; + this.interpolateUnimportantKnots(lhsT, rhsT, lhsU, rhsU); + return { lhs: lhsT, rhs: rhsT, lhsUntransformed: lhsU, rhsUntransformed: rhsU }; } interpolateUnimportantKnots(lhsT, rhsT, lhsU, rhsU) { for (let j = 0; j < this.m_biKnotsU.length; j++) { @@ -143,9 +144,6 @@ function normalY(tangent) { return tangent.x / Math.hypot(tangent.x, tangent.y); } -function reverseKnotType(ty) { - return ty === "left" ? "right" : ty === "right" ? "left" : ty; -} function cyNth(a, j) { return a[j % a.length]; }