Move font building related files to font-src
This commit is contained in:
parent
9a2f862631
commit
c48bc20aa2
56 changed files with 18 additions and 18 deletions
16
font-src/support/anchor.js
Normal file
16
font-src/support/anchor.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = class Anchor {
|
||||
constructor(x, y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
transform(tfm) {
|
||||
return Anchor.transform(tfm, this);
|
||||
}
|
||||
static transform(tfm, a) {
|
||||
const x = a.x * tfm.xx + a.y * tfm.yx + tfm.x;
|
||||
const y = a.x * tfm.xy + a.y * tfm.yy + tfm.y;
|
||||
return new Anchor(x, y);
|
||||
}
|
||||
};
|
229
font-src/support/curve-util.js
Normal file
229
font-src/support/curve-util.js
Normal file
|
@ -0,0 +1,229 @@
|
|||
"use strict";
|
||||
|
||||
const TypoGeom = require("typo-geom");
|
||||
const Point = require("./point");
|
||||
const { mix } = require("./utils");
|
||||
const Transform = require("./transform");
|
||||
|
||||
exports.OffsetCurve = class OffsetCurve {
|
||||
constructor(bone, offset, contrast) {
|
||||
this.bone = bone;
|
||||
this.offset = offset;
|
||||
this.contrast = contrast;
|
||||
}
|
||||
eval(t) {
|
||||
const c = this.bone.eval(t);
|
||||
const d = this.bone.derivative(t);
|
||||
const absD = Math.hypot(d.x, d.y);
|
||||
return {
|
||||
x: c.x - (d.y / absD) * this.offset * this.contrast,
|
||||
y: c.y + (d.x / absD) * this.offset
|
||||
};
|
||||
}
|
||||
derivative(t) {
|
||||
const DELTA = 1 / 0x10000;
|
||||
const forward = this.eval(t + DELTA);
|
||||
const backward = this.eval(t - DELTA);
|
||||
return {
|
||||
x: (forward.x - backward.x) / (2 * DELTA),
|
||||
y: (forward.y - backward.y) / (2 * DELTA)
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
exports.curveToContour = function (curve, segments) {
|
||||
const z0 = curve.eval(0);
|
||||
const z1 = curve.eval(1);
|
||||
const offPoints = fixedCubify(curve, segments || 16);
|
||||
return [Point.cornerFrom(z0), ...offPoints, Point.cornerFrom(z1)];
|
||||
};
|
||||
|
||||
function convertContourToCubic(contour) {
|
||||
if (!contour || !contour.length) return [];
|
||||
|
||||
const newContour = [];
|
||||
let z0 = contour[0];
|
||||
newContour.push(Point.cornerFrom(z0));
|
||||
|
||||
for (let j = 1; j < contour.length; j++) {
|
||||
const z = contour[j];
|
||||
if (z.on) {
|
||||
newContour.push(Point.cornerFrom(z));
|
||||
z0 = z;
|
||||
} else if (z.cubic) {
|
||||
const z1 = z;
|
||||
const z2 = contour[j + 1];
|
||||
const z3 = contour[j + 2];
|
||||
|
||||
newContour.push(Point.cubicOffFrom(z1));
|
||||
newContour.push(Point.cubicOffFrom(z2));
|
||||
newContour.push(Point.cornerFrom(z3));
|
||||
|
||||
z0 = z3;
|
||||
j += 2;
|
||||
} else {
|
||||
const zc = z;
|
||||
let zf = contour[j + 1] || contour[0];
|
||||
const zfIsCorner = zf.on;
|
||||
if (!zfIsCorner) zf = Point.cornerFrom(zc).mix(0.5, zf);
|
||||
|
||||
newContour.push(Point.cubicOffFrom(z0).mix(2 / 3, zc));
|
||||
newContour.push(Point.cubicOffFrom(zf).mix(2 / 3, zc));
|
||||
newContour.push(Point.cornerFrom(zf));
|
||||
|
||||
z0 = zf;
|
||||
if (zfIsCorner) j++;
|
||||
}
|
||||
}
|
||||
|
||||
return newContour;
|
||||
}
|
||||
|
||||
function convertContourToCubicRev(contour) {
|
||||
return convertContourToCubic(contour).reverse();
|
||||
}
|
||||
|
||||
function autoCubify(arc, err) {
|
||||
const MaxSegments = 16;
|
||||
const Hits = 64;
|
||||
let offPoints = [];
|
||||
for (let nSeg = 1; nSeg <= MaxSegments; nSeg++) {
|
||||
const perSegHits = Math.ceil(Hits / nSeg);
|
||||
offPoints.length = 0;
|
||||
let good = true;
|
||||
out: for (let s = 0; s < nSeg; s++) {
|
||||
const tBefore = s / nSeg;
|
||||
const tAfter = (s + 1) / nSeg;
|
||||
const z0 = Point.cornerFrom(arc.eval(tBefore));
|
||||
const z3 = Point.cornerFrom(arc.eval(tAfter));
|
||||
const z1 = Point.cubicOffFrom(z0).addScale(1 / (3 * nSeg), arc.derivative(tBefore));
|
||||
const z2 = Point.cubicOffFrom(z3).addScale(-1 / (3 * nSeg), arc.derivative(tAfter));
|
||||
|
||||
if (s > 0) offPoints.push(z0);
|
||||
offPoints.push(z1, z2);
|
||||
|
||||
const bezArc = new TypoGeom.Arcs.Bez3(z0, z1, z2, z3);
|
||||
|
||||
for (let k = 1; k < perSegHits; k++) {
|
||||
const tk = k / perSegHits;
|
||||
const zTest = arc.eval(mix(tBefore, tAfter, tk));
|
||||
const zBez = bezArc.eval(tk);
|
||||
if (Math.hypot(zTest.x - zBez.x, zTest.y - zBez.y) > err) {
|
||||
good = false;
|
||||
break out;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (good) break;
|
||||
}
|
||||
return offPoints;
|
||||
}
|
||||
|
||||
function fixedCubify(arc, nSeg) {
|
||||
let offPoints = [];
|
||||
for (let s = 0; s < nSeg; s++) {
|
||||
const tBefore = s / nSeg;
|
||||
const tAfter = (s + 1) / nSeg;
|
||||
const z0 = Point.cornerFrom(arc.eval(tBefore));
|
||||
const z3 = Point.cornerFrom(arc.eval(tAfter));
|
||||
const z1 = Point.cubicOffFrom(z0).addScale(1 / (3 * nSeg), arc.derivative(tBefore));
|
||||
const z2 = Point.cubicOffFrom(z3).addScale(-1 / (3 * nSeg), arc.derivative(tAfter));
|
||||
|
||||
if (s > 0) offPoints.push(z0);
|
||||
offPoints.push(z1, z2);
|
||||
}
|
||||
return offPoints;
|
||||
}
|
||||
|
||||
function convertContourToArcs(contour) {
|
||||
if (!contour || !contour.length) return [];
|
||||
|
||||
const newContour = [];
|
||||
let z0 = Point.cornerFrom(contour[0]);
|
||||
|
||||
for (let j = 1; j < contour.length; j++) {
|
||||
const z = contour[j];
|
||||
if (z.on) {
|
||||
newContour.push(
|
||||
TypoGeom.Arcs.Bez3.fromStraightSegment(
|
||||
new TypoGeom.Arcs.StraightSegment(z0, Point.cornerFrom(z))
|
||||
)
|
||||
);
|
||||
z0 = z;
|
||||
} else if (z.cubic) {
|
||||
const z1 = z;
|
||||
const z2 = contour[j + 1];
|
||||
const z3 = contour[j + 2];
|
||||
newContour.push(
|
||||
new TypoGeom.Arcs.Bez3(
|
||||
z0,
|
||||
Point.cubicOffFrom(z1),
|
||||
Point.cubicOffFrom(z2),
|
||||
Point.cornerFrom(z3)
|
||||
)
|
||||
);
|
||||
z0 = z3;
|
||||
j += 2;
|
||||
} else {
|
||||
const zc = z;
|
||||
let zf = contour[j + 1] || contour[0];
|
||||
const zfIsCorner = zf.on;
|
||||
if (!zfIsCorner) zf = Point.cornerFrom(zc).mix(0.5, zf);
|
||||
|
||||
newContour.push(
|
||||
new TypoGeom.Arcs.Bez3(
|
||||
z0,
|
||||
Point.cubicOffFrom(z0).mix(2 / 3, zc),
|
||||
Point.cubicOffFrom(zf).mix(2 / 3, zc),
|
||||
Point.cornerFrom(zf)
|
||||
)
|
||||
);
|
||||
|
||||
z0 = zf;
|
||||
if (zfIsCorner) j++;
|
||||
}
|
||||
}
|
||||
|
||||
return newContour;
|
||||
}
|
||||
|
||||
function convertShapeToArcs(shape) {
|
||||
return shape.map(convertContourToArcs);
|
||||
}
|
||||
|
||||
exports.convertContourToCubic = convertContourToCubic;
|
||||
exports.convertContourToCubicRev = convertContourToCubicRev;
|
||||
exports.autoCubify = autoCubify;
|
||||
exports.fixedCubify = fixedCubify;
|
||||
exports.convertShapeToArcs = convertShapeToArcs;
|
||||
|
||||
exports.ArcFlattener = class ArcFlattener {
|
||||
constructor(gizmo) {
|
||||
this.gizmo = gizmo || Transform.Id();
|
||||
this.contours = [];
|
||||
this.lastContour = [];
|
||||
}
|
||||
beginShape() {}
|
||||
endShape() {
|
||||
if (this.lastContour.length) {
|
||||
this.contours.push(this.lastContour);
|
||||
}
|
||||
this.lastContour = [];
|
||||
}
|
||||
moveTo(x, y) {
|
||||
this.endShape();
|
||||
this.lastContour.push(Point.transformedXY(this.gizmo, x, y, true));
|
||||
}
|
||||
lineTo(x, y) {
|
||||
this.lastContour.push(Point.transformedXY(this.gizmo, x, y, true));
|
||||
}
|
||||
curveTo(xc, yc, x, y) {
|
||||
this.lastContour.push(Point.transformedXY(this.gizmo, xc, yc, false, false));
|
||||
this.lastContour.push(Point.transformedXY(this.gizmo, x, y, true));
|
||||
}
|
||||
cubicTo(x1, y1, x2, y2, x, y) {
|
||||
this.lastContour.push(Point.transformedXY(this.gizmo, x1, y1, false, true));
|
||||
this.lastContour.push(Point.transformedXY(this.gizmo, x2, y2, false, true));
|
||||
this.lastContour.push(Point.transformedXY(this.gizmo, x, y, true));
|
||||
}
|
||||
};
|
138
font-src/support/glyph.js
Normal file
138
font-src/support/glyph.js
Normal file
|
@ -0,0 +1,138 @@
|
|||
"use strict";
|
||||
|
||||
const Transform = require("./transform");
|
||||
const Point = require("./point");
|
||||
const Anchor = require("./anchor");
|
||||
|
||||
module.exports = class Glyph {
|
||||
constructor(name) {
|
||||
Object.defineProperty(this, "name", { value: name, writable: false });
|
||||
this.unicode = [];
|
||||
this.contours = [];
|
||||
this.advanceWidth = 500;
|
||||
this.autoRefPriority = 0;
|
||||
this.markAnchors = {};
|
||||
this.baseAnchors = {};
|
||||
this.gizmo = Transform.Id();
|
||||
this.dependencies = [];
|
||||
this.defaultTag = null;
|
||||
}
|
||||
// PTL pattern matching
|
||||
static unapply(obj, arity) {
|
||||
if (obj instanceof Glyph) return [obj];
|
||||
else return null;
|
||||
}
|
||||
// Metrics
|
||||
setWidth(w) {
|
||||
this.advanceWidth = w;
|
||||
}
|
||||
// Encoding
|
||||
assignUnicode(u) {
|
||||
if (typeof u === "string") this.unicode.push(u.codePointAt(0));
|
||||
else this.unicode.push(u);
|
||||
}
|
||||
// Dependency
|
||||
dependsOn(glyph) {
|
||||
if (glyph.name) this.dependencies.push(glyph.name);
|
||||
if (glyph.dependencies) for (const dep of glyph.dependencies) this.dependencies.push(dep);
|
||||
}
|
||||
// Contour Tagging
|
||||
reTagContour(oldTag, newTag) {
|
||||
for (const c of this.contours) if (c.tag === oldTag) c.tag = newTag;
|
||||
}
|
||||
ejectContour(tag) {
|
||||
let i = 0,
|
||||
j = 0;
|
||||
for (; i < this.contours.length; i++) {
|
||||
if (!this.contours[i].tag || this.contours[i].tag !== tag)
|
||||
this.contours[j++] = this.contours[i];
|
||||
}
|
||||
this.contours.length = j;
|
||||
}
|
||||
// Inclusion
|
||||
include(component, copyAnchors, copyWidth) {
|
||||
if (component instanceof Function) {
|
||||
const t = this.defaultTag;
|
||||
if (component.tag) this.defaultTag = component.tag;
|
||||
component.call(this, copyAnchors, copyWidth);
|
||||
this.defaultTag = t;
|
||||
return;
|
||||
} else if (component instanceof Transform) {
|
||||
this.applyTransform(component, copyAnchors);
|
||||
} else if (component instanceof Array) {
|
||||
throw new Error("Attempt to include an array.");
|
||||
} else {
|
||||
this.includeGlyph(component, copyAnchors, copyWidth);
|
||||
}
|
||||
}
|
||||
includeGlyph(g, copyAnchors, copyWidth) {
|
||||
if (g instanceof Function) throw new Error("Unreachable");
|
||||
|
||||
// Combine anchors and get offset
|
||||
let shift = { x: 0, y: 0 };
|
||||
if (g.markAnchors) {
|
||||
for (const m in this.baseAnchors) {
|
||||
this.combineAnchor(shift, this.baseAnchors[m], g.markAnchors[m], g.baseAnchors);
|
||||
}
|
||||
}
|
||||
|
||||
this.includeGeometry(g, shift.x, shift.y);
|
||||
if (copyAnchors || g.isMarkSet) this.copyAnchors(g);
|
||||
if (copyWidth && g.advanceWidth >= 0) this.advanceWidth = g.advanceWidth;
|
||||
this.dependsOn(g);
|
||||
}
|
||||
includeGeometry(geom, shiftX, shiftY) {
|
||||
if (!geom || !geom.contours) return;
|
||||
for (const contour of geom.contours) {
|
||||
let c = [];
|
||||
c.tag = contour.tag || geom.tag || this.defaultTag;
|
||||
for (const z of contour) c.push(Point.translated(z, shiftX, shiftY));
|
||||
this.contours.push(c);
|
||||
}
|
||||
}
|
||||
combineAnchor(shift, baseThis, markThat, basesThat) {
|
||||
if (!baseThis || !markThat) return;
|
||||
shift.x = baseThis.x - markThat.x;
|
||||
shift.y = baseThis.y - markThat.y;
|
||||
if (basesThat) {
|
||||
for (const bk in basesThat) {
|
||||
this.baseAnchors[bk] = new Anchor(
|
||||
shift.x + basesThat[bk].x,
|
||||
shift.y + basesThat[bk].y
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
copyAnchors(g) {
|
||||
if (g.markAnchors) for (const k in g.markAnchors) this.markAnchors[k] = g.markAnchors[k];
|
||||
if (g.baseAnchors) for (const k in g.baseAnchors) this.baseAnchors[k] = g.baseAnchors[k];
|
||||
}
|
||||
applyTransform(tfm, alsoAnchors) {
|
||||
for (const c of this.contours)
|
||||
for (let j = 0; j < c.length; j++) {
|
||||
c[j] = Point.transformed(tfm, c[j]);
|
||||
}
|
||||
if (alsoAnchors) {
|
||||
for (const k in this.baseAnchors)
|
||||
this.baseAnchors[k] = Anchor.transform(tfm, this.baseAnchors[k]);
|
||||
for (const k in this.markAnchors)
|
||||
this.markAnchors[k] = Anchor.transform(tfm, this.markAnchors[k]);
|
||||
}
|
||||
}
|
||||
// Anchors
|
||||
setBaseAnchor(id, x, y) {
|
||||
this.baseAnchors[id] = new Anchor(x, y).transform(this.gizmo);
|
||||
}
|
||||
setMarkAnchor(id, x, y, mbx, mby) {
|
||||
this.markAnchors[id] = new Anchor(x, y).transform(this.gizmo);
|
||||
if (mbx != null && mby != null) {
|
||||
this.baseAnchors[id] = new Anchor(mbx, mby).transform(this.gizmo);
|
||||
}
|
||||
}
|
||||
deleteBaseAnchor(id) {
|
||||
delete this.baseAnchors[id];
|
||||
}
|
||||
deleteMarkAnchor(id) {
|
||||
delete this.markAnchors[id];
|
||||
}
|
||||
};
|
238
font-src/support/gr.js
Normal file
238
font-src/support/gr.js
Normal file
|
@ -0,0 +1,238 @@
|
|||
"use strict";
|
||||
|
||||
const Dotless = {
|
||||
tag: "dtls",
|
||||
get(glyph) {
|
||||
if (glyph && glyph.related) return glyph.related.dotless;
|
||||
else return null;
|
||||
},
|
||||
set(glyph, toGid) {
|
||||
if (typeof toGid !== "string") throw new Error("Must supply a GID instead of a glyph");
|
||||
if (!glyph.related) glyph.related = {};
|
||||
glyph.related.dotless = toGid;
|
||||
},
|
||||
amendName(name) {
|
||||
return name + ".dotless";
|
||||
}
|
||||
};
|
||||
|
||||
const CvDecompose = {
|
||||
get(glyph) {
|
||||
if (glyph && glyph.related) return glyph.related.CvDecompose;
|
||||
else return null;
|
||||
},
|
||||
set(glyph, composition) {
|
||||
if (!Array.isArray(composition)) throw new Error("Must supply a GID array");
|
||||
if (!glyph.related) glyph.related = {};
|
||||
glyph.related.CvDecompose = composition;
|
||||
}
|
||||
};
|
||||
|
||||
const CcmpDecompose = {
|
||||
get(glyph) {
|
||||
if (glyph && glyph.related) return glyph.related.CcmpDecompose;
|
||||
else return null;
|
||||
},
|
||||
set(glyph, composition) {
|
||||
if (!Array.isArray(composition)) throw new Error("Must supply a GID array");
|
||||
if (!glyph.related) glyph.related = {};
|
||||
glyph.related.CcmpDecompose = composition;
|
||||
}
|
||||
};
|
||||
|
||||
const TieMark = {
|
||||
tag: "TMRK",
|
||||
get(glyph) {
|
||||
if (glyph && glyph.related) return glyph.related.TieMark;
|
||||
else return null;
|
||||
},
|
||||
set(glyph, toGid) {
|
||||
if (typeof toGid !== "string") throw new Error("Must supply a GID instead of a glyph");
|
||||
if (!glyph.related) glyph.related = {};
|
||||
glyph.related.TieMark = toGid;
|
||||
},
|
||||
amendName(name) {
|
||||
return `TieMark{${name}}`;
|
||||
}
|
||||
};
|
||||
|
||||
const TieGlyph = {
|
||||
get(glyph) {
|
||||
if (glyph && glyph.related) return glyph.related.TieMark;
|
||||
else return null;
|
||||
},
|
||||
set(glyph) {
|
||||
if (!glyph.related) glyph.related = {};
|
||||
glyph.related.TieMark = true;
|
||||
}
|
||||
};
|
||||
|
||||
const DoNotDeriveVariants = {
|
||||
get(glyph) {
|
||||
if (glyph && glyph.related) return glyph.related.DoNotDeriveVariants;
|
||||
else return null;
|
||||
},
|
||||
set(glyph) {
|
||||
if (!glyph.related) glyph.related = {};
|
||||
glyph.related.DoNotDeriveVariants = true;
|
||||
}
|
||||
};
|
||||
|
||||
const CvTagCache = new Map();
|
||||
function Cv(tag) {
|
||||
if (CvTagCache.has(tag)) return CvTagCache.get(tag);
|
||||
const rel = {
|
||||
tag,
|
||||
get(glyph) {
|
||||
if (glyph && glyph.related && glyph.related.cv) return glyph.related.cv[tag];
|
||||
else return null;
|
||||
},
|
||||
set(glyph, toGid) {
|
||||
if (typeof toGid !== "string") throw new Error("Must supply a GID instead of a glyph");
|
||||
if (!glyph.related) glyph.related = {};
|
||||
if (!glyph.related.cv) glyph.related.cv = {};
|
||||
glyph.related.cv[tag] = toGid;
|
||||
},
|
||||
amendName(name) {
|
||||
return name + "." + tag;
|
||||
}
|
||||
};
|
||||
CvTagCache.set(tag, rel);
|
||||
return rel;
|
||||
}
|
||||
|
||||
const DotlessOrNot = {
|
||||
optional: true,
|
||||
query(glyph) {
|
||||
if (Dotless.get(glyph)) return [Dotless];
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const AnyCv = {
|
||||
optional: false,
|
||||
query(glyph) {
|
||||
let ret = [];
|
||||
if (glyph && glyph.related && glyph.related.cv) {
|
||||
for (const tag in glyph.related.cv) {
|
||||
const rel = Cv(tag);
|
||||
if (rel.get(glyph)) ret.push(rel);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
const AnyDerivingCv = {
|
||||
optional: false,
|
||||
query(glyph) {
|
||||
let ret = [];
|
||||
if (glyph && !DoNotDeriveVariants.get(glyph) && glyph.related && glyph.related.cv) {
|
||||
for (const tag in glyph.related.cv) {
|
||||
const rel = Cv(tag);
|
||||
if (rel.get(glyph)) ret.push(rel);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
function getGrTreeImpl(gid, grSetList, fnGidToGlyph, sink) {
|
||||
if (!grSetList.length) return;
|
||||
const g = fnGidToGlyph(gid);
|
||||
if (!g) return;
|
||||
const grq = grSetList[0];
|
||||
const grs = grq.query(g);
|
||||
if ((!grs || !grs.length) && grq.optional) {
|
||||
getGrTreeImpl(gid, grSetList.slice(1), fnGidToGlyph, sink);
|
||||
} else if (grs && grs.length) {
|
||||
if (grq.optional) {
|
||||
getGrTreeImpl(gid, grSetList.slice(1), fnGidToGlyph, sink);
|
||||
}
|
||||
for (const gr of grs) {
|
||||
sink.push([gr, gid, gr.get(g)]);
|
||||
getGrTreeImpl(gr.get(g), grSetList.slice(1), fnGidToGlyph, sink);
|
||||
}
|
||||
}
|
||||
}
|
||||
function getGrTree(gid, grSetList, fnGidToGlyph) {
|
||||
if (typeof gid !== "string") throw new TypeError("Must supply a GID");
|
||||
let sink = [];
|
||||
getGrTreeImpl(gid, grSetList, fnGidToGlyph, sink);
|
||||
return sink;
|
||||
}
|
||||
|
||||
function gidListSame(a, b) {
|
||||
for (let j = 0; j < a.length; j++) {
|
||||
if (a[j] !== b[j]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function gidListMap(gidList, gr, fnGidToGlyph) {
|
||||
let effective = false;
|
||||
const gidList1 = gidList.slice(0);
|
||||
for (let j = 0; j < gidList1.length; j++) {
|
||||
const g = fnGidToGlyph(gidList[j]);
|
||||
if (g && gr.get(g)) {
|
||||
gidList1[j] = gr.get(g);
|
||||
effective = true;
|
||||
}
|
||||
}
|
||||
if (effective) return gidList1;
|
||||
else return null;
|
||||
}
|
||||
|
||||
function collectGidLists(gidListOrig, gidList, grl, excluded, fnGidToGlyph, sink) {
|
||||
if (!grl.length) {
|
||||
sink.push(gidList);
|
||||
return;
|
||||
} else {
|
||||
const gr = grl[0],
|
||||
grlRest = grl.slice(1);
|
||||
collectGidLists(gidListOrig, gidList, grlRest, excluded, fnGidToGlyph, sink);
|
||||
if (gr !== excluded) {
|
||||
const gidList1 = gidListMap(gidList, gr, fnGidToGlyph);
|
||||
if (gidList1 && !gidListSame(gidList, gidList1))
|
||||
collectGidLists(gidListOrig, gidList1, grlRest, excluded, fnGidToGlyph, sink);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getGrMesh(gidList, grq, fnGidToGlyph) {
|
||||
if (typeof gidList === "string" || !Array.isArray(gidList))
|
||||
throw new TypeError(`glyphs must be a glyph array!`);
|
||||
|
||||
const allGrSet = new Set();
|
||||
for (const g of gidList) {
|
||||
for (const gr of grq.query(fnGidToGlyph(g))) allGrSet.add(gr);
|
||||
}
|
||||
|
||||
const allGrList = Array.from(allGrSet);
|
||||
let ret = [];
|
||||
for (const gr of allGrList) {
|
||||
const col = [];
|
||||
collectGidLists(gidList, gidList, allGrList, gr, fnGidToGlyph, col);
|
||||
if (!col.length) continue;
|
||||
for (const from of col) {
|
||||
const to = gidListMap(from, gr, fnGidToGlyph);
|
||||
if (to && !gidListSame(from, to)) {
|
||||
ret.push([gr, from, to]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
exports.Dotless = Dotless;
|
||||
exports.Cv = Cv;
|
||||
exports.AnyCv = AnyCv;
|
||||
exports.DotlessOrNot = DotlessOrNot;
|
||||
exports.getGrTree = getGrTree;
|
||||
exports.getGrMesh = getGrMesh;
|
||||
exports.TieMark = TieMark;
|
||||
exports.TieGlyph = TieGlyph;
|
||||
exports.DoNotDeriveVariants = DoNotDeriveVariants;
|
||||
exports.AnyDerivingCv = AnyDerivingCv;
|
||||
exports.CcmpDecompose = CcmpDecompose;
|
||||
exports.CvDecompose = CvDecompose;
|
26
font-src/support/ligation-data.js
Normal file
26
font-src/support/ligation-data.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = function formVariantData(data, para) {
|
||||
const optInBuildup = {};
|
||||
const optOutBuildup = {};
|
||||
|
||||
const hives = {};
|
||||
hives["default"] = { caltBuildup: [] };
|
||||
for (const gr in data.simple) {
|
||||
hives[gr] = { appends: { caltBuildup: [data.simple[gr].ligGroup] } };
|
||||
}
|
||||
for (const gr in data.composite) {
|
||||
const comp = data.composite[gr];
|
||||
if (!comp.tag) continue;
|
||||
if (comp.isOptOut) {
|
||||
optOutBuildup[comp.tag] = comp.buildup;
|
||||
} else {
|
||||
optInBuildup[comp.tag] = comp.buildup;
|
||||
}
|
||||
if (!comp.isOptOut) {
|
||||
hives[gr] = { caltBuildup: [...comp.buildup] };
|
||||
}
|
||||
}
|
||||
|
||||
return { defaultBuildup: { ...optInBuildup, ...optOutBuildup }, hives };
|
||||
};
|
5
font-src/support/mask-bit.js
Normal file
5
font-src/support/mask-bit.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = function maskBit(x, y) {
|
||||
return x & (1 << y);
|
||||
};
|
114
font-src/support/monotonic-interpolate.js
Normal file
114
font-src/support/monotonic-interpolate.js
Normal file
|
@ -0,0 +1,114 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = function (xs, ys) {
|
||||
let i,
|
||||
length = xs.length;
|
||||
|
||||
// Deal with length issues
|
||||
if (length != ys.length) {
|
||||
throw "Need an equal count of xs and ys.";
|
||||
}
|
||||
if (length === 0) {
|
||||
return function () {
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
if (length === 1) {
|
||||
// Impl: Precomputing the result prevents problems if ys is mutated later and allows garbage collection of ys
|
||||
// Impl: Unary plus properly converts values to numbers
|
||||
let result = +ys[0];
|
||||
return function () {
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
// Rearrange xs and ys so that xs is sorted
|
||||
let indexes = [];
|
||||
for (i = 0; i < length; i++) {
|
||||
indexes.push(i);
|
||||
}
|
||||
indexes.sort(function (a, b) {
|
||||
return xs[a] < xs[b] ? -1 : 1;
|
||||
});
|
||||
let oldXs = xs,
|
||||
oldYs = ys;
|
||||
// Impl: Creating new arrays also prevents problems if the input arrays are mutated later
|
||||
xs = [];
|
||||
ys = [];
|
||||
// Impl: Unary plus properly converts values to numbers
|
||||
for (i = 0; i < length; i++) {
|
||||
xs.push(+oldXs[indexes[i]]);
|
||||
ys.push(+oldYs[indexes[i]]);
|
||||
}
|
||||
|
||||
// Get consecutive differences and slopes
|
||||
let dys = [],
|
||||
dxs = [],
|
||||
ms = [];
|
||||
for (i = 0; i < length - 1; i++) {
|
||||
const dx = xs[i + 1] - xs[i],
|
||||
dy = ys[i + 1] - ys[i];
|
||||
dxs.push(dx);
|
||||
dys.push(dy);
|
||||
ms.push(dy / dx);
|
||||
}
|
||||
|
||||
// Get degree-1 coefficients
|
||||
let c1s = [ms[0]];
|
||||
for (i = 0; i < dxs.length - 1; i++) {
|
||||
const m = ms[i],
|
||||
mNext = ms[i + 1];
|
||||
if (m * mNext <= 0) {
|
||||
c1s.push(0);
|
||||
} else {
|
||||
const dx = dxs[i],
|
||||
dxNext = dxs[i + 1],
|
||||
common = dx + dxNext;
|
||||
c1s.push((3 * common) / ((common + dxNext) / m + (common + dx) / mNext));
|
||||
}
|
||||
}
|
||||
c1s.push(ms[ms.length - 1]);
|
||||
|
||||
// Get degree-2 and degree-3 coefficients
|
||||
let c2s = [],
|
||||
c3s = [];
|
||||
for (i = 0; i < c1s.length - 1; i++) {
|
||||
const c1 = c1s[i],
|
||||
m = ms[i],
|
||||
invDx = 1 / dxs[i],
|
||||
common = c1 + c1s[i + 1] - m - m;
|
||||
c2s.push((m - c1 - common) * invDx);
|
||||
c3s.push(common * invDx * invDx);
|
||||
}
|
||||
|
||||
// Return interpolant function
|
||||
return function (x) {
|
||||
// The rightmost point in the dataset should give an exact result
|
||||
let i = xs.length - 1;
|
||||
if (x == xs[i]) {
|
||||
return ys[i];
|
||||
}
|
||||
|
||||
// Search for the interval x is in, returning the corresponding y if x is one of the original xs
|
||||
let low = 0,
|
||||
mid,
|
||||
high = c3s.length - 1;
|
||||
while (low <= high) {
|
||||
mid = Math.floor(0.5 * (low + high));
|
||||
let xHere = xs[mid];
|
||||
if (xHere < x) {
|
||||
low = mid + 1;
|
||||
} else if (xHere > x) {
|
||||
high = mid - 1;
|
||||
} else {
|
||||
return ys[mid];
|
||||
}
|
||||
}
|
||||
i = Math.max(0, high);
|
||||
|
||||
// Interpolate
|
||||
let diff = x - xs[i],
|
||||
diffSq = diff * diff;
|
||||
return ys[i] + c1s[i] * diff + c2s[i] * diffSq + c3s[i] * diff * diffSq;
|
||||
};
|
||||
};
|
100
font-src/support/parameters.js
Normal file
100
font-src/support/parameters.js
Normal file
|
@ -0,0 +1,100 @@
|
|||
"use strict";
|
||||
|
||||
const monotonicInterpolate = require("./monotonic-interpolate");
|
||||
|
||||
function build(parametersData, styles, blendArgs) {
|
||||
const sink = {};
|
||||
for (const item of styles) intro(parametersData, item, blendArgs, sink);
|
||||
return sink;
|
||||
}
|
||||
exports.build = build;
|
||||
|
||||
function intro(source, style, blendArgs, sink) {
|
||||
let hive = source[style];
|
||||
if (!hive) return;
|
||||
hive = { ...hive };
|
||||
|
||||
if (hive.inherits) {
|
||||
for (const hn of hive.inherits) intro(source, hn, blendArgs, sink);
|
||||
delete hive.inherits;
|
||||
}
|
||||
if (hive.multiplies) {
|
||||
const mu = hiveBlend(hive.multiplies, getBlendArg(blendArgs, style), sink);
|
||||
for (const k in mu) sink[k] = (sink[k] || 0) * mu[k];
|
||||
delete hive.multiplies;
|
||||
}
|
||||
if (hive.adds) {
|
||||
const mu = hiveBlend(hive.adds, getBlendArg(blendArgs, style), sink);
|
||||
for (const k in mu) sink[k] = (sink[k] || 0) + mu[k];
|
||||
delete hive.adds;
|
||||
}
|
||||
if (hive.appends) {
|
||||
const mu = hive.appends;
|
||||
for (const k in mu) sink[k] = [...(sink[k] || []), ...mu[k]];
|
||||
delete hive.appends;
|
||||
}
|
||||
hive = hiveBlend(hive, getBlendArg(blendArgs, style), sink);
|
||||
for (const k in hive) sink[k] = hive[k];
|
||||
}
|
||||
|
||||
function getBlendArg(blendArgs, style) {
|
||||
if (!blendArgs) return undefined;
|
||||
return blendArgs[style];
|
||||
}
|
||||
|
||||
function hiveBlend(hive, value, sink) {
|
||||
if (!hive || !hive.blend || value == null) return hive;
|
||||
|
||||
const generatedHive = {};
|
||||
const block = hive.blend;
|
||||
let keys = new Set();
|
||||
for (const grade in block) {
|
||||
sink[grade] = block[grade];
|
||||
if (!isFinite(parseFloat(grade))) continue;
|
||||
for (const key in block[grade]) {
|
||||
if (block[grade][key] == null) continue;
|
||||
keys.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
for (const key of keys) {
|
||||
let xs = [],
|
||||
ys = [];
|
||||
for (const grade in block) {
|
||||
if (!isFinite(parseFloat(grade))) continue;
|
||||
if (block[grade][key] == null) continue;
|
||||
xs.push(grade);
|
||||
ys.push(block[grade][key]);
|
||||
}
|
||||
generatedHive[key] = monotonicInterpolate(xs, ys)(value);
|
||||
}
|
||||
return generatedHive;
|
||||
}
|
||||
|
||||
function numericConfigExists(x) {
|
||||
return x != null && isFinite(x);
|
||||
}
|
||||
function applyMetricOverride(para, mo) {
|
||||
if (numericConfigExists(mo.leading)) {
|
||||
para.leading = mo.leading;
|
||||
}
|
||||
if (numericConfigExists(mo.winMetricAscenderPad)) {
|
||||
para.winMetricAscenderPad = mo.winMetricAscenderPad;
|
||||
}
|
||||
if (numericConfigExists(mo.winMetricDescenderPad)) {
|
||||
para.winMetricDescenderPad = mo.winMetricDescenderPad;
|
||||
}
|
||||
if (numericConfigExists(mo.powerlineScaleY)) {
|
||||
para.powerlineScaleY = mo.powerlineScaleY;
|
||||
}
|
||||
if (numericConfigExists(mo.powerlineScaleX)) {
|
||||
para.powerlineScaleX = mo.powerlineScaleX;
|
||||
}
|
||||
if (numericConfigExists(mo.powerlineShiftY)) {
|
||||
para.powerlineShiftY = mo.powerlineShiftY;
|
||||
}
|
||||
if (numericConfigExists(mo.powerlineShiftX)) {
|
||||
para.powerlineShiftX = mo.powerlineShiftX;
|
||||
}
|
||||
}
|
||||
exports.applyMetricOverride = applyMetricOverride;
|
73
font-src/support/point.js
Normal file
73
font-src/support/point.js
Normal file
|
@ -0,0 +1,73 @@
|
|||
"use strict";
|
||||
|
||||
const { z } = require("typo-geom");
|
||||
|
||||
module.exports = class Point {
|
||||
constructor(x, y, on, cubic) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.on = on;
|
||||
this.cubic = cubic;
|
||||
}
|
||||
add(z2) {
|
||||
return this.addScale(1, z2);
|
||||
}
|
||||
addScale(scale, z2) {
|
||||
return new Point(this.x + scale * z2.x, this.y + scale * z2.y, this.on, this.cubic);
|
||||
}
|
||||
mix(scale, z2) {
|
||||
return new Point(
|
||||
this.x + scale * (z2.x - this.x),
|
||||
this.y + scale * (z2.y - this.y),
|
||||
this.on,
|
||||
this.cubic
|
||||
);
|
||||
}
|
||||
scale(t) {
|
||||
return new Point(t * this.x, t * this.y, this.on, this.cubic);
|
||||
}
|
||||
round(d) {
|
||||
return new Point(
|
||||
Math.round(d * this.x) / d,
|
||||
Math.round(d * this.y) / d,
|
||||
this.on,
|
||||
this.cubic
|
||||
);
|
||||
}
|
||||
|
||||
static from(z, on, cubic) {
|
||||
return new Point(z.x || 0, z.y || 0, on, cubic);
|
||||
}
|
||||
static cornerFrom(z) {
|
||||
return new Point(z.x || 0, z.y || 0, true, false);
|
||||
}
|
||||
static offFrom(z) {
|
||||
return new Point(z.x || 0, z.y || 0, false, false);
|
||||
}
|
||||
static cubicOffFrom(z) {
|
||||
return new Point(z.x || 0, z.y || 0, false, true);
|
||||
}
|
||||
static cornerFromXY(x, y) {
|
||||
return new Point(x || 0, y || 0, true, false);
|
||||
}
|
||||
static offFromXY(x, y) {
|
||||
return new Point(x || 0, y || 0, false, false);
|
||||
}
|
||||
static cubicOffFromXY(x, y) {
|
||||
return new Point(x || 0, y || 0, false, true);
|
||||
}
|
||||
static transformed(tfm, z) {
|
||||
return Point.transformedXY(tfm, z.x, z.y, z.on, z.cubic);
|
||||
}
|
||||
static transformedXY(tfm, x, y, on, cubic) {
|
||||
return new Point(
|
||||
x * tfm.xx + y * tfm.yx + tfm.x || 0,
|
||||
x * tfm.xy + y * tfm.yy + tfm.y || 0,
|
||||
on,
|
||||
cubic
|
||||
);
|
||||
}
|
||||
static translated(z, dx, dy) {
|
||||
return new Point(z.x + dx || 0, z.y + dy || 0, z.on, z.cubic);
|
||||
}
|
||||
};
|
132
font-src/support/spiro-expand.js
Normal file
132
font-src/support/spiro-expand.js
Normal file
|
@ -0,0 +1,132 @@
|
|||
const Transform = require("./transform");
|
||||
const { linreg } = require("./utils");
|
||||
|
||||
module.exports = class SpiroExpansionContext {
|
||||
constructor() {
|
||||
this.gizmo = Transform.Id();
|
||||
this.controlKnots = [];
|
||||
this.defaultD1 = 0;
|
||||
this.defaultD2 = 0;
|
||||
}
|
||||
beginShape() {}
|
||||
endShape() {}
|
||||
moveTo(x, y, unimportant) {
|
||||
if (unimportant) return;
|
||||
this.controlKnots.push({
|
||||
type: "g4",
|
||||
d1: this.defaultD1,
|
||||
d2: this.defaultD2,
|
||||
...this.gizmo.apply({ x, y })
|
||||
});
|
||||
}
|
||||
arcTo(arc, x, y) {
|
||||
const k0 = this.controlKnots[this.controlKnots.length - 1];
|
||||
if (!k0) throw new Error("Unreachable: lineTo called before moveTo");
|
||||
if (k0.normalAngle == null) {
|
||||
const tfDerive0 = this.gizmo.applyOffset({ x: arc.deriveX0, y: arc.deriveY0 });
|
||||
k0.normalAngle = Math.PI / 2 + Math.atan2(tfDerive0.y, tfDerive0.x);
|
||||
}
|
||||
{
|
||||
const tfDerive1 = this.gizmo.applyOffset({ x: arc.deriveX1, y: arc.deriveY1 });
|
||||
this.controlKnots.push({
|
||||
type: "g4",
|
||||
d1: k0.d1,
|
||||
d2: k0.d2,
|
||||
...this.gizmo.apply({ x, y }),
|
||||
normalAngle: Math.PI / 2 + Math.atan2(tfDerive1.y, tfDerive1.x)
|
||||
});
|
||||
}
|
||||
}
|
||||
setWidth(l, r) {
|
||||
const k0 = this.controlKnots[this.controlKnots.length - 1];
|
||||
if (k0) {
|
||||
(k0.d1 = l), (k0.d2 = r);
|
||||
} else {
|
||||
(this.defaultD1 = l), (this.defaultD2 = r);
|
||||
}
|
||||
}
|
||||
headsTo(direction) {
|
||||
const k0 = this.controlKnots[this.controlKnots.length - 1];
|
||||
if (k0) k0.proposedNormal = direction;
|
||||
}
|
||||
setType(type) {
|
||||
const k0 = this.controlKnots[this.controlKnots.length - 1];
|
||||
if (k0) k0.type = type;
|
||||
}
|
||||
expand(contrast) {
|
||||
if (contrast == null) contrast = 1 / 0.9;
|
||||
const lhs = [],
|
||||
rhs = [];
|
||||
|
||||
// Create important knots
|
||||
for (let j = 0; j < this.controlKnots.length; j++) {
|
||||
const knot = this.controlKnots[j];
|
||||
if (knot.unimportant) continue;
|
||||
|
||||
let dx = 0,
|
||||
dy = 0;
|
||||
if (knot.proposedNormal) {
|
||||
dx = knot.proposedNormal.x - normalX(knot.normalAngle, contrast);
|
||||
dy = knot.proposedNormal.y - normalY(knot.normalAngle, contrast);
|
||||
}
|
||||
lhs[j] = {
|
||||
type: knot.type,
|
||||
x: knot.x + knot.d1 * (dx + normalX(knot.normalAngle, contrast)),
|
||||
y: knot.y + knot.d1 * (dy + normalY(knot.normalAngle, contrast))
|
||||
};
|
||||
rhs[j] = {
|
||||
type: reverseKnotType(knot.type),
|
||||
x: knot.x - knot.d2 * (dx + normalX(knot.normalAngle, contrast)),
|
||||
y: knot.y - knot.d2 * (dy + normalY(knot.normalAngle, contrast))
|
||||
};
|
||||
}
|
||||
this.interpolateUnimportantKnots(lhs, rhs);
|
||||
return { lhs, rhs };
|
||||
}
|
||||
interpolateUnimportantKnots(lhs, rhs) {
|
||||
for (let j = 0; j < this.controlKnots.length; j++) {
|
||||
const knot = this.controlKnots[j];
|
||||
if (!knot.unimportant) continue;
|
||||
let jBefore, jAfter;
|
||||
for (jBefore = j - 1; this.controlKnots[jBefore].unimportant; jBefore--);
|
||||
for (jAfter = j + 1; this.controlKnots[jAfter].unimportant; jAfter++);
|
||||
|
||||
const knotBefore = this.gizmo.unapply(this.controlKnots[jBefore]),
|
||||
knotAfter = this.gizmo.unapply(this.controlKnots[jAfter]),
|
||||
ref = this.gizmo.unapply(knot),
|
||||
lhsBefore = this.gizmo.unapply(lhs[jBefore]),
|
||||
lhsAfter = this.gizmo.unapply(lhs[jAfter]),
|
||||
rhsBefore = this.gizmo.unapply(rhs[jBefore]),
|
||||
rhsAfter = this.gizmo.unapply(rhs[jAfter]);
|
||||
|
||||
lhs[j] = {
|
||||
type: knot.type,
|
||||
...this.gizmo.apply({
|
||||
x: linreg(knotBefore.x, lhsBefore.x, knotAfter.x, lhsAfter.x, ref.x),
|
||||
y: linreg(knotBefore.y, lhsBefore.y, knotAfter.y, lhsAfter.y, ref.y)
|
||||
})
|
||||
};
|
||||
rhs[j] = {
|
||||
type: reverseKnotType(knot.type),
|
||||
...this.gizmo.apply({
|
||||
x: linreg(knotBefore.x, rhsBefore.x, knotAfter.x, rhsAfter.x, ref.x),
|
||||
y: linreg(knotBefore.y, rhsBefore.y, knotAfter.y, rhsAfter.y, ref.y)
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
function zeroes(n) {
|
||||
let a = new Array(n);
|
||||
for (let i = 0; i < n; ++i) a[i] = 0;
|
||||
return a;
|
||||
}
|
||||
function normalX(angle, contrast) {
|
||||
return Math.cos(angle) * contrast;
|
||||
}
|
||||
function normalY(angle) {
|
||||
return Math.sin(angle);
|
||||
}
|
||||
function reverseKnotType(ty) {
|
||||
return ty === "left" ? "right" : ty === "right" ? "left" : ty;
|
||||
}
|
49
font-src/support/transform.js
Normal file
49
font-src/support/transform.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = class Transform {
|
||||
constructor(xx, yx, xy, yy, x, y) {
|
||||
this.xx = xx;
|
||||
this.yx = yx;
|
||||
this.xy = xy;
|
||||
this.yy = yy;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
static Id() {
|
||||
return new Transform(1, 0, 0, 1, 0, 0);
|
||||
}
|
||||
|
||||
apply(pt) {
|
||||
return {
|
||||
x: pt.x * this.xx + pt.y * this.yx + this.x,
|
||||
y: pt.x * this.xy + pt.y * this.yy + this.y
|
||||
};
|
||||
}
|
||||
applyOffset(delta) {
|
||||
return {
|
||||
x: delta.x * this.xx + delta.y * this.yx,
|
||||
y: delta.x * this.xy + delta.y * this.yy
|
||||
};
|
||||
}
|
||||
unapply(pt) {
|
||||
const xx = pt.x - this.x;
|
||||
const yy = pt.y - this.y;
|
||||
const denom = this.xx * this.yy - this.xy * this.yx;
|
||||
return {
|
||||
x: (xx * this.yy - yy * this.yx) / denom,
|
||||
y: (yy * this.xx - xx * this.xy) / denom
|
||||
};
|
||||
}
|
||||
inverse() {
|
||||
const denom = this.xx * this.yy - this.xy * this.yx;
|
||||
return new Transform(
|
||||
this.yy / denom,
|
||||
-this.yx / denom,
|
||||
-this.xy / denom,
|
||||
this.xx / denom,
|
||||
-(this.x * this.yy - this.y * this.yx) / denom,
|
||||
-(-this.x * this.xy + this.y * this.xx) / denom
|
||||
);
|
||||
}
|
||||
};
|
49
font-src/support/utils.js
Normal file
49
font-src/support/utils.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
"use strict";
|
||||
|
||||
function mix(a, b, p) {
|
||||
return a + (b - a) * p;
|
||||
}
|
||||
function ratio(l, r, m) {
|
||||
return l === r ? 0 : (m - l) / (r - l);
|
||||
}
|
||||
function barmixL(l, r, b, p) {
|
||||
return l > r ? barmixL(r, l, b, p) : l + b + p * (r - l - b * 3);
|
||||
}
|
||||
function barmixM(l, r, b, p) {
|
||||
return barmixL(l, r, b, p) + b / 2;
|
||||
}
|
||||
function barMixR(l, r, b, p) {
|
||||
return barMixR(l, r, b, p) + b;
|
||||
}
|
||||
function linreg(x0, y0, x1, y1, x) {
|
||||
return y0 + ((x - x0) * (y1 - y0)) / (x1 - x0);
|
||||
}
|
||||
function clamp(l, h, x) {
|
||||
return x < l ? l : x > h ? h : x;
|
||||
}
|
||||
function fallback(...args) {
|
||||
for (const item of args) if (item !== void 0) return item;
|
||||
return void 0;
|
||||
}
|
||||
function bez2(a, b, c, t) {
|
||||
return (1 - t) * (1 - t) * a + 2 * (1 - t) * t * b + t * t * c;
|
||||
}
|
||||
function bez3(a, b, c, d, t) {
|
||||
return (
|
||||
(1 - t) * (1 - t) * (1 - t) * a +
|
||||
3 * (1 - t) * (1 - t) * t * b +
|
||||
3 * t * t * (1 - t) * c +
|
||||
t * t * t * d
|
||||
);
|
||||
}
|
||||
|
||||
exports.mix = mix;
|
||||
exports.ratio = ratio;
|
||||
exports.barmixL = barmixL;
|
||||
exports.barmixM = barmixM;
|
||||
exports.barmixR = barMixR;
|
||||
exports.linreg = linreg;
|
||||
exports.clamp = clamp;
|
||||
exports.fallback = fallback;
|
||||
exports.bez2 = bez2;
|
||||
exports.bez3 = bez3;
|
71
font-src/support/variant-data.js
Normal file
71
font-src/support/variant-data.js
Normal file
|
@ -0,0 +1,71 @@
|
|||
"use strict";
|
||||
|
||||
const objectAssign = require("object-assign");
|
||||
|
||||
function mergeVSHive(_target, source) {
|
||||
if (!source) return _target;
|
||||
let __cvmap = objectAssign({}, _target.__cvmap, source.__cvmap);
|
||||
let target = objectAssign(_target, source);
|
||||
target.__cvmap = __cvmap;
|
||||
return target;
|
||||
}
|
||||
|
||||
function produceComposite(vs, para, g) {
|
||||
let sel = {};
|
||||
if (g.design)
|
||||
for (let h of g.design) {
|
||||
sel = mergeVSHive(sel, vs[h]);
|
||||
}
|
||||
if (!para.isItalic && g.upright) {
|
||||
for (let h of g.upright) {
|
||||
sel = mergeVSHive(sel, vs[h]);
|
||||
}
|
||||
}
|
||||
if (para.isItalic && g.italic) {
|
||||
for (let h of g.italic) {
|
||||
sel = mergeVSHive(sel, vs[h]);
|
||||
}
|
||||
}
|
||||
sel.__isComposite = true;
|
||||
return sel;
|
||||
}
|
||||
|
||||
module.exports = function formVariantData(data, para) {
|
||||
const vs = {};
|
||||
// simple selector
|
||||
for (let k in data.simple) {
|
||||
const varDef = data.simple[k];
|
||||
if (!varDef.variant) throw new Error("Unreachable! Variant definition is invalid");
|
||||
const hive = { ...varDef.variant };
|
||||
vs[k] = hive;
|
||||
|
||||
const tag = varDef.tag;
|
||||
const tagUpright = varDef.tagUpright;
|
||||
const tagItalic = varDef.tagItalic;
|
||||
if (tag) {
|
||||
let __cvmap = {};
|
||||
for (let k in hive) __cvmap[k] = tag;
|
||||
hive.__cvmap = __cvmap;
|
||||
vs[tag] = hive;
|
||||
} else {
|
||||
if (tagItalic && para.isItalic) {
|
||||
let __cvmap = {};
|
||||
for (let k in hive) __cvmap[k] = tagItalic;
|
||||
hive.__cvmap = __cvmap;
|
||||
vs[tagItalic] = hive;
|
||||
}
|
||||
if (tagUpright && !para.isItalic) {
|
||||
let __cvmap = {};
|
||||
for (let k in hive) __cvmap[k] = tagUpright;
|
||||
hive.__cvmap = __cvmap;
|
||||
vs[tagUpright] = hive;
|
||||
}
|
||||
}
|
||||
}
|
||||
// default selector
|
||||
vs.default = produceComposite(vs, para, data.default);
|
||||
// ss## selector
|
||||
for (let k in data.composite) vs[k] = produceComposite(vs, para, data.composite[k]);
|
||||
|
||||
return vs;
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue