* Replace autoRef with semantic inclusion for more stable results.

* Move files around to make repository organized better.
This commit is contained in:
be5invis 2020-10-17 15:45:00 -07:00
parent 400d8f3f38
commit 7c78329244
37 changed files with 1098 additions and 761 deletions

3
.gitignore vendored
View file

@ -43,8 +43,7 @@ release-archives/
testdrive/iosevka*
# Generated scripts
font-src/gen/build-glyphs.js
font-src/gen/kits/**/*.js
font-src/kits/**/*.js
font-src/meta/**/*.js
font-src/otl/**/*.js
font-src/glyphs/**/*.js

View file

@ -57,7 +57,7 @@ Iosevka supports Language-Specific Ligations, which is the ligation set enabled
To build Iosevka you should:
1. Ensure that [`nodejs`](http://nodejs.org) (≥ 12.16.0), [`ttfautohint`](http://www.freetype.org/ttfautohint/), [`otfcc`](https://github.com/caryll/otfcc) (≥ 0.10.3-alpha) and [`otf2otc`](https://github.com/adobe-type-tools/afdko) are present.
1. Ensure that [`nodejs`](http://nodejs.org) (≥ 12.16.0) and [`ttfautohint`](http://www.freetype.org/ttfautohint/) are present.
2. Install necessary libs by `npm install`. If youve installed them, upgrade to the latest.
3. `npm run build -- contents::iosevka`.

View file

@ -1,24 +1,25 @@
"use strict";
const EmptyFont = require("./empty-font.js");
const buildGlyphs = require("./build-glyphs.js");
const EmptyFont = require("./empty-font");
const buildGlyphs = require("../glyphs/index");
const finalizeFont = require("./finalize/index");
const convertOtd = require("./otd-conv/index");
const { buildOtl } = require("../otl/index");
const { assignFontNames } = require("../meta/naming");
const { setFontMetrics } = require("../meta/aesthetics");
module.exports = function (para) {
const font = EmptyFont();
const otd = EmptyFont();
const gs = buildGlyphs(para);
assignFontNames(para, gs.metrics, font);
setFontMetrics(para, gs.metrics, font);
assignFontNames(para, gs.metrics, otd);
setFontMetrics(para, gs.metrics, otd);
const otl = buildOtl(para, gs.glyphStore);
font.GSUB = otl.GSUB;
font.GPOS = otl.GPOS;
font.GDEF = otl.GDEF;
otd.GSUB = otl.GSUB;
otd.GPOS = otl.GPOS;
otd.GDEF = otl.GDEF;
// Regulate
const excludeChars = new Set();
@ -28,6 +29,7 @@ module.exports = function (para) {
}
}
const finalGs = finalizeFont(para, gs.glyphStore, excludeChars, font);
const finalGs = finalizeFont(para, gs.glyphStore, excludeChars, otd);
const font = convertOtd(otd, finalGs);
return { font, glyphStore: finalGs };
};

View file

@ -76,20 +76,8 @@ module.exports = function () {
ySuperscriptXSize: 665,
ySuperscriptYOffset: 491,
ySuperscriptYSize: 716,
ulCodePageRange1: {
latin1: true,
latin2: true,
greek: true,
cyrillic: true,
turkish: true,
vietnamese: true,
macRoman: true
},
ulCodePageRange2: {
cp852: true,
cp850: true,
ascii: true
}
ulCodePageRange1: 0x2000011f,
ulCodePageRange2: 0xc4000000
},
post: {
version: 2,

View file

@ -1,165 +0,0 @@
"use strict";
const Point = require("../../support/point");
const { AnyCv } = require("../../support/gr");
function autoref(glyphStore) {
suppressNaN(glyphStore);
hashContours(glyphStore);
const glyphEntryList = getGlyphEntryList(glyphStore);
linkRefl(glyphEntryList);
linkComponent(glyphEntryList);
unlinkHybrid(glyphStore);
}
function suppressNaN(glyphStore) {
for (const g of glyphStore.glyphs()) {
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;
}
}
}
}
function hashContours(glyphStore) {
for (const g of glyphStore.glyphs()) {
if (!g.contours) continue;
for (let k = 0; k < g.contours.length; k++) {
const contour = g.contours[k];
contour.hash = contourHash(contour);
}
}
}
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 delta(a, b) {
return Math.round((a - b) * 32);
}
function getGlyphEntryList(glyphStore) {
const excludeUnicode = new Set();
excludeUnicode.add(0x80);
for (let c = 0x2500; c <= 0x259f; c++) excludeUnicode.add(c);
for (const [j, gn, g] of glyphStore.indexedNamedEntries()) {
if (AnyCv.query(g).length) g.autoRefPriority = -1;
const us = glyphStore.queryUnicodeOf(g);
if (us) {
for (const u of us) if (excludeUnicode.has(u)) g.avoidBeingComposite = true;
}
}
return Array.from(glyphStore.indexedNamedEntries()).sort(byGlyphPriority);
}
function byGlyphPriority([ja, gna, a], [jb, gnb, 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 linkRefl(glyphEntryList) {
for (let j = 0; j < glyphEntryList.length; j++) {
const [, gnj, gj] = glyphEntryList[j];
if (!gj.contours.length || (gj.references && gj.references.length)) continue;
for (let k = j + 1; k < glyphEntryList.length; k++) {
const [, gnk, gk] = glyphEntryList[k];
if (gj.contours.length === gk.contours.length) {
match(gnj, gj, gnk, gk);
}
}
}
}
function linkComponent(glyphEntryList) {
for (let j = 0; j < glyphEntryList.length; j++) {
const [, gnj, gj] = glyphEntryList[j];
if (gj.autoRefPriority < 0) continue;
if (!gj.contours.length) continue;
if (gj.references && gj.references.length) continue;
for (let k = glyphEntryList.length - 1; k >= 0; k--) {
const [, gnk, gk] = glyphEntryList[k];
if (gj.contours.length > gk.contours.length) continue;
if (
gj.contours.length === gk.contours.length &&
!(gk.references && gk.references.length)
) {
continue;
}
while (match(gnj, gj, gnk, gk)) "pass";
}
}
}
function match(gn1, g1, gn2, g2) {
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: gn1, x: refX, y: refY, roundToGrid: false });
g2.contours.splice(j, g1.contours.length);
return true;
}
}
return false;
}
function unlinkHybrid(glyphStore) {
for (const g of glyphStore.glyphs()) {
if (!g.references || g.references.length === 0) continue;
if (!g.avoidBeingComposite && g.contours.length === 0) continue;
g.contours = unlinkRef(g, 0, 0, glyphStore);
g.references = [];
}
}
function unlinkRef(g, dx, dy, glyphStore) {
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(glyphStore.queryByName(r.glyph), r.x + dx, r.y + dy, glyphStore)
);
}
}
return contours;
}
module.exports = autoref;

View file

