Iosevka/font-src/kits/spiro-kit.js
2021-07-07 20:08:06 -07:00

354 lines
9.7 KiB
JavaScript

"use strict";
const SpiroJs = require("spiro");
const CurveUtil = require("../support/curve-util");
const Transform = require("../support/transform");
const { SpiroExpansionContext1, SpiroExpansionContext2 } = require("../support/spiro-expand");
const { fallback, mix, bez2, bez3 } = require("../support/utils");
const { SpiroGeometry, CombineGeometry } = require("../support/geometry");
exports.SetupBuilders = function (bindings) {
const { Contrast, GlobalTransform, Stroke, Superness } = bindings;
const g4 = (x, y, f) => ({ x, y, type: "g4", af: f });
const g2 = (x, y, f) => ({ x, y, type: "g2", af: f });
const corner = (x, y, f) => ({ x, y, type: "corner", af: f });
const flat = (x, y, f) => ({ x, y, type: "left", af: f });
const curl = (x, y, f) => ({ x, y, type: "right", af: f });
const close = f => ({ type: "close", af: f });
const end = f => ({ type: "end", af: f });
const straight = { l: flat, r: curl };
{
let directions = [
{ name: "up", x: 0, y: 1 },
{ name: "down", x: 0, y: -1 },
{ name: "left", x: -1, y: 0 },
{ name: "right", x: 1, y: 0 }
];
let adhesions = [
{ name: "start", l: 0, r: 0.01 },
{ name: "mid", l: -0.005, r: 0.005 },
{ name: "end", l: -0.01, r: 0 }
];
let knotTypes = [
[g4, g4, g4],
[g2, g2, g2],
[corner, corner, corner],
[straight, flat, curl]
];
for (const [sink, kl, kr] of knotTypes) {
for (const d of directions) {
sink[d.name] = {};
for (const a of adhesions) {
sink[d.name][a.name] = (x, y, af) => [
kl(x + d.x * a.l, y + d.y * a.l, af),
kr(x + d.x * a.r, y + d.y * a.r, af)
];
}
}
}
}
function widths(l, r) {
return function () {
return this.setWidth ? this.setWidth(l, r) : void 0;
};
}
widths.lhs = function (w) {
return widths(fallback(w, Stroke), 0);
};
widths.rhs = function (w) {
return widths(0, fallback(w, Stroke));
};
widths.center = function (w) {
return widths(fallback(w, Stroke) / 2, fallback(w, Stroke) / 2);
};
function heading(d) {
return function () {
return this.headsTo ? this.headsTo(d) : void 0;
};
}
widths.heading = function (l, r, d) {
return function () {
if (this.setWidth) this.setWidth(l, r);
return this.headsTo ? this.headsTo(d) : void 0;
};
};
widths.lhs.heading = function (w, d) {
return function () {
if (this.setWidth) this.setWidth(fallback(w, Stroke), 0);
return this.headsTo ? this.headsTo(d) : void 0;
};
};
widths.rhs.heading = function (w, d) {
return function () {
if (this.setWidth) this.setWidth(0, fallback(w, Stroke));
return this.headsTo ? this.headsTo(d) : void 0;
};
};
widths.center.heading = function (w, d) {
return function () {
if (this.setWidth) this.setWidth(fallback(w, Stroke) / 2, fallback(w, Stroke) / 2);
return this.headsTo ? this.headsTo(d) : void 0;
};
};
function disableContrast() {
return function () {
return (this.contrast = 1);
};
}
function unimportant() {
return this.setUnimportant ? this.setUnimportant() : void 0;
}
function important() {
return void 0;
}
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)
);
}
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
)
);
}
return innerKnots;
}
function afInterpolateSNeck(before, after, args) {
return [
g2(
mix(before.x, after.x, 1 / 2 - args.px / 6),
mix(before.y, after.y, 1 / 2 - args.py / 6),
widths(args.sw * args.ps, args.sw * (1 - args.ps))
),
g2(
mix(before.x, after.x, 1 / 2 + args.px / 6),
mix(before.y, after.y, 1 / 2 + args.py / 6),
widths(args.sw * (1 - args.ps), args.sw * args.ps)
)
];
}
function alsoThru(rx, ry, raf) {
return { type: "interpolate", rx, ry, raf, af: afInterpolate };
}
alsoThru.g2 = function (rx, ry, raf) {
return { type: "interpolate", rx, ry, raf, af: afInterpolateG2 };
};
alsoThru.sNeck = function (px, py, sw, ps) {
return { type: "interpolate", px, py, sw, ps, af: afInterpolateSNeck };
};
function alsoThruThem(es, raf, ty) {
return { type: "interpolate", rs: es, raf, ty, af: afInterpolateThem };
}
function bezControlsImpl(x1, y1, x2, y2, samples, raf, ty) {
let rs = [];
for (let j = 1; j < samples; j = j + 1)
rs.push([
bez3(0, x1, x2, 1, j / samples),
bez3(0, y1, y2, 1, j / samples),
j / samples
]);
return alsoThruThem(rs, raf);
}
function bezControls(x1, y1, x2, y2, _samples, raf) {
return bezControlsImpl(x1, y1, x2, y2, fallback(_samples, 3), raf);
}
function quadControls(x1, y1, _samples, raf) {
return bezControlsImpl(
(x1 * 2) / 3,
(y1 * 2) / 3,
mix(1, x1, 2 / 3),
mix(1, y1, 2 / 3),
fallback(_samples, 3),
raf
);
}
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]);
}
return { hv, vh: vh };
}
function buildHVImpl(samples, _superness) {
if (_superness) return build(samples, _superness).hv;
if (!cache[samples]) cache[samples] = build(samples, _superness);
return cache[samples].hv;
}
function buildVHImpl(samples, _superness) {
if (_superness) return build(samples, _superness).vh;
if (!cache[samples]) cache[samples] = build(samples, _superness);
return cache[samples].vh;
}
return [buildHVImpl, buildVHImpl];
})([]);
function archv(samples, superness) {
return alsoThruThem(buildHV(fallback(samples, DEFAULT_STEPS), superness));
}
archv.superness = function (s) {
return archv(DEFAULT_STEPS, s);
};
function arcvh(samples, superness) {
return alsoThruThem(buildVH(fallback(samples, DEFAULT_STEPS), superness));
}
arcvh.superness = function (s) {
return arcvh(DEFAULT_STEPS, s);
};
function complexThru(...a) {
return {
type: "interpolate",
af: function (before, after, args) {
let ks = [];
for (const knot of a) ks.push(knot.af.call(this, before, after, knot));
return ks;
}
};
}
function flattenImpl(sink, knots) {
for (const p of knots) {
if (p instanceof Array) flattenImpl(sink, p);
else sink.push(p);
}
}
function nCyclic(p, n) {
return (p + n + n) % n;
}
function flatten(s, knots0) {
let knots = [];
flattenImpl(knots, knots0);
let unwrapped = false;
for (let j = 0; j < knots.length; j = j + 1)
if (knots[j] && knots[j].type === "interpolate") {
const kBefore = knots[nCyclic(j - 1, knots.length)];
const kAfter = knots[nCyclic(j + 1, knots.length)];
knots[j] = knots[j].af.call(s, kBefore, kAfter, knots[j]);
unwrapped = true;
}
if (unwrapped) return flatten(s, knots);
return knots;
}
function dropTailKnot(knots) {
let last = knots[knots.length - 1];
if (last && (last.type === "close" || last.type === "end")) {
knots.length = knots.length - 1;
return last.type === "close";
} else {
return false;
}
}
function prepareSpiroKnots(_knots, s) {
let knots = _knots;
while (knots[0] && knots[0] instanceof Function) {
knots[0].call(s);
knots.splice(0, 1);
}
const closed = dropTailKnot(knots);
knots = flatten(s, knots);
return { knots, closed };
}
function iterateNormals(s, closed) {
let knotsP2 = s.getPass2Knots(closed, fallback(s.contrast, Contrast));
let s2 = new SpiroExpansionContext2(s.controlKnots, s.gizmo);
return SpiroJs.spiroToArcsOnContext(knotsP2, closed, s2);
}
function dispiro(...args) {
return function () {
let s = new SpiroExpansionContext1(this.gizmo || GlobalTransform);
let { knots, closed } = prepareSpiroKnots([].slice.call(args, 0), s);
for (const knot of knots) {
const ty = knot.type;
const af = knot.af;
knot.af = function () {
this.setType(ty);
return af ? af.apply(this, args) : void 0;
};
}
SpiroJs.spiroToArcsOnContext(knots, closed, s);
iterateNormals(s, closed);
iterateNormals(s, closed);
const { lhs, rhs } = s.expand(fallback(s.contrast, Contrast));
if (closed) {
this.includeGeometry(
new CombineGeometry([
new SpiroGeometry(lhs.slice(0, -1), closed, Transform.Id()),
new SpiroGeometry(rhs.reverse().slice(0, -1), closed, Transform.Id())
])
);
} 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());
this.includeGeometry(new SpiroGeometry(allKnots, true, Transform.Id()));
}
return { knots, lhsKnots: lhs, rhsKnots: rhs };
};
}
function spiroOutline(...args) {
return function () {
const gizmo = this.gizmo || GlobalTransform;
const g = new CurveUtil.BezToContoursSink(gizmo);
const { knots, closed } = prepareSpiroKnots(args, g);
return this.includeGeometry(new SpiroGeometry(knots, closed, gizmo));
};
}
return {
g4,
g2,
corner,
flat,
curl,
close,
end,
straight,
widths,
heading,
"disable-contrast": disableContrast,
unimportant,
important,
alsoThru,
alsoThruThem,
bezControls,
quadControls,
archv,
arcvh,
complexThru,
dispiro,
"spiro-outline": spiroOutline
};
};