Move font building related files to font-src

This commit is contained in:
be5invis 2020-06-29 23:31:41 -07:00
parent 9a2f862631
commit c48bc20aa2
56 changed files with 18 additions and 18 deletions

View file

@ -0,0 +1,133 @@
"use strict";
const Point = require("../../support/point");
function delta(a, b) {
return Math.round((a - b) * 32);
}
function contourHash(c) {
if (!c || c.length < 2) return ".";
let lx = c[0].x,
ly = c[0].y;
let buf = "";
for (let j = 1; j < c.length; j++) {
const z = c[j];
buf += `${z.on ? "o" : "f"}${z.cubic ? "c" : "q"}${delta(z.x, lx)},${delta(z.y, ly)};`;
(lx = z.x), (ly = z.y);
}
return buf;
}
function match(g1, g2, _n) {
for (let j = 0; j + g1.contours.length <= g2.contours.length; j++) {
let found = true;
for (let k = j; k < g2.contours.length && k - j < g1.contours.length; k++) {
if (
g1.contours[k - j].hash !== g2.contours[k].hash ||
!(
k <= j ||
(delta(g1.contours[k - j][0].x, g1.contours[k - j - 1][0].x) ===
delta(g2.contours[k][0].x, g2.contours[k - 1][0].x) &&
delta(g1.contours[k - j][0].y, g1.contours[k - j - 1][0].y) ===
delta(g2.contours[k][0].y, g2.contours[k - 1][0].y))
)
) {
found = false;
break;
}
}
if (found) {
const refX = g2.contours[j][0].x - g1.contours[0][0].x || 0;
const refY = g2.contours[j][0].y - g1.contours[0][0].y || 0;
if (Math.abs(refY) > 1 && g1.advanceWidth > 1) {
continue;
}
if (!g2.references) g2.references = [];
g2.references.push({
glyph: g1.name,
_n: _n,
x: refX,
y: refY,
roundToGrid: false
});
g2.contours.splice(j, g1.contours.length);
return true;
}
}
return false;
}
function unlinkRef(g, dx, dy, glyf) {
let contours = g.contours.map(c => c.map(z => new Point(z.x + dx, z.y + dy, z.on, z.cubic)));
if (g.references)
for (let r of g.references) {
contours = contours.concat(unlinkRef(glyf[r._n], r.x + dx, r.y + dy, glyf));
}
return contours;
}
function autoref(gs, excludeUnicodeSet) {
suppressNaN(gs);
for (let j = 0; j < gs.length; j++) {
const g = gs[j];
if (g.contours) {
for (let k = 0; k < g.contours.length; k++) {
const contour = g.contours[k];
contour.hash = contourHash(contour);
}
}
}
// Refl-referencify, forward.
for (let j = 0; j < gs.length; j++) {
if (!gs[j].contours.length || (gs[j].references && gs[j].references.length)) continue;
for (let k = j + 1; k < gs.length; k++) {
if (gs[j].contours.length === gs[k].contours.length) {
match(gs[j], gs[k], j);
}
}
}
// referencify, backward
for (let j = 0; j < gs.length; j++) {
if (gs[j].autoRefPriority < 0) continue;
if (!gs[j].contours.length) continue;
if (gs[j].references && gs[j].references.length) continue;
for (let k = gs.length - 1; k >= 0; k--) {
if (gs[j].contours.length > gs[k].contours.length) continue;
if (
gs[j].contours.length === gs[k].contours.length &&
!(gs[k].references && gs[k].references.length)
) {
continue;
}
while (match(gs[j], gs[k], j)) "pass";
}
}
// unlink composite
for (let j = 0; j < gs.length; j++) {
if (!gs[j].references || gs[j].references.length === 0) continue;
if (!gs[j].avoidBeingComposite && gs[j].contours.length === 0) continue;
gs[j].contours = unlinkRef(gs[j], 0, 0, gs);
gs[j].references = [];
}
}
function suppressNaN(glyf) {
for (let j = 0; j < glyf.length; j++) {
let g = glyf[j];
if (!g.contours) continue;
for (let k = 0; k < g.contours.length; k++) {
let contour = g.contours[k];
for (let z of contour) {
if (!isFinite(z.x)) z.x = 0;
if (!isFinite(z.y)) z.y = 0;
}
}
}
}
module.exports = autoref;

288
font-src/gen/finalize/gc.js Normal file
View file