@ -6,7 +6,7 @@ module.exports = function gcFont(glyphStore, excludedChars, restFont, cfg) {
markSweepOtl(restFont.GSUB);
markSweepOtl(restFont.GPOS);
const sink = mark(glyphStore, excludedChars, restFont, cfg);
return sweep(glyphStore, restFont, sink);
return sweep(glyphStore, sink);
};
function markSweepOtl(table) {
@ -30,6 +30,44 @@ function markSweepOtl(table) {
}
table.features = features1;
}
function mark(glyphStore, excludedChars, restFont, cfg) {
const sink = markInitial(glyphStore, excludedChars);
while (markStep(glyphStore, sink, restFont, cfg));
return sink;
}
function markInitial(glyphStore, excludedChars) {
let sink = new Set();
for (const [gName, g] of glyphStore.namedEntries()) {
if (!g) continue;
if (g.glyphRank > 0) sink.add(gName);
if (Radical.get(g)) sink.add(gName);
const unicodeSet = glyphStore.queryUnicodeOf(g);
if (unicodeSet) {
for (const u of unicodeSet) {
if (!excludedChars.has(u)) sink.add(gName);
}
}
}
return sink;
}
function markStep(glyphStore, 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 markLookups(table, sink) {
if (!table || !table.features) return;
for (let f in table.features) {
@ -57,43 +95,6 @@ function markLookups(table, sink) {
} while (loop < 0xff && lookupSetChanged);
}
function mark(glyphStore, excludedChars, restFont, cfg) {
const sink = markInitial(glyphStore, excludedChars);
while (markStep(sink, restFont, cfg));
return sink;
}
function markInitial(glyphStore, excludedChars) {
let sink = new Set();
for (const [gName, g] of glyphStore.namedEntries()) {
if (!g) continue;
if (g.glyphRank > 0) sink.add(gName);
if (Radical.get(g)) sink.add(gName);
const unicodeSet = glyphStore.queryUnicodeOf(g);
if (unicodeSet) {
for (const u of unicodeSet) {
if (!excludedChars.has(u)) sink.add(gName);
}
}
}
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":
@ -127,156 +128,6 @@ function markSubtable(sink, type, st, cfg) {
}
}
function sweep(glyphStore, restFont, gnSet) {
sweepOtl(restFont.GSUB, gnSet);
sweepOtl(restFont.GPOS, gnSet);
function sweep(glyphStore, gnSet) {
return glyphStore.filterByName(gnSet);
}
function sweepOtl(table, gnSet) {
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, gnSet);
if (keep) newSubtables.push(st);
}
lookup.subtables = newSubtables;
}
}
function sweepSubtable(st, type, gnSet) {
switch (type) {
case "gsub_single":
return sweep_GsubSingle(st, gnSet);
case "gsub_multiple":
case "gsub_alternate":
return sweep_GsubMultiple(st, gnSet);
case "gsub_ligature":
return sweep_GsubLigature(st, gnSet);
case "gsub_chaining":
return sweep_GsubChaining(st, gnSet);
case "gsub_reverse":
return sweep_gsubReverse(st, gnSet);
case "gpos_mark_to_base":
case "gpos_mark_to_mark":
return sweep_gposMark(st, gnSet);
default:
return true;
}
}
function sweep_GsubSingle(st, gnSet) {
let nonEmpty = false;
let from = Object.keys(st);
for (const gidFrom of from) {
if (!gnSet.has(gidFrom) || !gnSet.has(st[gidFrom])) {
delete st[gidFrom];
} else {
nonEmpty = true;
}
}
return nonEmpty;
}
function sweep_GsubMultiple(st, gnSet) {
let nonEmpty = false;
let from = Object.keys(st);
for (const gidFrom of from) {
let include = gnSet.has(gidFrom);
if (st[gidFrom]) {
for (const gidTo of st[gidFrom]) {
include = include && gnSet.has(gidTo);
}
} else {
include = false;
}
if (!include) {
delete st[gidFrom];
} else {
nonEmpty = true;
}
}
return nonEmpty;
}
function sweep_GsubLigature(st, gnSet) {
if (!st.substitutions) return false;
let newSubst = [];
for (const rule of st.substitutions) {
let include = true;
if (!gnSet.has(rule.to)) include = false;
for (const from of rule.from) if (!gnSet.has(from)) include = false;
if (include) newSubst.push(rule);
}
st.substitutions = newSubst;
return true;
}
function sweep_GsubChaining(st, gnSet) {
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 (gnSet.has(gidFrom)) {
newMatch[j].push(gidFrom);
}
}
if (!newMatch[j].length) return false;
}
st.match = newMatch;
return true;
}
function sweep_gsubReverse(st, gnSet) {
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 = gnSet.has(gidFrom);
if (j === st.inputIndex) {
include = include && gnSet.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, gnSet) {
let marks = st.marks || {},
newMarks = {},
hasMarks = false;
let bases = st.bases || {},
newBases = {},
hasBases = true;
for (const gid in marks) {
if (gnSet.has(gid) && marks[gid]) {
newMarks[gid] = marks[gid];
hasMarks = true;
}
}
for (const gid in bases) {
if (gnSet.has(gid) && bases[gid]) {
newBases[gid] = bases[gid];
hasBases = true;
}
}
st.marks = newMarks;
st.bases = newBases;
return hasMarks && hasBases;
}

View file

@ -0,0 +1,144 @@
"use strict";
const TypoGeom = require("typo-geom");
const Point = require("../../support/point");
const CurveUtil = require("../../support/curve-util");
module.exports = finalizeGlyphs;
function finalizeGlyphs(para, glyphStore) {
suppressNaN(glyphStore);
const skew = Math.tan(((para.slopeAngle || 0) / 180) * Math.PI);
regulateGlyphStore(skew, glyphStore);
return glyphStore;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
function suppressNaN(glyphStore) {
for (const g of glyphStore.glyphs()) {
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;
}
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
function regulateGlyphStore(skew, glyphStore) {
for (const g of glyphStore.glyphs()) {
if (!g.semanticInclusions || !g.contours) continue;
if (g.isPureComposite()) regulateCompositeGlyph(glyphStore, g);
}
for (const g of glyphStore.glyphs()) regulateSimpleGlyph(g, skew);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
function regulateCompositeGlyph(glyphStore, g) {
const references = [];
for (const sr of g.semanticInclusions) {
const gn = glyphStore.queryNameOf(sr.glyph);
if (!gn || sr.glyph.autoRefPriority < 0) return;
references.push({ glyph: gn, x: sr.x, y: sr.y });
}
g.semanticInclusions = [];
g.contours = [];
g.references = references;
}
function regulateSimpleGlyph(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.ShapeConv.transferGenericShape(
TypoGeom.Fairize.fairizeBezierShape(
TypoGeom.Boolean.removeOverlap(
CurveUtil.convertShapeToArcs(source),
TypoGeom.Boolean.PolyFillType.pftNonZero,
CurveUtil.BOOLE_RESOLUTION
)
),
sink,
CurveUtil.GEOMETRY_PRECISION
);
return sink.contours;
}
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(CurveUtil.RECIP_GEOMETRY_PRECISION);
while (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();
} else {
break;
}
}
this.lastContour.push(z);
}
arcTo(arc, x, y) {
const offPoints = TypoGeom.Quadify.auto(arc, 1, 16);
if (offPoints) {
for (const z of offPoints)
this.lastContour.push(Point.offFrom(z).round(CurveUtil.RECIP_GEOMETRY_PRECISION));
}
this.lineTo(x, y);
}
}
function isLineExtend(a, b, c) {
return (
a.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 geometryPrecisionEqual(a, b) {
return (
Math.round(a * CurveUtil.RECIP_GEOMETRY_PRECISION) ===
Math.round(b * CurveUtil.RECIP_GEOMETRY_PRECISION)
);
}
function aligned(a, b, c) {
return geometryPrecisionEqual(a, b) && geometryPrecisionEqual(b, c);
}
function between(a, b, c) {
return (a <= b && b <= c) || (a >= b && b >= c);
}

View file

@ -1,17 +1,12 @@
"use strict";
const autoRef = require("./autoref");
const TypoGeom = require("typo-geom");
const Point = require("../../support/point");
const CurveUtil = require("../../support/curve-util");
const finalizeGlyphs = require("./glyphs");
const gcFont = require("./gc");
module.exports = function finalizeFont(para, glyphStore, excludedCodePoints, font) {
module.exports = function finalizeFont(para, glyphStore, excludedCodePoints, restFont) {
glyphStore = forceMonospaceIfNeeded(para, glyphStore);
glyphStore = gcFont(glyphStore, excludedCodePoints, font, {});
glyphStore = regulateGlyphStore(para, glyphStore);
extractGlyfCmap(glyphStore, font);
glyphStore = gcFont(glyphStore, excludedCodePoints, restFont, {});
glyphStore = finalizeGlyphs(para, glyphStore);
return glyphStore;
};
@ -25,121 +20,3 @@ function forceMonospaceIfNeeded(para, glyphStore) {
}
});
}
function extractGlyfCmap(glyphStore, font) {
const glyf = {};
const cmap = {};
const sortedEntries = Array.from(glyphStore.indexedNamedEntries()).sort(byRank);
for (const [origIndex, name, g] of sortedEntries) {
glyf[name] = g;
const us = glyphStore.queryUnicodeOf(g);
if (us) {
for (const u of us) if (isFinite(u - 0) && u) cmap[u] = name;
}
}
font.glyf = glyf;
font.cmap = cmap;
}
function regulateGlyphStore(para, glyphStore) {
autoRef(glyphStore);
// regulate
const skew = Math.tan(((para.slopeAngle || 0) / 180) * Math.PI);
for (let g of glyphStore.glyphs()) regulateGlyph(g, skew);
return glyphStore;
}
function byRank([ja, gna, a], [jb, gnb, b]) {
return (b.glyphRank || 0) - (a.glyphRank || 0) || (ja || 0) - (jb || 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.ShapeConv.transferGenericShape(
TypoGeom.Fairize.fairizeBezierShape(
TypoGeom.Boolean.removeOverlap(
CurveUtil.convertShapeToArcs(source),
TypoGeom.Boolean.PolyFillType.pftNonZero,
CurveUtil.BOOLE_RESOLUTION
)
),
sink,
CurveUtil.GEOMETRY_PRECISION
);
return sink.contours;
}
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(CurveUtil.RECIP_GEOMETRY_PRECISION);
while (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();
} else {
break;
}
}
this.lastContour.push(z);
}
arcTo(arc, x, y) {
const offPoints = TypoGeom.Quadify.auto(arc, 1, 16);
if (offPoints) {
for (const z of offPoints)
this.lastContour.push(Point.offFrom(z).round(CurveUtil.RECIP_GEOMETRY_PRECISION));
}
this.lineTo(x, y);
}
}
function isLineExtend(a, b, c) {
return (
a.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 geometryPrecisionEqual(a, b) {
return (
Math.round(a * CurveUtil.RECIP_GEOMETRY_PRECISION) ===
Math.round(b * CurveUtil.RECIP_GEOMETRY_PRECISION)
);
}
function aligned(a, b, c) {
return geometryPrecisionEqual(a, b) && geometryPrecisionEqual(b, c);
}
function between(a, b, c) {
return (a <= b && b <= c) || (a >= b && b >= c);
}

View file

@ -0,0 +1,94 @@
const { Ot } = require("ot-builder");
class NamedGlyphStore {
constructor() {
this.m_mapping = new Map();
}
declare(name) {
const g = new Ot.Glyph();
g.name = name;
this.m_mapping.set(name, g);
}
query(name) {
return this.m_mapping.get(name);
}
decideOrder() {
const gs = Ot.ListGlyphStoreFactory.createStoreFromList([...this.m_mapping.values()]);
return gs.decideOrder();
}
fill(name, data) {
const g = this.query(name);
if (!g) return;
g.horizontal = { start: 0, end: data.advanceWidth };
if (data.references && data.references.length) {
this.fillReferences(g, data);
} else if (data.contours && data.contours.length) {
this.fillContours(g, data);
}
}
fillReferences(g, data) {
const gl = new Ot.Glyph.GeometryList();
for (const ref of data.references) {
const target = this.query(ref.glyph);
if (!target) continue;
const tfm = Ot.Glyph.Transform2X3.Translate(ref.x, ref.y);
gl.items.push(new Ot.Glyph.TtReference(target, tfm));
}
g.geometry = gl;
}
fillContours(g, data) {
const cs = new Ot.Glyph.ContourSet();
for (const c of data.contours) {
const c1 = [];
for (const z of c) {
c1.push(
Ot.Glyph.Point.create(
z.x,
z.y,
z.on ? Ot.Glyph.PointType.Corner : Ot.Glyph.PointType.Quad
)
);
}
cs.contours.push(c1);
}
g.geometry = cs;
}
}
module.exports = convertGlyphs;
function convertGlyphs(gsOrig) {
const sortedEntries = Array.from(gsOrig.indexedNamedEntries())
.map(([j, gn, g]) => [j, gn, queryOrderingUnicode(gsOrig, g), g])
.sort(byRank);
const gs = new NamedGlyphStore();
const cmap = new Ot.Cmap.Table();
for (const [origIndex, name, uOrd, g] of sortedEntries) {
gs.declare(name);
const us = gsOrig.queryUnicodeOf(g);
if (us) {
for (const u of us) {
if (isFinite(u - 0) && u) {
cmap.unicode.set(u, gs.query(name));
}
}
}
}
for (const [origIndex, name, uOrd, g] of sortedEntries) gs.fill(name, g);
return { glyphs: gs, cmap };
}
function queryOrderingUnicode(gs, g) {
const us = gs.queryUnicodeOf(g);
if (us && us.size) return Array.from(us).sort((a, b) => a - b)[0];
else return 0xffffff;
}
function byRank([ja, gna, ua, a], [jb, gnb, ub, b]) {
return (
(b.glyphRank || 0) - (a.glyphRank || 0) || (ua || 0) - (ub || 0) || (ja || 0) - (jb || 0)
);
}

View file

@ -0,0 +1,22 @@
const Metadata = require("./metadata");
const convertGlyphs = require("./glyphs");
const convertName = require("./name");
const { convertGsub, convertGpos, convertGdef } = require("./layout");
module.exports = function (otdRestFont, gs) {
const head = Metadata.convertHead(otdRestFont.head);
const hhea = Metadata.convertHhea(otdRestFont.hhea);
const post = Metadata.convertPost(otdRestFont.post);
const maxp = Metadata.convertMaxp(otdRestFont.maxp);
const os2 = Metadata.convertOs2(otdRestFont.OS_2);
const name = convertName(otdRestFont.name);
const { glyphs, cmap } = convertGlyphs(gs);
const gsub = convertGsub(otdRestFont.GSUB, glyphs);
const gpos = convertGpos(otdRestFont.GPOS, glyphs);
const gdef = convertGdef(otdRestFont.GDEF, glyphs);
return { glyphs, head, hhea, post, maxp, os2, name, cmap, gsub, gpos, gdef };
};

View file

@ -0,0 +1,331 @@
const { Ot } = require("ot-builder");
class LookupStore {
constructor(handlers, glyphs) {
this.glyphs = glyphs;
this.m_handlers = handlers;
this.m_mapping = new Map();
}
extract() {
return Array.from(this.m_mapping.values());
}
query(id) {
return this.m_mapping.get(id);
}
declare(id, otdLookup) {
if (this.m_mapping.has(id)) return;
const handler = this.m_handlers[otdLookup.type];
if (!handler) return;
this.m_mapping.set(id, handler.init());
}
fill(id, otdLookup) {
const dst = this.query(id);
const handler = this.m_handlers[otdLookup.type];
if (!dst || !handler) return;
handler.fill(dst, otdLookup, this);
}
}
const GsubSingleHandler = {
init() {
return new Ot.Gsub.Single();
},
fill(dst, src, store) {
for (const st of src.subtables) {
for (const k in st) {
const from = store.glyphs.query(k);
const to = store.glyphs.query(st[k]);
if (from && to) dst.mapping.set(from, to);
}
}
}
};
const GsubMultipleHandler = {
init() {
return new Ot.Gsub.Multiple();
},
fill(dst, src, store) {
for (const st of src.subtables) {
out: for (const k in st) {
const from = store.glyphs.query(k);
const to = mapGlyphListAll(st[k], store);
if (!from || !to) continue out;
dst.mapping.set(from, to);
}
}
}
};
const GsubAlternateHandler = {
init() {
return new Ot.Gsub.Alternate();
},
fill: GsubMultipleHandler.fill
};
const GsubLigatureHandler = {
init() {
return new Ot.Gsub.Ligature();
},
fill(dst, src, store) {
for (const st of src.subtables) {
for (const { from: _from, to: _to } of st.substitutions) {
const to = store.glyphs.query(_to);
const from = mapGlyphListAll(_from, store);
if (!from || !to) continue;
dst.mapping.push({ from, to });
}
}
}
};
const GsubChainingHandler = {
init() {
return new Ot.Gsub.Chaining();
},
fill(dst, src, store) {
out: for (const st of src.subtables) {
const match = [];
for (const m of st.match) {
const m1 = mapGlyphListSome(m, store);
if (!m1) continue out;
match.push(new Set(m1));
}
const inputBegins = st.inputBegins;
const inputEnds = st.inputEnds;
const applications = [];
for (const ap of st.apply) {
const lookup = store.query(ap.lookup);
if (!lookup) continue out;
applications.push({ at: ap.at - inputBegins, apply: lookup });
}
dst.rules.push({ match, inputBegins, inputEnds, applications });
}
}
};
const GsubReverseHandler = {
init() {
return new Ot.Gsub.ReverseSub();
},
fill(dst, src, store) {
out: for (const st of src.subtables) {
const match = [];
const doSubAt = st.inputIndex;
const replacement = new Map();
for (let j = 0; j < st.match.length; j++) {
{
const m1 = new Set();
for (let k = 0; k < st.match[j].length; k++) {
const gFrom = store.glyphs.query(st.match[j][k]);
if (gFrom) m1.add(gFrom);
}
if (!m1.size) continue out;
match.push(m1);
}
if (j === doSubAt) {
for (let k = 0; k < st.match[j].length; k++) {
const gFrom = store.glyphs.query(st.match[j][k]);
const gTo = store.glyphs.query(st.to[k]);
if (!gFrom) continue;
if (gTo) {
replacement.set(gFrom, gTo);
} else {
replacement.set(gFrom, gFrom);
}
}
}
}
dst.rules.push({ match, doSubAt, replacement });
}
}
};
function mapGlyphListAll(gl, store) {
const out = [];
for (const item of gl) {
const fg = store.glyphs.query(item);
if (!fg) return null;
out.push(fg);
}
return out;
}
function mapGlyphListSome(gl, store) {
const out = [];
for (const item of gl) {
const fg = store.glyphs.query(item);
if (!fg) continue;
out.push(fg);
}
if (!out.length) return null;
return out;
}
const GsubHandlers = {
gsub_single: GsubSingleHandler,
gsub_multiple: GsubMultipleHandler,
gsub_alternate: GsubAlternateHandler,
gsub_ligature: GsubLigatureHandler,
gsub_chaining: GsubChainingHandler,
gsub_reverse: GsubReverseHandler
};
const GposMarkToBaseHandler = {
init() {
return new Ot.Gpos.MarkToBase();
},
fill(dst, src, store) {
const st = src.subtables[0];
const mm = collectClassMap(st.marks);
dst.marks = convertMarkRecords(st.marks, mm, store);
dst.bases = convertBaseRecords(st.bases, mm, store);
}
};
const GposMarkToMarkHandler = {
init() {
return new Ot.Gpos.MarkToMark();
},
fill(dst, src, store) {
const st = src.subtables[0];
const mm = collectClassMap(st.marks);
dst.marks = convertMarkRecords(st.marks, mm, store);
dst.baseMarks = convertBaseRecords(st.bases, mm, store);
}
};
function collectClassMap(marks) {
let n = 0;
const m = new Map();
for (const gn in marks) {
const mark = marks[gn];
if (!m.has(mark.class)) {
m.set(mark.class, n);
n++;
}
}
return m;
}
function convertMarkRecords(marks, mm, store) {
const out = new Map();
for (const gn in marks) {
const mark = marks[gn];
const g = store.glyphs.query(gn);
if (!g) continue;
let markAnchors = [];
markAnchors[mm.get(mark.class)] = { x: mark.x, y: mark.y };
out.set(g, { markAnchors: markAnchors });
}
return out;
}
function convertBaseRecords(bases, mm, store) {
const out = new Map();
for (const gn in bases) {
const baseObj = bases[gn];
const g = store.glyphs.query(gn);
if (!g) continue;
const baseArray = [];
for (const bkStr in baseObj) {
baseArray[mm.get(bkStr)] = baseObj[bkStr];
}
out.set(g, { baseAnchors: baseArray });
}
return out;
}
const GposHandlers = {
gpos_mark_to_base: GposMarkToBaseHandler,
gpos_mark_to_mark: GposMarkToMarkHandler
};
class FeatureStore {
constructor(lookups) {
this.lookupStore = lookups;
this.m_mapping = new Map();
}
extract() {
return Array.from(this.m_mapping.values());
}
query(id) {
return this.m_mapping.get(id);
}
fill(id, data) {
const tag = id.slice(0, 4);
const lookups = [];
for (const lid of data) {
const lookup = this.lookupStore.query(lid);
if (lookup) lookups.push(lookup);
}
this.m_mapping.set(id, { tag, lookups });
}
}
class ScriptLanguageStore {
constructor(features) {
this.featureStore = features;
this.m_scriptMapping = new Map();
}
extract() {
return this.m_scriptMapping;
}
fill(id, data) {
const scriptTag = id.slice(0, 4);
const languageTag = id.slice(5, 9).padEnd(4);
let sr = this.m_scriptMapping.get(scriptTag);
if (!sr) {
sr = { defaultLanguage: null, languages: new Map() };
this.m_scriptMapping.set(scriptTag, sr);
}
const lr = this.createLanguageRecord(data);
if (languageTag === "dflt" || languageTag === "DFLT") sr.defaultLanguage = lr;
else sr.languages.set(languageTag, lr);
}
createLanguageRecord(data) {
const features = [];
for (const fid of data.features) {
const feature = this.featureStore.query(fid);
if (feature) features.push(feature);
}
return {
requiredFeature: this.featureStore.query(data.requiredFeature) || null,
features: features
};
}
}
exports.convertGsub = ConvertGsubGposT(GsubHandlers, Ot.Gsub.Table);
exports.convertGpos = ConvertGsubGposT(GposHandlers, Ot.Gpos.Table);
function ConvertGsubGposT(handlers, T) {
return function (table, glyphs) {
if (!table) return null;
const ls = new LookupStore(handlers, glyphs);
if (table.lookups) {
if (table.lookupOrder) {
for (const l of table.lookupOrder) ls.declare(l, table.lookups[l]);
}
for (const l in table.lookups) ls.declare(l, table.lookups[l]);
for (const l in table.lookups) ls.fill(l, table.lookups[l]);
}
const fs = new FeatureStore(ls);
if (table.features) {
for (const f in table.features) fs.fill(f, table.features[f]);
}
const ss = new ScriptLanguageStore(fs);
if (table.languages) {
for (const sl in table.languages) ss.fill(sl, table.languages[sl]);
}
return new T(ss.extract(), fs.extract(), ls.extract());
};
}
exports.convertGdef = convertGdef;
function convertGdef(otdGdef, glyphs) {
const gdef = new Ot.Gdef.Table();
gdef.glyphClassDef = new Map();
for (const gn in otdGdef.glyphClassDef) {
const g = glyphs.query(gn);
if (g) gdef.glyphClassDef.set(g, otdGdef.glyphClassDef[gn]);
}
return gdef;
}

View file

@ -0,0 +1,98 @@
const { Ot } = require("ot-builder");
exports.convertHead = convertHead;
function convertHead(otdHead) {
const head = new Ot.Head.Table();
head.fontRevision = otdHead.fontRevision;
head.flags = otdHead.flags;
head.fontDirectionHint = otdHead.fontDirectionHint;
head.unitsPerEm = otdHead.unitsPerEm;
head.macStyle = macStyleObjToEnum(otdHead.macStyle);
head.lowestRecPPEM = otdHead.lowestRecPPEM;
head.glyphDataFormat = otdHead.glyphDataFormat;
return head;
}
exports.convertHhea = convertHhea;
function convertHhea(otdHhea) {
const hhea = new Ot.MetricHead.Hhea();
hhea.ascender = otdHhea.ascender;
hhea.descender = otdHhea.descender;
hhea.lineGap = otdHhea.lineGap;
hhea.caretSlopeRise = otdHhea.caretSlopeRise;
hhea.caretSlopeRun = otdHhea.caretSlopeRun;
hhea.caretOffset = otdHhea.caretOffset;
return hhea;
}
exports.convertPost = convertPost;
function convertPost(otdPost) {
const post = new Ot.Post.Table(3, 0);
post.italicAngle = otdPost.italicAngle;
post.underlinePosition = otdPost.underlinePosition;
post.underlineThickness = otdPost.underlineThickness;
post.isFixedPitch = otdPost.isFixedPitch;
return post;
}
exports.convertMaxp = convertMaxp;
function convertMaxp(otdMaxp) {
const maxp = Ot.Maxp.Table.TrueType();
return maxp;
}
exports.convertOs2 = convertOs2;
function convertOs2(otdOs2) {
const os2 = new Ot.Os2.Table(4);
os2.achVendID = otdOs2.achVendID;
os2.panose = otdOs2.panose;
os2.fsSelection = fsSelectionObjToEnum(otdOs2.fsSelection);
os2.fsType = otdOs2.fsType;
os2.sCapHeight = otdOs2.sCapHeight;
os2.sFamilyClass = otdOs2.sFamilyClass;
os2.sTypoAscender = otdOs2.sTypoAscender;
os2.sTypoDescender = otdOs2.sTypoDescender;
os2.sTypoLineGap = otdOs2.sTypoLineGap;
os2.sxHeight = otdOs2.sxHeight;
os2.usBreakChar = otdOs2.usBreakChar;
os2.usDefaultChar = otdOs2.usDefaultChar;
os2.usFirstCharIndex = otdOs2.usFirstCharIndex;
os2.usLastCharIndex = otdOs2.usLastCharIndex;
os2.usMaxContext = otdOs2.usMaxContext;
os2.usWeightClass = otdOs2.usWeightClass;
os2.usWidthClass = otdOs2.usWidthClass;
os2.usWinAscent = otdOs2.usWinAscent;
os2.usWinDescent = otdOs2.usWinDescent;
os2.xAvgCharWidth = otdOs2.xAvgCharWidth;
os2.yStrikeoutPosition = otdOs2.yStrikeoutPosition;
os2.yStrikeoutSize = otdOs2.yStrikeoutSize;
os2.ySubscriptXOffset = otdOs2.ySubscriptXOffset;
os2.ySubscriptXSize = otdOs2.ySubscriptXSize;
os2.ySubscriptYOffset = otdOs2.ySubscriptYOffset;
os2.ySubscriptYSize = otdOs2.ySubscriptYSize;
os2.ySuperscriptXOffset = otdOs2.ySuperscriptXOffset;
os2.ySuperscriptXSize = otdOs2.ySuperscriptXSize;
os2.ySuperscriptYOffset = otdOs2.ySuperscriptYOffset;
os2.ySuperscriptYSize = otdOs2.ySuperscriptYSize;
os2.ulCodePageRange1 = otdOs2.ulCodePageRange1;
os2.ulCodePageRange2 = otdOs2.ulCodePageRange2;
return os2;
}
function macStyleObjToEnum(o) {
return (
(o.bold ? Ot.Head.MacStyle.Bold : 0) |
(o.italic ? Ot.Head.MacStyle.Italic : 0) |
(o.condensed ? Ot.Head.MacStyle.Condensed : 0) |
(o.extended ? Ot.Head.MacStyle.Extended : 0)
);
}
function fsSelectionObjToEnum(o) {
return (
(o.oblique ? Ot.Os2.FsSelection.OBLIQUE : 0) |
(o.bold ? Ot.Os2.FsSelection.BOLD : 0) |
(o.italic ? Ot.Os2.FsSelection.ITALIC : 0) |
(o.regular ? Ot.Os2.FsSelection.REGULAR : 0) |
(o.useTypoMetrics ? Ot.Os2.FsSelection.USE_TYPO_METRICS : 0)
);
}

View file

@ -0,0 +1,20 @@
const { Ot } = require("ot-builder");
module.exports = convertName;
function convertName(otdName) {
const name = new Ot.Name.Table();
for (const entry of otdName) {
name.records.push({
platformID: entry.platformID,
encodingID: entry.encodingID,
languageID: entry.languageID,
nameID: entry.nameID,
value:
entry.platformID === 3 && entry.encodingID === 1
? entry.nameString
: Buffer.from(entry.nameString, "utf-8")
});
}
return name;
}

View file

@ -114,14 +114,15 @@ glyph-block AutoBuild-Accents : begin
define construction : glyph-proc
include s_parts.0 AS_BASE ALSO_METRICS
local nonTrivial false
foreach part [items-of : s_parts.slice 1] : if part : begin
include part
if (part.markAnchors && part.markAnchors.bottomright) : begin
eject-contour 'serifRB'
if [AnyDerivingCv.query s_parts.0] : begin
set nonTrivial true
if ([not nonTrivial] && [AnyDerivingCv.query s_parts.0]) : begin
local decomposeParts { }
foreach part [items-of s_parts] : begin
recursivelyDecompose part decomposeParts
foreach part [items-of s_parts] : recursivelyDecompose part decomposeParts
CvDecompose.set currentGlyph decomposeParts
define [RootGlyphProc goalName code parts] : begin
@ -195,12 +196,8 @@ define customDecompositions : object
."\u0247" "e\u0337"
."\u0290" "z\u0322"
."\u0256" "d\u0322"
."\u0273" "n\u0322"
."\u01AE" "T\u0322"
."\u1D8F" "a\u0322"
."\u1D90" "\u0251\u0322"
."\u1D91" "\u0257\u0322"
."\u1E10" "D\u0326"
."\u1E11" "d\u0326"

View file

@ -3,6 +3,8 @@ $$include '../../meta/macros.ptl'
import [mix linreg clamp fallback] from '../../support/utils'
import [getGrMesh AnyDerivingCv CvDecompose] from "../../support/gr"
extern Map
extern Set
glyph-module
@ -27,12 +29,16 @@ glyph-block Autobuild-Enclosure-Shared : begin
glyph-block-export EnsureComponentGlyphT
define [EnsureComponentGlyphT gidPart fnBuildup] : begin
local rs : new Set
local gniPart : fnBuildup gidPart
local grs : AnyDerivingCv.query [query-glyph gidPart]
if grs : foreach gr [items-of grs] : begin
local relatedGidPart : gr.get [query-glyph gidPart]
local gniRelated : fnBuildup relatedGidPart
if [query-glyph gniPart] : gr.set [query-glyph gniPart] gniRelated
if [query-glyph gniPart] : begin
gr.set [query-glyph gniPart] gniRelated
if [query-glyph gniRelated] : begin
[query-glyph gniPart].tryBecomeMirrorOf [query-glyph gniRelated] rs
return gniPart
glyph-block-export CollectJobs
@ -78,8 +84,11 @@ glyph-block Autobuild-Enclosure-Shared : begin
glyph-block-export applyRelations
define [applyRelations relApplications] : begin
local rs : new Set
foreach {gr f t} [items-of relApplications] : begin
if [query-glyph f] : gr.set [query-glyph f] t
if [query-glyph f] : begin
gr.set [query-glyph f] t
if [query-glyph t] : [query-glyph t].tryBecomeMirrorOf [query-glyph f] rs
glyph-block AutoBuild-Enclosure : begin
glyph-block-import CommonShapes
@ -91,18 +100,30 @@ glyph-block AutoBuild-Enclosure : begin
define [AdjustDigitCount digits width] : Math.max 1 (digits * Width / width)
define [EnclosureStrokeScale dscale digits width] : dscale / [Math.pow [AdjustDigitCount digits width] 0.66]
define aliasableInnerSubGlyphMap : new Map
define [EnsureInnerSubGlyphImpl miniatureFont prefix finalPlacement dscale xCompress shift] : lambda [gidPart] : begin
define gniPartNoPlc : '.ci.' + gidPart + '@' + [{ prefix dscale xCompress shift }.join '/']
define gniPart : '.ci.' + gidPart + '@' + [{ prefix finalPlacement dscale xCompress shift }.join '/']
if [not : query-glyph gniPart] : create-glyph gniPart : glyph-proc
set-width 0
include : miniatureFont.queryByNameEnsured gidPart
include : Upright
include : Scale (dscale * xCompress) dscale
include : Translate 0 (dscale * (-CAP / 2 + shift))
include : Translate 0 (CAP / 2 * dscale)
include : Translate 0 (SymbolMid - CAP * dscale / 2)
include : Italify
include : Translate finalPlacement 0
define aliasGoal : aliasableInnerSubGlyphMap.get gniPartNoPlc
if [not : query-glyph gniPart] : begin
if aliasGoal : begin
create-glyph gniPart : composite-proc
refer-glyph aliasGoal.gn
Translate (finalPlacement - aliasGoal.placement) 0
: else : begin
create-glyph gniPart : glyph-proc
set-width 0
include : miniatureFont.queryByNameEnsured gidPart
include : Upright
include : Scale (dscale * xCompress) dscale
include : Translate 0 (dscale * (-CAP / 2 + shift))
include : Translate 0 (CAP / 2 * dscale)
include : Translate 0 (SymbolMid - CAP * dscale / 2)
include : Italify
include : Translate finalPlacement 0
aliasableInnerSubGlyphMap.set gniPartNoPlc : object
gn gniPart
placement finalPlacement
return gniPart
define [EnsureInnerSubGlyphSeq miniatureFont prefix job dimens] : begin
@ -136,12 +157,13 @@ glyph-block AutoBuild-Enclosure : begin
define { gn unicode parts w bal baly } job
define [object width mockInnerWidth dscale] dimens
local finalParts : EnsureInnerSubGlyphSeq miniatureFont prefix job dimens
local inner : new-glyph : glyph-proc
if gnEnclosure : return : glyph-proc
foreach [gidPart : items-of finalParts] : include [refer-glyph gidPart]
include : Translate width 0
CvDecompose.set currentGlyph [{gnEnclosure}.concat finalParts]
: else : return : new-glyph : glyph-proc
foreach [gidPart : items-of finalParts] : include [refer-glyph gidPart]
include : Translate width 0
return : glyph-proc
include inner
if (gnEnclosure) : CvDecompose.set currentGlyph [{gnEnclosure}.concat finalParts]
define [CircCrowd digits width] : 2 + 2 * [Math.pow [AdjustDigitCount digits width] 0.5] * [Math.max 1 (HalfUPM / Width)]
define [CircScale digits width] : 0.65 / [Math.pow [AdjustDigitCount digits width] 0.5]
@ -350,8 +372,8 @@ glyph-block AutoBuild-Enclosure : begin
define dimens : bracedDottdeDimens digits ww
define [object width mockInnerWidth dscale] dimens
set-width width
include : refer-glyph gnb
include : EnclosureInner [if jobDecomposable gnb null] miniatureFont prefix job dimens
include : refer-glyph gnb
foreach job [items-of jobs.decomposableJobs] : CreateGlyphImpl true job
foreach job [items-of jobs.nonDecomposable] : CreateGlyphImpl false job

View file

@ -3,6 +3,7 @@ $$include '../../meta/macros.ptl'
import [linreg clamp mix fallback] from '../../support/utils'
import [AnyDerivingCv DotlessOrNot getGrTree getGrMesh CvDecompose] from "../../support/gr"
extern Set
glyph-module
@ -46,13 +47,17 @@ glyph-block Autobuild-Transformed : begin
set relatedRecord.1 relSrcName
relatedRecords.push relatedRecord
relSets.push {gr origDstName relDstName}
relSets.push { gr origDstName relDstName }
return {[records.concat relatedRecords] relSets targetNameMap}
define [link-relations relSets] : foreach [{gr origDstName relDstName} : items-of relSets]
if [query-glyph origDstName] : gr.set [query-glyph origDstName] relDstName
return { [records.concat relatedRecords] relSets targetNameMap }
define [link-relations relSets] : begin
local rs : new Set
foreach [{gr origDstName relDstName} : items-of relSets]
if [query-glyph origDstName] : begin
gr.set [query-glyph origDstName] relDstName
if [query-glyph relDstName] : begin
[query-glyph origDstName].tryBecomeMirrorOf [query-glyph relDstName] rs
define [createSuperscripts _records] : begin
local {records relSets targetNameMap} : extendRelatedGlyphs _records 'sup'
@ -254,7 +259,7 @@ glyph-block Autobuild-Transformed : begin
list 0x1DAC 'mltail'
list 0x1DAD 'turnmleg'
list 0x1DAE 'nltail'
list 0x1DAF 'nrtailBR'
list 0x1DAF 'nHookBottom'
list 0x1DB0 'smcpN'
list 0x1DB1 'obar'
list 0x1DB2 'latinphi'

View file

@ -90,27 +90,19 @@ glyph-block Common-Derivatives : begin
if [not newid] : throw : new Error "Target ID not specified"
return : HCombine newid unicode id id spacing
define [vcombine newid unicode id1 id2 spacing] : begin
define [VCombine newid unicode id1 id2 spacing] : begin
if [not newid] : throw : new Error "Target ID not specified"
create-glyph newid unicode : glyph-proc
set-width [query-glyph id1].advanceWidth
currentGlyph.dependsOn [query-glyph id1]
currentGlyph.dependsOn [query-glyph id2]
include : new-glyph : glyph-proc
include [refer-glyph id2]
include : Upright
include : Translate 0 (-spacing)
include : new-glyph : glyph-proc
include [refer-glyph id1]
include : Upright
include : Translate 0 (spacing / 2)
include : Italify
include [refer-glyph id2] AS_BASE ALSO_METRICS
include : Translate (-spacing * TanSlope) (-spacing)
include [refer-glyph id1]
include : Translate (spacing * TanSlope / 2) (spacing / 2)
define [VDual newid unicode id spacing] : begin
if [not newid] : throw : new Error "Target ID not specified"
return : vcombine newid unicode id id spacing
return : VCombine newid unicode id id spacing
glyph-block-export select-variant italic-variant refer-glyph query-glyph with-related-glyphs
glyph-block-export alias turned HDual HCombine VDual vcombine glyph-is-needed
glyph-block-export alias turned HDual HCombine VDual VCombine glyph-is-needed
glyph-block Recursive-Build : begin
define [Fork gs ps] : begin

View file

@ -1,8 +1,8 @@
import '../support/glyph' as Glyph
import '../support/glyph-store' as GlyphStore
import '../support/point' as Point
import './kits/spiro-kit' as spirokit
import './kits/boole-kit' as BooleKit
import '../kits/spiro-kit' as spirokit
import '../kits/boole-kit' as BooleKit
import '../support/anchor' as Anchor
import [ designParameters ] from "../meta/aesthetics"
@ -115,35 +115,35 @@ export all : define [buildGlyphs para recursive recursiveCodes] : begin
define $$Capture$$ : object [metrics : Object.create metrics] $NamedParameterPair$ $donothing$ para recursive recursiveCodes variantSelector glyphStore $createAndSaveGlyphImpl$ spirofns booleFns MarkSet AS_BASE ALSO_METRICS pickHash buildGlyphs tagged DivFrame fontMetrics $assignUnicodeImpl$
### HERE WE GO
run-glyph-module '../glyphs/common/shapes.js'
run-glyph-module '../glyphs/common/derivatives.js'
run-glyph-module '../glyphs/space/index.js'
run-glyph-module '../glyphs/marks/index.js'
run-glyph-module './common/shapes.js'
run-glyph-module './common/derivatives.js'
run-glyph-module './space/index.js'
run-glyph-module './marks/index.js'
# Unified letters
run-glyph-module '../glyphs/letter/latin.js'
run-glyph-module '../glyphs/letter/greek.js'
run-glyph-module '../glyphs/letter/cyrillic.js'
run-glyph-module './letter/latin.js'
run-glyph-module './letter/greek.js'
run-glyph-module './letter/cyrillic.js'
# Numbers
run-glyph-module '../glyphs/number/index.js'
run-glyph-module './number/index.js'
# Symbols
run-glyph-module '../glyphs/symbol/punctuation.js'
run-glyph-module '../glyphs/symbol/arrow.js'
run-glyph-module '../glyphs/symbol/geometric.js'
run-glyph-module '../glyphs/symbol/enclosure.js'
run-glyph-module '../glyphs/symbol/math.js'
run-glyph-module '../glyphs/symbol/letter.js'
run-glyph-module '../glyphs/symbol/braille.js'
run-glyph-module '../glyphs/symbol/mosaic.js'
run-glyph-module '../glyphs/symbol/pictograph.js'
run-glyph-module '../glyphs/symbol/ligation.js'
run-glyph-module './symbol/punctuation.js'
run-glyph-module './symbol/arrow.js'
run-glyph-module './symbol/geometric.js'
run-glyph-module './symbol/enclosure.js'
run-glyph-module './symbol/math.js'
run-glyph-module './symbol/letter.js'
run-glyph-module './symbol/braille.js'
run-glyph-module './symbol/mosaic.js'
run-glyph-module './symbol/pictograph.js'
run-glyph-module './symbol/ligation.js'
# Autobuilds
run-glyph-module '../glyphs/auto-build/accents.js'
run-glyph-module '../glyphs/auto-build/composite.js'
run-glyph-module '../glyphs/auto-build/transformed.js'
run-glyph-module './auto-build/accents.js'
run-glyph-module './auto-build/composite.js'
run-glyph-module './auto-build/transformed.js'
return : object metrics glyphStore

View file

@ -1160,7 +1160,6 @@ glyph-block Marks : begin
curl (Middle + dx - Width) (XH / 2 + dy)
create-glyph 'arrVStrokeOver' : glyph-proc
include : ForceUpright
set-width 0
local fine : 0.375 * OverlayStroke
local dx : Width * 0
@ -1171,7 +1170,6 @@ glyph-block Marks : begin
curl (Middle + dx - Width) (XH / 2 + dy)
create-glyph 'arrDblVStrokeOver' : glyph-proc
include : ForceUpright
set-width 0
local fine : 0.3 * OverlayStroke
local dx : Width * 0
@ -1186,7 +1184,6 @@ glyph-block Marks : begin
curl (Middle + dx - Width + gap) (XH / 2 + dy)
create-glyph 'arrHStrokeOver' : glyph-proc
include : ForceUpright
set-width 0
local fine : 0.375 * OverlayStroke
local dx : Width * 0.3
@ -1196,7 +1193,6 @@ glyph-block Marks : begin
curl (Middle - Width + dx) (XH / 2)
create-glyph 'arrDblHStrokeOver' : glyph-proc
include : ForceUpright
set-width 0
local fine : 0.3 * OverlayStroke
local dx : Width * 0.3

View file

@ -13,16 +13,11 @@ glyph-block Symbol-Mosaic-NotDef : begin
set currentGlyph.autoRefPriority (-9998)
set currentGlyph.glyphRank (9998)
create-glyph 'nonmarkingreturn' 0x000D : glyph-proc
set-width 0
set currentGlyph.autoRefPriority (-1)
create-glyph 'space' ' ' : glyph-proc
set currentGlyph.autoRefPriority (-100)
local df : DivFrame para.diversityI
set-width df.width
create-glyph 'markDemoBaseSpace' 0xE00E : glyph-proc
local df : DivFrame 1
set-width df.width
@ -41,7 +36,8 @@ glyph-block Symbol-Mosaic-NotDef : begin
set-width 0
set currentGlyph.autoRefPriority (-9999)
alias 'cgj' 0x034F '.null'
alias 'nonmarkingreturn' 0x000D 'zwsp'
alias 'cgj' 0x034F 'zwsp'
alias 'nbsp' 0xA0 'space'
alias 'threePerEmsp' 0x2004 'space'
alias 'fourPerEmsp' 0x2005 'space'

View file

@ -443,17 +443,17 @@ glyph-block Symbol-Arrow : begin
do
local hcDist : Math.max (halfstroke * 5) (arrowWidth / 2)
vcombine [MangleName 'uni21C4'] [MangleUnicode 0x21C4] [MangleName 'arrowright'] [MangleName 'arrowleft'] (arrowSize * 1.75)
VCombine [MangleName 'uni21C4'] [MangleUnicode 0x21C4] [MangleName 'arrowright'] [MangleName 'arrowleft'] (arrowSize * 1.75)
HCombine [MangleName 'uni21C5'] [MangleUnicode 0x21C5] [MangleName 'arrowup'] [MangleName 'arrowdown'] hcDist
vcombine [MangleName 'uni21C6'] [MangleUnicode 0x21C6] [MangleName 'arrowleft'] [MangleName 'arrowright'] (arrowSize * 1.75)
vcombine [MangleName 'uni21C7'] [MangleUnicode 0x21C7] [MangleName 'arrowleft'] [MangleName 'arrowleft'] (arrowSize * 1.75)
VCombine [MangleName 'uni21C6'] [MangleUnicode 0x21C6] [MangleName 'arrowleft'] [MangleName 'arrowright'] (arrowSize * 1.75)
VCombine [MangleName 'uni21C7'] [MangleUnicode 0x21C7] [MangleName 'arrowleft'] [MangleName 'arrowleft'] (arrowSize * 1.75)
HCombine [MangleName 'uni21C8'] [MangleUnicode 0x21C8] [MangleName 'arrowup'] [MangleName 'arrowup'] hcDist
vcombine [MangleName 'uni21C9'] [MangleUnicode 0x21C9] [MangleName 'arrowright'] [MangleName 'arrowright'] (arrowSize * 1.75)
VCombine [MangleName 'uni21C9'] [MangleUnicode 0x21C9] [MangleName 'arrowright'] [MangleName 'arrowright'] (arrowSize * 1.75)
HCombine [MangleName 'uni21CA'] [MangleUnicode 0x21CA] [MangleName 'arrowdown'] [MangleName 'arrowdown'] hcDist
HCombine [MangleName 'uni21F5'] [MangleUnicode 0x21F5] [MangleName 'arrowdown'] [MangleName 'arrowup'] hcDist
vcombine [MangleName 'uni21CB'] [MangleUnicode 0x21CB] [MangleName 'arrowleftHR'] [MangleName 'arrowrightHR'] (arrowSize)
vcombine [MangleName 'uni21CC'] [MangleUnicode 0x21CC] [MangleName 'arrowrightHL'] [MangleName 'arrowleftHL'] (arrowSize)
vcombine [MangleName 'uni21B9'] [MangleUnicode 0x21B9] [MangleName 'arrowbarleft'] [MangleName 'arrowbarright'] (arrowSize * 1.75)
VCombine [MangleName 'uni21CB'] [MangleUnicode 0x21CB] [MangleName 'arrowleftHR'] [MangleName 'arrowrightHR'] (arrowSize)
VCombine [MangleName 'uni21CC'] [MangleUnicode 0x21CC] [MangleName 'arrowrightHL'] [MangleName 'arrowleftHL'] (arrowSize)
VCombine [MangleName 'uni21B9'] [MangleUnicode 0x21B9] [MangleName 'arrowbarleft'] [MangleName 'arrowbarright'] (arrowSize * 1.75)
do "Legacy computing arrows"
define bodyWidth : Math.min arrowWidth (arrowHeight * 0.6)

View file

@ -3,9 +3,7 @@ $$include '../../meta/macros.ptl'
import [mix linreg clamp fallback] from '../../support/utils'
import [designParameters] from '../../meta/aesthetics'
# Needed for bitwise operators
import '../../support/mask-bit' as maskBit
import [maskBit maskBits popCountByte] from '../../support/mask-bit'
glyph-module
@ -21,6 +19,39 @@ glyph-block Symbol-Braille : begin
local stress : 0.5 * [adviceBlackness 3.375]
local dotRadius : PeriodRadius * [Math.min 1 (stress / HalfStroke)]
define [BrailleYCoord y] (SymbolMid + y * offsetY)
define [BrailleOutlineShape byte] : glyph-proc
foreach [bit : range 0 8] : if [maskBit byte bit] : begin
include : match bit
0 : DotAt left [BrailleYCoord (-3)] dotRadius
1 : DotAt left [BrailleYCoord (-1)] dotRadius
2 : DotAt left [BrailleYCoord 1 ] dotRadius
3 : DotAt right [BrailleYCoord (-3)] dotRadius
4 : DotAt right [BrailleYCoord (-1)] dotRadius
5 : DotAt right [BrailleYCoord 1 ] dotRadius
6 : DotAt left [BrailleYCoord 3 ] dotRadius
7 : DotAt right [BrailleYCoord 3 ] dotRadius
define [BrailleReferenceShape byte] : glyph-proc
foreach [bit : range 0 8] : if [maskBit byte bit]
include : refer-glyph ['braille' + (bit + 1)]
define [BrailleHalvesShape byte] : glyph-proc
local leftHalf : maskBits byte 0x47
local rightHalf : maskBits byte 0xB8
include : refer-glyph : brailleGlyphName leftHalf
include : refer-glyph : brailleGlyphName rightHalf
define [brailleBuildStage byte] : begin
if (1 == [popCountByte byte]) : return 1
if (![maskBits byte 0x47] || ![maskBits byte 0xB8]) : return 2
return 3
define [brailleGlyphName byte] : begin
local dots {}
foreach [bit : range 0 8] : if [maskBit byte bit] : dots.push (bit + 1)
return ("braille" + [dots.join ''])
### Computed Braille Patterns
###
### Bit to Dot mapping:
@ -28,52 +59,12 @@ glyph-block Symbol-Braille : begin
### 1 4
### 2 5
### 6 7
###
foreach [byte : range 1 256] : begin
local dots {}
foreach [bit : range 0 8] : if [maskBit byte bit] : dots.push (bit + 1)
create-glyph ("braille" + [dots.join '']) (0x2800 + byte) : glyph-proc
set currentGlyph.autoRefPriority (-101)
local [y-coord y] (SymbolMid + y * offsetY)
# Is there a way to directly use (byte & (1 << bit)) here?
foreach [bit : range 0 8] : if [maskBit byte bit] : begin
include : match bit
0 : DotAt left [y-coord (-3)] dotRadius
1 : DotAt left [y-coord (-1)] dotRadius
2 : DotAt left [y-coord 1 ] dotRadius
3 : DotAt right [y-coord (-3)] dotRadius
4 : DotAt right [y-coord (-1)] dotRadius
5 : DotAt right [y-coord 1 ] dotRadius
6 : DotAt left [y-coord 3 ] dotRadius
7 : DotAt right [y-coord 3 ] dotRadius
if [query-glyph 'braille7'] : set [query-glyph 'braille7'].autoRefPriority 101
if [query-glyph 'braille37'] : set [query-glyph 'braille37'].autoRefPriority 102
if [query-glyph 'braille3'] : set [query-glyph 'braille3'].autoRefPriority 101
if [query-glyph 'braille237'] : set [query-glyph 'braille237'].autoRefPriority 103
if [query-glyph 'braille27'] : set [query-glyph 'braille27'].autoRefPriority 102
if [query-glyph 'braille23'] : set [query-glyph 'braille23'].autoRefPriority 102
if [query-glyph 'braille2'] : set [query-glyph 'braille2'].autoRefPriority 101
if [query-glyph 'braille1237'] : set [query-glyph 'braille1237'].autoRefPriority 104
if [query-glyph 'braille123'] : set [query-glyph 'braille123'].autoRefPriority 103
if [query-glyph 'braille127'] : set [query-glyph 'braille127'].autoRefPriority 103
if [query-glyph 'braille137'] : set [query-glyph 'braille137'].autoRefPriority 103
if [query-glyph 'braille13'] : set [query-glyph 'braille13'].autoRefPriority 102
if [query-glyph 'braille12'] : set [query-glyph 'braille12'].autoRefPriority 102
if [query-glyph 'braille17'] : set [query-glyph 'braille17'].autoRefPriority 102
if [query-glyph 'braille1'] : set [query-glyph 'braille1'].autoRefPriority 101
if [query-glyph 'braille7'] : set [query-glyph 'braille7'].avoidBeingComposite true
if [query-glyph 'braille37'] : set [query-glyph 'braille37'].avoidBeingComposite true
if [query-glyph 'braille3'] : set [query-glyph 'braille3'].avoidBeingComposite true
if [query-glyph 'braille237'] : set [query-glyph 'braille237'].avoidBeingComposite true
if [query-glyph 'braille27'] : set [query-glyph 'braille27'].avoidBeingComposite true
if [query-glyph 'braille23'] : set [query-glyph 'braille23'].avoidBeingComposite true
if [query-glyph 'braille2'] : set [query-glyph 'braille2'].avoidBeingComposite true
if [query-glyph 'braille1237'] : set [query-glyph 'braille1237'].avoidBeingComposite true
if [query-glyph 'braille123'] : set [query-glyph 'braille123'].avoidBeingComposite true
if [query-glyph 'braille127'] : set [query-glyph 'braille127'].avoidBeingComposite true
if [query-glyph 'braille137'] : set [query-glyph 'braille137'].avoidBeingComposite true
if [query-glyph 'braille13'] : set [query-glyph 'braille13'].avoidBeingComposite true
if [query-glyph 'braille12'] : set [query-glyph 'braille12'].avoidBeingComposite true
if [query-glyph 'braille17'] : set [query-glyph 'braille17'].avoidBeingComposite true
if [query-glyph 'braille1'] : set [query-glyph 'braille1'].avoidBeingComposite true
define stageConfig : list
list 1 BrailleOutlineShape
list 2 BrailleReferenceShape
list 3 BrailleHalvesShape
foreach { stage F } [items-of stageConfig] : begin
foreach [byte : range 1 256] : if (stage == [brailleBuildStage byte])
create-glyph [brailleGlyphName byte] (0x2800 + byte) : F byte

View file

@ -305,7 +305,7 @@ glyph-block Symbol-Geometric : for-width-kinds WideWidth1
corner (cx - size * triangleOvershoot) (cy - size)
define [TriangularVeeFill cx cy size] : intersection
TriangleUpFill cx cy size
TriangleDownFill cx cy size
union
dispiro
widths.lhs GeometryStroke

View file

@ -4,7 +4,7 @@ $$include '../../meta/macros.ptl'
import [mix linreg clamp fallback] from '../../support/utils'
import [designParameters] from '../../meta/aesthetics'
import '../../support/transform' as Transform
import '../../support/mask-bit' as maskBit
import [maskBit] from '../../support/mask-bit'
glyph-module

View file

@ -1135,27 +1135,17 @@ glyph-block Symbol-Punctuation-Quotes-And-Primes : begin
include : FlipAround Middle [mix [mix PeriodSize commaLow 0.5] yCurlyQuotes 0.5]
include : FlipAround Middle yCurlyQuotes
create-glyph 'lowDoubleQuote' 0x201E : glyph-proc
define [DoubleQuoteProc gn] : glyph-proc
local dist : Width * 0.225
include : refer-glyph "lowSingleQuote"
include : refer-glyph gn
include : Translate (-dist * 2) 0
include : refer-glyph "lowSingleQuote"
include : refer-glyph gn
include : Translate dist 0
create-glyph 'openDoubleQuote' 0x201C : glyph-proc
include : refer-glyph "lowDoubleQuote"
include : FlipAround Middle [mix [mix PeriodSize commaLow 0.5] yCurlyQuotes 0.5]
create-glyph 'closeDoubleQuote' 0x201D : glyph-proc
include : refer-glyph "openDoubleQuote"
include : FlipAround Middle yCurlyQuotes
create-glyph 'revertDoubleQuote' 0x201F : glyph-proc
local dist : Width * 0.225
include : refer-glyph "revertSingleQuote"
include : Translate (-dist * 2) 0
include : refer-glyph "revertSingleQuote"
include : Translate dist 0
create-glyph 'lowDoubleQuote' 0x201E : DoubleQuoteProc 'lowSingleQuote'
create-glyph 'openDoubleQuote' 0x201C : DoubleQuoteProc 'openSingleQuote'
create-glyph 'closeDoubleQuote' 0x201D : DoubleQuoteProc 'closeSingleQuote'
create-glyph 'revertDoubleQuote' 0x201F : DoubleQuoteProc 'revertSingleQuote'
create-glyph 'prime' 0x2032 : glyph-proc
local wide : Width * 0.1
@ -1592,15 +1582,18 @@ glyph-block Symbol-Punctuation-Ligation-Variants : begin
* "question"
* "exclam"
foreach [baseName : items-of dotLikePunctuations] : if [query-glyph baseName]
create-glyph : glyph-proc
include [refer-glyph baseName] AS_BASE ALSO_METRICS
local delta : Math.max 0 : Width / 2 - (XH - PeriodSize) / 2
create-derived (baseName + '.lig.dRight') : Translate (delta * 1.0 ) 0
create-derived (baseName + '.lig.dRightHalf') : Translate (delta * 0.5 ) 0
create-derived (baseName + ".lig.dMid") : Translate 0 0
create-derived (baseName + '.lig.dLeftHalf') : Translate (delta * (-0.5)) 0
create-derived (baseName + '.lig.dLeft') : Translate (delta * (-1.0)) 0
define [LigD baseName suffix pDelta] : if [query-glyph baseName] : begin
local delta : Math.max 0 : Width / 2 - (XH - PeriodSize) / 2
create-glyph (baseName + '.lig.' + suffix) : composite-proc
refer-glyph baseName
Translate (delta * pDelta ) 0
foreach [baseName : items-of dotLikePunctuations] : begin
LigD baseName 'dRight' 1.0
LigD baseName 'dRightHalf' 0.5
LigD baseName "dMid" 0
LigD baseName 'dLeftHalf' (-0.5)
LigD baseName 'dLeft' (-1.0)
glyph-block Symbol-Other-Phonetic : begin
glyph-block-import CommonShapes

View file

@ -2,6 +2,7 @@
const fs = require("fs-extra");
const path = require("path");
const { FontIo } = require("ot-builder");
const Toml = require("@iarna/toml");
@ -15,7 +16,7 @@ module.exports = async function main(argv) {
const para = await getParameters(argv);
const { font, glyphStore } = BuildFont(para);
if (argv.oCharMap) await saveCharMap(argv, glyphStore);
if (argv.o) await saveOTD(argv, font);
if (argv.o) await saveTTF(argv, font);
};
// Parameter preparation
@ -81,9 +82,14 @@ async function tryParseToml(str) {
}
}
// Save OTD
async function saveOTD(argv, font) {
await fs.writeJSON(argv.o, font);
// Save TTF
async function saveTTF(argv, font) {
const sfnt = FontIo.writeFont(font, {
glyphStore: { statOs2XAvgCharWidth: false },
generateDummyDigitalSignature: true
});
const buf = FontIo.writeSfntOtf(sfnt);
await fs.writeFile(argv.o, buf);
}
// Save character map file

View file

@ -1,5 +1,5 @@
import 'typo-geom' as TypoGeom
import '../../support/curve-util' as CurveUtil
import '../support/curve-util' as CurveUtil
export : define [SetupBuilders args] : begin
define [object Glyph GlobalTransform] args

View file

@ -1,8 +1,8 @@
import 'spiro' as SpiroJs
import '../../support/spiro-expand' as SpiroExpansionContext
import '../../support/curve-util' as CurveUtil
import '../../support/transform' as Transform
import '../../support/utils' as [object fallback mix bez2 bez3]
import '../support/spiro-expand' as SpiroExpansionContext
import '../support/curve-util' as CurveUtil
import '../support/transform' as Transform
import '../support/utils' as [object fallback mix bez2 bez3]
export : define [SetupBuilders args] : begin
define [object para Glyph Contrast GlobalTransform Stroke] args

View file

@ -107,7 +107,7 @@ define-macro glyph-block-import : syntax-rules
define allExports : object
Common-Derivatives `[select-variant italic-variant refer-glyph query-glyph alias turned
HDual HCombine VDual vcombine with-related-glyphs glyph-is-needed]
HDual HCombine VDual VCombine with-related-glyphs glyph-is-needed]
CommonShapes `[Rect SquareAt Ring RingAt DotAt RingStroke RingStrokeAt DotStrokeAt
CircleRing CircleRingAt CircleDotAt OShape OShapeOutline OBarLeftShape OBarRightShape

View file

@ -140,9 +140,6 @@ export : define [assignFontNames para metrics font] : begin
nameFont font WWS_PREFERRED_FAMILY family # WWS Preferred Family
nameFont font WWS_PREFERRED_STYLE style # WWS Preferred Style
set font.name.preferredFamily family
set font.name.preferredSubFamily style
local {compatStyle compatFamilySuffix shortCompatFamilySuffix} : getStyleLinkedStyles para.naming.weight para.naming.width para.naming.slope
local compatFamily family
if (compatFamilySuffix != "Regular") : set compatFamily : family + ' ' + compatFamilySuffix

View file

@ -18,17 +18,17 @@ export : define [buildMarkMkmk sink glyphStore] : begin
foreach markCls [items-of MarkClasses] : begin
local [object markSubtable mkmkSubtable] : createMTSubtables glyphStore { markCls }
if ([objectIsNotEmpty markSubtable.marks] && [objectIsNotEmpty markSubtable.bases]) : begin
local markLookup : add-lookup sink {.type 'gpos_mark_to_base' .subtables { markSubtable }}
mark.lookups.push markLookup
markLookupNames.push markLookup
local lidMark : add-lookup sink {.type 'gpos_mark_to_base' .subtables { markSubtable }}
mark.lookups.push lidMark
markLookupNames.push lidMark
if ([objectIsNotEmpty mkmkSubtable.marks] && [objectIsNotEmpty mkmkSubtable.bases]) : begin
local mkmkLookup : add-lookup sink {.type 'gpos_mark_to_mark' .subtables { mkmkSubtable }}
mkmk.lookups.push mkmkLookup
mkmkLookupNames.push mkmkLookup
local lidMkmk : add-lookup sink {.type 'gpos_mark_to_mark' .subtables { mkmkSubtable }}
mkmk.lookups.push lidMkmk
mkmkLookupNames.push lidMkmk
foreach markLookup [items-of markLookupNames] : foreach mkmkLookup [items-of mkmkLookupNames]
sink.lookupDep.push { markLookup mkmkLookup }
foreach lidMark [items-of markLookupNames] : foreach lidMkmk [items-of mkmkLookupNames]
sink.lookupDep.push { lidMark lidMkmk }
define [createMTSubtables glyphStore markClasses] : begin
local markSubtable {.marks {.} .bases {.}}

View file

@ -13,6 +13,7 @@ module.exports = class Glyph {
this.markAnchors = {};
this.baseAnchors = {};
this.gizmo = Transform.Id();
this.semanticInclusions = [];
this.dependencies = [];
this.defaultTag = null;
}
@ -51,6 +52,7 @@ module.exports = class Glyph {
this.contours[j++] = this.contours[i];
}
this.contours.length = j;
this.semanticInclusions = [];
}
// Inclusion
include(component, copyAnchors, copyWidth) {
@ -83,10 +85,18 @@ module.exports = class Glyph {
}
}
this.includeGeometry(g, shift.x, shift.y);
const newContours = 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);
if (g._m_identifier && newContours && newContours.length) {
this.semanticInclusions.push({
glyph: g,
x: shift.x,
y: shift.y,
contours: newContours
});
}
}
cloneFromGlyph(g) {
this.includeGlyph(g, true, true);
@ -102,15 +112,56 @@ module.exports = class Glyph {
this.glyphRank = g.glyphRank;
this.avoidBeingComposite = g.avoidBeingComposite;
}
includeGeometry(geom, shiftX, shiftY) {
if (!geom || !geom.contours) return;
if (!geom || !geom.contours || !geom.contours.length) return null;
if (this.includeGeometryAsTransparentReferences(geom, shiftX, shiftY)) return null;
return this.includeGeometryImpl(geom, shiftX, shiftY);
}
isPureComposite() {
if (!this.semanticInclusions || !this.semanticInclusions.length) return false;
const origContourSet = new Set(this.contours);
let handledContours = new Set();
for (const sr of this.semanticInclusions) {
for (const c of sr.contours) {
if (!origContourSet.has(c) || handledContours.has(c)) return false;
handledContours.add(c);
}
}
for (const c of this.contours) if (!handledContours.has(c)) return false;
return true;
}
includeGeometryAsTransparentReferences(geom, shiftX, shiftY) {
if (!(geom instanceof Glyph && !geom._m_identifier)) return false;
if (!geom.isPureComposite()) return false;
for (const sr of geom.semanticInclusions) {
const cs = this.includeGeometryImpl(sr.glyph, sr.x + shiftX, sr.y + shiftY);
if (cs) {
this.semanticInclusions.push({
glyph: sr.glyph,
x: sr.x + shiftX,
y: sr.y + shiftY,
contours: cs
});
}
}
return true;
}
includeGeometryImpl(geom, shiftX, shiftY) {
let newContours = [];
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);
newContours.push(c);
}
return newContours;
}
combineAnchor(shift, baseThis, markThat, basesThat) {
if (!baseThis || !markThat) return;
shift.x = baseThis.x - markThat.x;
@ -129,10 +180,20 @@ module.exports = class Glyph {
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]);
for (const c of this.contours) {
for (let k = 0; k < c.length; k++) {
c[k] = Point.transformed(tfm, c[k]);
}
}
if (Transform.isTranslate(tfm)) {
for (const sr of this.semanticInclusions) {
sr.x += tfm.x;
sr.y += tfm.y;
}
} else {
// Applying a non-trivial inclusion will unlink all the SIs
this.semanticInclusions = [];
}
if (alsoAnchors) {
for (const k in this.baseAnchors)
this.baseAnchors[k] = Anchor.transform(tfm, this.baseAnchors[k]);
@ -140,6 +201,35 @@ module.exports = class Glyph {
this.markAnchors[k] = Anchor.transform(tfm, this.markAnchors[k]);
}
}
tryBecomeMirrorOf(dst, rankSet) {
if (rankSet.has(this) || rankSet.has(dst)) return;
if (this.contours.length !== dst.contours.length) return;
for (let j = 0; j < this.contours.length; j++) {
const c1 = this.contours[j],
c2 = dst.contours[j];
if (c1.length !== c2.length) return;
}
for (let j = 0; j < this.contours.length; j++) {
const c1 = this.contours[j],
c2 = dst.contours[j];
for (let k = 0; k < c1.length; k++) {
const z1 = c1[k],
z2 = c2[k];
if (z1.x !== z2.x || z1.y !== z2.y || z1.on !== z2.on) return;
}
}
this.semanticInclusions = [
{
glyph: dst,
x: 0,
y: 0,
contours: [...this.contours]
}
];
rankSet.add(this);
}
// Anchors
setBaseAnchor(id, x, y) {
this.baseAnchors[id] = new Anchor(x, y).transform(this.gizmo);

View file

@ -1,5 +1,14 @@
"use strict";
module.exports = function maskBit(x, y) {
exports.maskBit = function maskBit(x, y) {
return x & (1 << y);
};
exports.maskBits = function maskBits(x, y) {
return x & y;
};
const pcNibbleLookup = [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4];
exports.popCountByte = function (x) {
return pcNibbleLookup[x & 0x0f] + pcNibbleLookup[(x >>> 4) & 0x0f];
};

View file

@ -46,4 +46,8 @@ module.exports = class Transform {
-(-this.x * this.xy + this.y * this.xx) / denom
);
}
static isTranslate(tfm) {
return tfm.xx === 1 && tfm.yy === 1 && tfm.xy === 0 && tfm.yx === 0;
}
};