@ -0,0 +1,288 @@
"use strict";
module.exports = function gcFont(gs, excludedChars, restFont, cfg) {
markSweepOtl(restFont.GSUB);
markSweepOtl(restFont.GPOS);
const sink = mark(gs, excludedChars, restFont, cfg);
sweep(gs, restFont, sink);
};
function markSweepOtl(table) {
if (!table || !table.features || !table.lookups) return;
const accessibleLookupsIds = new Set();
markLookups(table, accessibleLookupsIds);
let lookups1 = {};
for (const l in table.lookups) {
if (accessibleLookupsIds.has(l)) lookups1[l] = table.lookups[l];
}
table.lookups = lookups1;
let features1 = {};
for (let f in table.features) {
const feature = table.features[f];
if (!feature) continue;
const featureFiltered = [];
for (const l of feature) if (accessibleLookupsIds.has(l)) featureFiltered.push(l);
if (!featureFiltered.length) continue;
features1[f] = featureFiltered;
}
table.features = features1;
}
function markLookups(table, sink) {
if (!table || !table.features) return;
for (let f in table.features) {
const feature = table.features[f];
if (!feature) continue;
for (const l of feature) sink.add(l);
}
let loop = 0,
lookupSetChanged = false;
do {
lookupSetChanged = false;
let sizeBefore = sink.size;
for (const l of Array.from(sink)) {
const lookup = table.lookups[l];
if (!lookup || !lookup.subtables) continue;
if (lookup.type === "gsub_chaining" || lookup.type === "gpos_chaining") {
for (let st of lookup.subtables) {
if (!st || !st.apply) continue;
for (const app of st.apply) sink.add(app.lookup);
}
}
}
loop++;
lookupSetChanged = sizeBefore !== sink.size;
} while (loop < 0xff && lookupSetChanged);
}
function mark(gs, excludedChars, restFont, cfg) {
const sink = markInitial(gs, excludedChars);
while (markStep(sink, restFont, cfg));
return sink;
}
function markInitial(gs, excludedChars) {
let sink = new Set();
for (const g of gs) {
if (g.glyphRank > 0) sink.add(g.name);
if (!g || !g.unicode) continue;
for (const u of g.unicode) if (!excludedChars.has(u)) sink.add(g.name);
}
return sink;
}
function markStep(sink, restFont, cfg) {
const glyphCount = sink.size;
if (restFont.GSUB) {
for (const l in restFont.GSUB.lookups) {
const lookup = restFont.GSUB.lookups[l];
if (!lookup || !lookup.subtables) continue;
for (let st of lookup.subtables) {
markSubtable(sink, lookup.type, st, cfg);
}
}
}
const glyphCount1 = sink.size;
return glyphCount1 > glyphCount;
}
function markSubtable(sink, type, st, cfg) {
switch (type) {
case "gsub_single":
for (const k in st) if (sink.has(k) && st[k]) sink.add(st[k]);
break;
case "gsub_multiple":
for (const k in st) if (sink.has(k) && st[k]) for (const g of st[k]) sink.add(g);
break;
case "gsub_alternate":
if (!cfg || !cfg.ignoreAltSub) {
for (const k in st) if (sink.has(k) && st[k]) for (const g of st[k]) sink.add(g);
}
break;
case "gsub_ligature":
for (const sub of st.substitutions) {
let check = true;
for (const g of sub.from) if (!sink.has(g)) check = false;
if (check && sub.to) sink.add(sub.to);
}
break;
case "gsub_chaining":
break;
case "gsub_reverse":
if (st.match && st.to) {
const matchCoverage = st.match[st.inputIndex];
for (let j = 0; j < matchCoverage.length; j++) {
if (sink.has(matchCoverage[j]) && st.to[j]) sink.add(st.to[j]);
}
}
break;
}
}
function sweep(gs, restFont, sink) {
filterInPlace(gs, g => sink.has(g.name));
sweepOtl(restFont.GSUB, sink);
sweepOtl(restFont.GPOS, sink);
}
function sweepOtl(table, sink) {
if (!table || !table.lookups) return;
for (const lid in table.lookups) {
const lookup = table.lookups[lid];
if (!lookup.subtables) continue;
const newSubtables = [];
for (const st of lookup.subtables) {
const keep = sweepSubtable(st, lookup.type, sink);
if (keep) newSubtables.push(st);
}
lookup.subtables = newSubtables;
}
}
function sweepSubtable(st, type, gs) {
switch (type) {
case "gsub_single":
return sweep_GsubSingle(st, gs);
case "gsub_multiple":
case "gsub_alternate":
return sweep_GsubMultiple(st, gs);
case "gsub_ligature":
return sweep_GsubLigature(st, gs);
case "gsub_chaining":
return sweep_GsubChaining(st, gs);
case "gsub_reverse":
return sweep_gsubReverse(st, gs);
case "gpos_mark_to_base":
case "gpos_mark_to_mark":
return sweep_gposMark(st, gs);
default:
return true;
}
}
function sweep_GsubSingle(st, gs) {
let nonEmpty = false;
let from = Object.keys(st);
for (const gidFrom of from) {
if (!gs.has(gidFrom) || !gs.has(st[gidFrom])) {
delete st[gidFrom];
} else {
nonEmpty = true;
}
}
return nonEmpty;
}
function sweep_GsubMultiple(st, gs) {
let nonEmpty = false;
let from = Object.keys(st);
for (const gidFrom of from) {
let include = gs.has(gidFrom);
if (st[gidFrom]) {
for (const gidTo of st[gidFrom]) {
include = include && gs.has(gidTo);
}
} else {
include = false;
}
if (!include) {
delete st[gidFrom];
} else {
nonEmpty = true;
}
}
return nonEmpty;
}
function sweep_GsubLigature(st, gs) {
if (!st.substitutions) return false;
let newSubst = [];
for (const rule of st.substitutions) {
let include = true;
if (!gs.has(rule.to)) include = false;
for (const from of rule.from) if (!gs.has(from)) include = false;
if (include) newSubst.push(rule);
}
st.substitutions = newSubst;
return true;
}
function sweep_GsubChaining(st, gs) {
const newMatch = [];
for (let j = 0; j < st.match.length; j++) {
newMatch[j] = [];
for (let k = 0; k < st.match[j].length; k++) {
const gidFrom = st.match[j][k];
if (gs.has(gidFrom)) {
newMatch[j].push(gidFrom);
}
}
if (!newMatch[j].length) return false;
}
st.match = newMatch;
return true;
}
function sweep_gsubReverse(st, gs) {
const newMatch = [],
newTo = [];
for (let j = 0; j < st.match.length; j++) {
newMatch[j] = [];
for (let k = 0; k < st.match[j].length; k++) {
const gidFrom = st.match[j][k];
let include = gs.has(gidFrom);
if (j === st.inputIndex) {
include = include && gs.has(st.to[k]);
if (include) {
newMatch[j].push(gidFrom);
newTo.push(st.to[k]);
}
} else {
if (include) newMatch[j].push(gidFrom);
}
}
if (!newMatch[j].length) return false;
}
st.match = newMatch;
st.to = newTo;
return true;
}
function sweep_gposMark(st, gs) {
let marks = st.marks || {},
newMarks = {},
hasMarks = false;
let bases = st.bases || {},
newBases = {},
hasBases = true;
for (const gid in marks) {
if (gs.has(gid) && marks[gid]) {
newMarks[gid] = marks[gid];
hasMarks = true;
}
}
for (const gid in bases) {
if (gs.has(gid) && bases[gid]) {
newBases[gid] = bases[gid];
hasBases = true;
}
}
st.marks = newMarks;
st.bases = newBases;
return hasMarks && hasBases;
}
function filterInPlace(a, condition) {
let i = 0,
j = 0;
while (i < a.length) {
const val = a[i];
if (condition(val, i, a)) a[j++] = val;
i++;
}
a.length = j;
return a;
}

View file

@ -0,0 +1,171 @@
"use strict";
const autoRef = require("./autoref");
const TypoGeom = require("typo-geom");
const Point = require("../../support/point");
const CurveUtil = require("../../support/curve-util");
const { AnyCv } = require("../../support/gr");
const gcFont = require("./gc");
module.exports = function finalizeFont(para, glyphList, excludedCodePoints, font) {
forceMonospaceIfNeeded(para, glyphList);
gcFont(glyphList, excludedCodePoints, font, {});
extractGlyfCmap(regulateGlyphList(para, glyphList), font);
};
function forceMonospaceIfNeeded(para, glyphList) {
if (!para.forceMonospace || para.spacing > 0) return;
const unitWidth = Math.round(para.width);
let i = 0,
j = 0;
for (; i < glyphList.length; i++) {
const g = glyphList[i];
g.advanceWidth = Math.round(g.advanceWidth || 0);
if (g.advanceWidth === 0 || g.advanceWidth === unitWidth) glyphList[j++] = g;
}
glyphList.length = j;
}
function extractGlyfCmap(glyphList, font) {
const glyf = {};
const cmap = {};
for (let g of glyphList) {
glyf[g.name] = g;
if (!g.unicode) continue;
for (let u of g.unicode) {
if (isFinite(u - 0)) cmap[u] = g.name;
}
}
font.glyf = glyf;
font.cmap = cmap;
}
function regulateGlyphList(para, gs) {
const skew = Math.tan(((para.slantAngle || 0) / 180) * Math.PI);
const excludeUnicode = new Set();
excludeUnicode.add(0x80);
for (let c = 0x2500; c <= 0x259f; c++) excludeUnicode.add(c);
// autoref
for (let j = 0; j < gs.length; j++) {
gs[j].glyphOrder = j;
if (AnyCv.query(gs[j]).length) gs[j].autoRefPriority = -1;
if (gs[j].unicode) {
for (const u of gs[j].unicode) {
if (excludeUnicode.has(u)) gs[j].avoidBeingComposite = true;
}
}
}
gs.sort(byGlyphPriority);
autoRef(gs, excludeUnicode);
// regulate
for (let g of gs) regulateGlyph(g, skew);
// reorder
return gs.sort(byRank);
}
function byGlyphPriority(a, b) {
const pri1 = a.autoRefPriority || 0;
const pri2 = b.autoRefPriority || 0;
if (pri1 > pri2) return -1;
if (pri1 < pri2) return 1;
if (a.contours && b.contours && a.contours.length < b.contours.length) return 1;
if (a.contours && b.contours && a.contours.length > b.contours.length) return -1;
return 0;
}
function byRank(a, b) {
return (b.glyphRank || 0) - (a.glyphRank || 0) || (a.glyphOrder || 0) - (b.glyphOrder || 0);
}
function regulateGlyph(g, skew) {
if (!g.contours || !g.contours.length) return;
for (const contour of g.contours) for (const z of contour) z.x -= z.y * skew;
g.contours = simplifyContours(g.contours);
for (const contour of g.contours) for (const z of contour) z.x += z.y * skew;
}
function simplifyContours(source) {
const sink = new FairizedShapeSink();
TypoGeom.transferGenericShape(
TypoGeom.Fairize.fairizeBezierShape(
TypoGeom.Boolean.removeOverlap(
CurveUtil.convertShapeToArcs(source),
TypoGeom.Boolean.PolyFillType.pftNonZero,
1 << 17
)
),
sink,
FINAL_SIMPLIFY_TOLERANCE
);
return sink.contours;
}
const FINAL_SIMPLIFY_RESOLUTION = 16;
const FINAL_SIMPLIFY_TOLERANCE = 2 / FINAL_SIMPLIFY_RESOLUTION;
class FairizedShapeSink {
constructor() {
this.contours = [];
this.lastContour = [];
}
beginShape() {}
endShape() {
if (this.lastContour.length > 2) {
const zFirst = this.lastContour[0],
zLast = this.lastContour[this.lastContour.length - 1];
if (zFirst.on && zLast.on && zFirst.x === zLast.x && zFirst.y === zLast.y) {
this.lastContour.pop();
}
this.contours.push(this.lastContour);
}
this.lastContour = [];
}
moveTo(x, y) {
this.endShape();
this.lineTo(x, y);
}
lineTo(x, y) {
const z = Point.cornerFromXY(x, y).round(FINAL_SIMPLIFY_RESOLUTION);
if (this.lastContour.length >= 2) {
const a = this.lastContour[this.lastContour.length - 2],
b = this.lastContour[this.lastContour.length - 1];
if (isLineExtend(a, b, z)) {
this.lastContour.pop();
this.lastContour.push(z);
return;
}
}
this.lastContour.push(z);
}
arcTo(arc, x, y) {
const offPoints = TypoGeom.Quadify.auto(arc, FINAL_SIMPLIFY_TOLERANCE);
if (offPoints) {
for (const z of offPoints)
this.lastContour.push(Point.offFrom(z).round(FINAL_SIMPLIFY_RESOLUTION));
}
this.lineTo(x, y);
}
}
function isLineExtend(a, b, c) {
return (
a.on &&
b.on &&
c.on &&
((aligned(a.x, b.x, c.x) && between(a.y, b.y, c.y)) ||
(aligned(a.y, b.y, c.y) && between(a.x, b.x, c.x)))
);
}
function aligned(a, b, c) {
return a === b && b === c;
}
function between(a, b, c) {
return (a <= b && b <= c) || (a >= b && b >= c);
}