View file

@ -14,7 +14,8 @@
"ejs": "^3.1.3",
"fs-extra": "^9.0.1",
"object-assign": "^4.1.1",
"otfcc-ttcize": "^0.10.2",
"ot-builder": "^0.10.28",
"otb-ttc-bundle": "^0.10.28",
"patel": "^0.33.1",
"semver": "^7.3.2",
"spiro": "^2.0.0",

View file

@ -7,11 +7,7 @@ const FgYellow = "\x1b[33m";
const Reset = "\x1b[0m";
console.log("Checking External Dependencies");
check("otfccdump");
check("otfccbuild");
check("ttfautohint");
check("otf2otc");
checkOptional("ttx");
function check(util) {
try {
@ -21,11 +17,3 @@ function check(util) {
console.error(FgRed + ` * External dependency <${util}> is not found.` + Reset);
}
}
function checkOptional(util) {
try {
which.sync(util);
console.error(FgGreen + ` * Optional external dependency <${util}> is present.` + Reset);
} catch (e) {
console.error(FgYellow + ` * Optional external dependency <${util}> is not found.` + Reset);
}
}

View file

@ -12,7 +12,7 @@ module.exports = build;
///////////////////////////////////////////////////////////
const path = require("path");
const Path = require("path");
const toml = require("@iarna/toml");
const BUILD = ".build";
@ -21,11 +21,13 @@ const SNAPSHOT_TMP = ".build/snapshot";
const DIST_SUPER_TTC = "dist/.super-ttc";
const ARCHIVE_DIR = "release-archives";
const OTF2OTC = "otf2otc";
const OTFCC_BUILD = "otfccbuild";
const TTX = "ttx";
const PATEL_C = ["node", "./node_modules/patel/bin/patel-c"];
const TTCIZE = ["node", "./node_modules/otfcc-ttcize/bin/_startup"];
const TTCIZE = [
"node",
"--max-old-space-size=8192",
"node_modules/otb-ttc-bundle/bin/otb-ttc-bundle"
];
const webfontFormats = [
["woff2", "woff2"],
["woff", "woff"],
@ -41,11 +43,8 @@ const WEIGHT_NORMAL = "regular";
const SLOPE_NORMAL = "upright";
const DEFAULT_SUBFAMILY = "regular";
const BUILD_PLANS = path.relative(__dirname, path.resolve(__dirname, "./build-plans.toml"));
const PRIVATE_BUILD_PLANS = path.relative(
__dirname,
path.resolve(__dirname, "./private-build-plans.toml")
);
const BUILD_PLANS = "build-plans.toml";
const PRIVATE_BUILD_PLANS = "private-build-plans.toml";
// Save journal to build/
build.setJournal(`${BUILD}/.verda-build-journal`);
@ -56,8 +55,9 @@ build.setSelfTracking();
////// Oracles //////
///////////////////////////////////////////////////////////
const Version = oracle(`oracle:version`, async () => {
const package_json = JSON.parse(fs.readFileSync(path.join(__dirname, "package.json")));
const Version = oracle(`oracle:version`, async target => {
const [pj] = await target.need(sfu`package.json`);
const package_json = JSON.parse(await fs.promises.readFile(pj.full, "utf-8"));
return package_json.version;
});
@ -422,15 +422,9 @@ const BuildRawTtf = file.make(
const [fi] = await target.need(FontInfoOf(fn), Version);
const charmap = output.dir + "/" + fn + ".charmap";
await target.need(Scripts, Parameters, de`${output.dir}`);
const otdPath = `${output.dir}/${output.name}.otd`;
await node("font-src/index", { o: otdPath, oCharMap: charmap, ...fi });
await optimizedOtfcc(otdPath, output.full);
await rm(otdPath);
await node("font-src/index", { o: output.full, oCharMap: charmap, ...fi });
}
);
function optimizedOtfcc(from, to) {
return run(OTFCC_BUILD, from, ["-o", `${to}`], ["-O3", "--keep-average-char-width", "-q"]);
}
const BuildTTF = file.make(
(gr, fn) => `${BUILD}/ttf/${gr}/${fn}.ttf`,
@ -557,25 +551,17 @@ const glyfTtc = file.make(
);
async function buildGlyfTtc(target, parts, out) {
const [useFilter, useTtx] = await target.need(
OptimizeWithFilter,
OptimizeWithTtx,
de`${out.dir}`
);
await target.need(de`${out.dir}`);
const [ttfInputs] = await target.need(parts.map(part => BuildTTF(part.dir, part.file)));
const tmpTtc = `${out.dir}/${out.name}.unhinted.ttc`;
const ttfInputPaths = ttfInputs.map(p => p.full);
const optimization = useFilter ? ["--filter-loop", useFilter] : useTtx ? ["--ttx-loop"] : [];
await run(TTCIZE, optimization, ["-o", tmpTtc], ttfInputPaths);
await run(TTCIZE, "-u", ["-o", tmpTtc], ttfInputPaths);
await run("ttfautohint", tmpTtc, out.full);
await rm(tmpTtc);
}
async function buildCompositeTtc(out, inputs) {
await run(
OTF2OTC,
["-o", out.full],
inputs.map(f => f.full)
);
const inputPaths = inputs.map(f => f.full);
await run(TTCIZE, ["-o", out.full], inputPaths);
}
const ExportSuperTtc = file.make(
@ -664,7 +650,7 @@ const GroupArchive = task.group(`archive`, async (target, gid) => {
///////////////////////////////////////////////////////////
const PagesDir = oracle(`pages-dir-path`, async target => {
const pagesDir = path.resolve(__dirname, "../Iosevka-Pages");
const pagesDir = Path.resolve(__dirname, "../Iosevka-Pages");
if (!fs.existsSync(pagesDir)) {
return "";
} else {
@ -686,7 +672,7 @@ const PagesDataExport = task(`pages:data-export`, async target => {
cm.full,
cmi.full,
cmo.full,
path.resolve(pagesDir, "shared/data-import/iosevka.json")
Path.resolve(pagesDir, "shared/data-import/iosevka.json")
);
});
@ -701,7 +687,7 @@ const PagesFontExport = task(`pages:font-export`, async target => {
GroupContents`iosevka-sparkle`
);
for (const dir of dirs) {
await cp(`${DIST}/${dir}`, path.resolve(pagesDir, "shared/font-import", dir));
await cp(`${DIST}/${dir}`, Path.resolve(pagesDir, "shared/font-import", dir));
}
});
@ -710,7 +696,7 @@ const PagesFastFontExport = task(`pages:fast-font-export`, async target => {
if (!pagesDir) return;
const dirs = await target.need(GroupContents`iosevka`);
for (const dir of dirs) {
await cp(`${DIST}/${dir}`, path.resolve(pagesDir, "shared/font-import", dir));
await cp(`${DIST}/${dir}`, Path.resolve(pagesDir, "shared/font-import", dir));
}
});
@ -889,28 +875,31 @@ const ScriptFiles = computed.group("script-files", async (target, ext) => {
});
const JavaScriptFromPtl = computed("scripts-js-from-ptl", async target => {
const [ptl] = await target.need(ScriptFiles("ptl"));
return ptl.map(x => x.replace(/\.ptl$/g, ".js"));
return ptl.map(x => replaceExt(".js", x));
});
function replaceExt(extNew, file) {
return Path.join(Path.dirname(file), Path.basename(file, Path.extname(file)) + extNew);
}
const ScriptJS = file.glob(`font-src/**/*.js`, async (target, path) => {
const [jsFromPtl] = await target.need(JavaScriptFromPtl);
if (jsFromPtl.indexOf(path.full) >= 0) {
const ptl = path.full.replace(/\.js$/g, ".ptl");
if (/\/glyphs\//.test(path.full)) {
await target.need(MARCOS);
}
await target.need(fu`${ptl}`);
await run(PATEL_C, "--strict", ptl, "-o", path.full);
} else {
await target.need(fu`${path.full}`);
const CompiledJs = file.make(
p => p,
async (target, out) => {
const ptl = replaceExt(".ptl", out.full);
if (/\/glyphs\//.test(out.full)) await target.need(MARCOS);
await target.need(sfu(ptl));
await run(PATEL_C, "--strict", ptl, "-o", out.full);
}
});
);
const Scripts = task("scripts", async target => {
await target.need(Parameters);
const [jsFromPtl] = await target.need(JavaScriptFromPtl);
await target.need(jsFromPtl);
const [_jsFromPtl] = await target.need(JavaScriptFromPtl);
const [js] = await target.need(ScriptFiles("js"));
await target.need(js.map(ScriptJS));
const jsFromPtl = new Set(_jsFromPtl);
let subGoals = [];
for (const item of jsFromPtl) subGoals.push(CompiledJs(item));
for (const item of js) if (!jsFromPtl.has(js)) subGoals.push(sfu(item));
await target.need(subGoals);
});
const UtilScripts = task("util-scripts", async target => {
const [files] = await target.need(UtilScriptFiles);