Code reorg for building
This commit is contained in:
parent
d32599e0e2
commit
d461934be1
16 changed files with 210 additions and 187 deletions
44
packages/font/src/build-font/index.mjs
Normal file
44
packages/font/src/build-font/index.mjs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import { buildGlyphs } from "@iosevka/font-glyphs";
|
||||||
|
import { copyFontMetrics } from "@iosevka/font-glyphs/aesthetics";
|
||||||
|
import { buildOtl } from "@iosevka/font-otl";
|
||||||
|
|
||||||
|
import { cleanupGlyphStore } from "../cleanup/index.mjs";
|
||||||
|
import { CreateEmptyFont } from "../font-io/index.mjs";
|
||||||
|
import { buildCompatLigatures } from "../hb-compat-ligature/index.mjs";
|
||||||
|
import { assignFontNames } from "../naming/index.mjs";
|
||||||
|
import { convertOtd } from "../otd-conv/index.mjs";
|
||||||
|
import { generateTtfaControls } from "../ttfa-controls/index.mjs";
|
||||||
|
import { validateFontConfigMono } from "../validate/metrics.mjs";
|
||||||
|
|
||||||
|
export async function buildFont(para, cache) {
|
||||||
|
const baseFont = CreateEmptyFont(para);
|
||||||
|
assignFontNames(baseFont, para.naming, para.isQuasiProportional);
|
||||||
|
|
||||||
|
// Build glyphs
|
||||||
|
const gs = buildGlyphs(para);
|
||||||
|
copyFontMetrics(gs.fontMetrics, baseFont);
|
||||||
|
|
||||||
|
// Build OTL
|
||||||
|
const otl = buildOtl(para, gs.glyphStore);
|
||||||
|
|
||||||
|
// Regulate (like geometry conversion)
|
||||||
|
const excludeChars = new Set();
|
||||||
|
if (para.excludedCharRanges) {
|
||||||
|
for (const [start, end] of para.excludedCharRanges) {
|
||||||
|
for (let p = start; p <= end; p++) excludeChars.add(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const cleanGs = cleanupGlyphStore(cache, para, gs.glyphStore, excludeChars, otl);
|
||||||
|
|
||||||
|
// Convert to TTF
|
||||||
|
const font = await convertOtd(baseFont, otl, cleanGs);
|
||||||
|
// Build compatibility ligatures
|
||||||
|
if (para.compatibilityLigatures) await buildCompatLigatures(para, font);
|
||||||
|
// Generate ttfaControls
|
||||||
|
const ttfaControls = await generateTtfaControls(cleanGs, font.glyphs);
|
||||||
|
|
||||||
|
// Validation : Metrics
|
||||||
|
if (para.forceMonospace) validateFontConfigMono(font);
|
||||||
|
|
||||||
|
return { font, glyphStore: cleanGs, cacheUpdated: cache && cache.isUpdated(), ttfaControls };
|
||||||
|
}
|
56
packages/font/src/cleanup/glyphs.mjs
Normal file
56
packages/font/src/cleanup/glyphs.mjs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import * as Geom from "@iosevka/geometry";
|
||||||
|
import { Transform } from "@iosevka/geometry/transform";
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
export function finalizeGlyphs(cache, para, glyphStore) {
|
||||||
|
const skew = Math.tan(((para.slopeAngle || 0) / 180) * Math.PI);
|
||||||
|
regulateGlyphStore(cache, skew, glyphStore);
|
||||||
|
return glyphStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
function regulateGlyphStore(cache, skew, glyphStore) {
|
||||||
|
for (const g of glyphStore.glyphs()) {
|
||||||
|
if (!(g.geometry.measureComplexity() & Geom.CPLX_NON_EMPTY)) continue;
|
||||||
|
if (!g.geometry.toReferences()) flattenSimpleGlyph(cache, skew, g);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function flattenSimpleGlyph(cache, skew, g) {
|
||||||
|
// Check if the geometry is already in the cache. If so, use the cached geometry.
|
||||||
|
const ck = Geom.hashGeometry(g.geometry);
|
||||||
|
if (ck && cache) {
|
||||||
|
const cachedGeometry = cache && cache.getGF(ck);
|
||||||
|
if (cachedGeometry) {
|
||||||
|
g.clearGeometry();
|
||||||
|
g.includeContours(cachedGeometry);
|
||||||
|
cache.refreshGF(ck);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform the actual simplification
|
||||||
|
try {
|
||||||
|
let gSimplified;
|
||||||
|
if (skew) {
|
||||||
|
const tfBack = g.gizmo ? g.gizmo.inverse() : new Transform(1, -skew, 0, 1, 0, 0);
|
||||||
|
const tfForward = g.gizmo ? g.gizmo : new Transform(1, +skew, 0, 1, 0, 0);
|
||||||
|
gSimplified = new Geom.TransformedGeometry(
|
||||||
|
tfForward,
|
||||||
|
new Geom.SimplifyGeometry(new Geom.TransformedGeometry(tfBack, g.geometry)),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
gSimplified = new Geom.SimplifyGeometry(g.geometry);
|
||||||
|
}
|
||||||
|
|
||||||
|
const cs = gSimplified.toContours();
|
||||||
|
g.clearGeometry();
|
||||||
|
g.includeContours(cs);
|
||||||
|
if (ck && cache) cache.saveGF(ck, cs);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Detected broken geometry when processing", g._m_identifier);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
25
packages/font/src/cleanup/index.mjs
Normal file
25
packages/font/src/cleanup/index.mjs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { Nwid, Wwid } from "@iosevka/glyph/relation";
|
||||||
|
|
||||||
|
import { gcFont } from "./gc.mjs";
|
||||||
|
import { finalizeGlyphs } from "./glyphs.mjs";
|
||||||
|
|
||||||
|
export function cleanupGlyphStore(cache, para, glyphStore, excludedCodePoints, restFont) {
|
||||||
|
assignGrAndCodeRank(glyphStore, Wwid, Nwid);
|
||||||
|
assignSubRank(glyphStore);
|
||||||
|
glyphStore = gcFont(glyphStore, excludedCodePoints, restFont);
|
||||||
|
glyphStore = finalizeGlyphs(cache, para, glyphStore);
|
||||||
|
return glyphStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
function assignGrAndCodeRank(glyphStore, ...flatteners) {
|
||||||
|
for (const g of glyphStore.glyphs()) {
|
||||||
|
g.codeRank = 0xffffffff;
|
||||||
|
for (const c of glyphStore.flattenCodes(g, flatteners)) if (c < g.codeRank) g.codeRank = c;
|
||||||
|
g.grRank = 0;
|
||||||
|
for (let i = 0; i < flatteners.length; i++) if (flatteners[i].get(g)) g.grRank |= 1 << i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function assignSubRank(glyphStore) {
|
||||||
|
let sr = 0;
|
||||||
|
for (const g of glyphStore.glyphs()) g.subRank = sr++;
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ import { CliProc, Ot } from "ot-builder";
|
||||||
|
|
||||||
import { readTTF, saveTTF } from "./font-io/index.mjs";
|
import { readTTF, saveTTF } from "./font-io/index.mjs";
|
||||||
import { assignFontNames, createNamingDictFromArgv } from "./naming/index.mjs";
|
import { assignFontNames, createNamingDictFromArgv } from "./naming/index.mjs";
|
||||||
|
import { validateFontConfigMono } from "./validate/metrics.mjs";
|
||||||
|
|
||||||
export default main;
|
export default main;
|
||||||
async function main(argv) {
|
async function main(argv) {
|
||||||
|
@ -112,21 +113,3 @@ async function deriveFixed_DropFeatures(font, argv, fFixed) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// In FontConfig, a font is considered "monospace" if and only if all encoded non-combining
|
|
||||||
// characters (AW > 0) have the same width. We use this method to validate whether our
|
|
||||||
// "Fixed" subfamilies are properly built.
|
|
||||||
function validateFontConfigMono(font) {
|
|
||||||
let awSet = new Set();
|
|
||||||
for (const [ch, g] of [...font.cmap.unicode.entries()]) {
|
|
||||||
const aw = g.horizontal.end - g.horizontal.start;
|
|
||||||
if (aw > 0) awSet.add(aw);
|
|
||||||
}
|
|
||||||
for (const [ch, vs, g] of [...font.cmap.vs.entries()]) {
|
|
||||||
const aw = g.horizontal.end - g.horizontal.start;
|
|
||||||
if (aw > 0) awSet.add(aw);
|
|
||||||
}
|
|
||||||
if (awSet.size > 1) {
|
|
||||||
console.error("Fixed variant has wide characters");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
import * as Geom from "@iosevka/geometry";
|
|
||||||
import { Transform } from "@iosevka/geometry/transform";
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
export function finalizeGlyphs(cache, para, glyphStore) {
|
|
||||||
const skew = Math.tan(((para.slopeAngle || 0) / 180) * Math.PI);
|
|
||||||
regulateGlyphStore(cache, skew, glyphStore);
|
|
||||||
return glyphStore;
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
function regulateGlyphStore(cache, skew, glyphStore) {
|
|
||||||
for (const g of glyphStore.glyphs()) {
|
|
||||||
if (!(g.geometry.measureComplexity() & Geom.CPLX_NON_EMPTY)) continue;
|
|
||||||
if (!g.geometry.toReferences()) flattenSimpleGlyph(cache, skew, g);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function flattenSimpleGlyph(cache, skew, g) {
|
|
||||||
const ck = Geom.hashGeometry(g.geometry);
|
|
||||||
const cached = cache.getGF(ck);
|
|
||||||
if (ck && cached) {
|
|
||||||
g.clearGeometry();
|
|
||||||
g.includeContours(cached);
|
|
||||||
cache.refreshGF(ck);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
let gSimplified;
|
|
||||||
if (skew) {
|
|
||||||
const tfBack = g.gizmo ? g.gizmo.inverse() : new Transform(1, -skew, 0, 1, 0, 0);
|
|
||||||
const tfForward = g.gizmo ? g.gizmo : new Transform(1, +skew, 0, 1, 0, 0);
|
|
||||||
gSimplified = new Geom.TransformedGeometry(
|
|
||||||
tfForward,
|
|
||||||
new Geom.SimplifyGeometry(new Geom.TransformedGeometry(tfBack, g.geometry)),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
gSimplified = new Geom.SimplifyGeometry(g.geometry);
|
|
||||||
}
|
|
||||||
|
|
||||||
const cs = gSimplified.toContours();
|
|
||||||
g.clearGeometry();
|
|
||||||
g.includeContours(cs);
|
|
||||||
if (ck) cache.saveGF(ck, cs);
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Detected broken geometry when processing", g._m_identifier);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
import { Nwid, Wwid } from "@iosevka/glyph/relation";
|
|
||||||
|
|
||||||
import { gcFont } from "./gc.mjs";
|
|
||||||
import { finalizeGlyphs } from "./glyphs.mjs";
|
|
||||||
|
|
||||||
export function finalizeFont(cache, para, glyphStore, excludedCodePoints, restFont) {
|
|
||||||
assignGrAndCodeRank(glyphStore, Wwid, Nwid);
|
|
||||||
assignSubRank(glyphStore);
|
|
||||||
glyphStore = gcFont(glyphStore, excludedCodePoints, restFont);
|
|
||||||
glyphStore = finalizeGlyphs(cache, para, glyphStore);
|
|
||||||
validateMonospace(para, glyphStore);
|
|
||||||
return glyphStore;
|
|
||||||
}
|
|
||||||
|
|
||||||
function assignGrAndCodeRank(glyphStore, ...flatteners) {
|
|
||||||
for (const g of glyphStore.glyphs()) {
|
|
||||||
g.codeRank = 0xffffffff;
|
|
||||||
for (const c of glyphStore.flattenCodes(g, flatteners)) if (c < g.codeRank) g.codeRank = c;
|
|
||||||
g.grRank = 0;
|
|
||||||
for (let i = 0; i < flatteners.length; i++) if (flatteners[i].get(g)) g.grRank |= 1 << i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function assignSubRank(glyphStore) {
|
|
||||||
let sr = 0;
|
|
||||||
for (const g of glyphStore.glyphs()) g.subRank = sr++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// In FontConfig, a font is considered "monospace" if and only if all encoded non-combining
|
|
||||||
// characters (AW > 0) have the same width. We use this method to validate whether our
|
|
||||||
// "Fixed" subfamilies are properly built.
|
|
||||||
function validateMonospace(para, glyphStore) {
|
|
||||||
let awSet = new Set();
|
|
||||||
for (const [u, n, g] of glyphStore.encodedEntries()) {
|
|
||||||
const aw = Math.round(g.advanceWidth || 0);
|
|
||||||
if (aw > 0) awSet.add(aw);
|
|
||||||
}
|
|
||||||
if (para.forceMonospace && awSet.size > 1) {
|
|
||||||
throw new Error("Unreachable! Fixed variant has wide characters");
|
|
||||||
}
|
|
||||||
if (!para.isQuasiProportional && !para.compLig && awSet.size > 2) {
|
|
||||||
throw new Error("Unreachable! Building monospace with more than 2 character widths");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,12 +2,12 @@ import fs from "fs";
|
||||||
|
|
||||||
import { FontIo, Ot } from "ot-builder";
|
import { FontIo, Ot } from "ot-builder";
|
||||||
|
|
||||||
export function CreateEmptyFont(argv) {
|
export function CreateEmptyFont(para) {
|
||||||
let font = {
|
let font = {
|
||||||
head: new Ot.Head.Table(),
|
head: new Ot.Head.Table(),
|
||||||
hhea: new Ot.MetricHead.Hhea(),
|
hhea: new Ot.MetricHead.Hhea(),
|
||||||
os2: new Ot.Os2.Table(4),
|
os2: new Ot.Os2.Table(4),
|
||||||
post: new Ot.Post.Table(argv.featureControl.exportGlyphNames ? 2 : 3, 0),
|
post: new Ot.Post.Table(para.exportGlyphNames ? 2 : 3, 0),
|
||||||
maxp: Ot.Maxp.Table.TrueType(),
|
maxp: Ot.Maxp.Table.TrueType(),
|
||||||
name: new Ot.Name.Table(),
|
name: new Ot.Name.Table(),
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
import { buildGlyphs } from "@iosevka/font-glyphs";
|
|
||||||
import { copyFontMetrics } from "@iosevka/font-glyphs/aesthetics";
|
|
||||||
import { buildOtl } from "@iosevka/font-otl";
|
|
||||||
import * as Caching from "@iosevka/geometry-cache";
|
|
||||||
|
|
||||||
import { finalizeFont } from "./finalize/index.mjs";
|
|
||||||
import { CreateEmptyFont } from "./font-io/index.mjs";
|
|
||||||
import { assignFontNames } from "./naming/index.mjs";
|
|
||||||
import { convertOtd } from "./otd-conv/index.mjs";
|
|
||||||
import { generateTtfaControls } from "./ttfa-controls/index.mjs";
|
|
||||||
|
|
||||||
export async function buildFont(argv, para) {
|
|
||||||
const baseFont = CreateEmptyFont(argv);
|
|
||||||
assignFontNames(baseFont, para.naming, para.isQuasiProportional);
|
|
||||||
|
|
||||||
// Build glyphs
|
|
||||||
const gs = buildGlyphs(para);
|
|
||||||
copyFontMetrics(gs.fontMetrics, baseFont);
|
|
||||||
|
|
||||||
// Build OTL
|
|
||||||
const otl = buildOtl(para, gs.glyphStore);
|
|
||||||
|
|
||||||
// Regulate (like geometry conversion)
|
|
||||||
const excludeChars = new Set();
|
|
||||||
if (para.excludedCharRanges) {
|
|
||||||
for (const [start, end] of para.excludedCharRanges) {
|
|
||||||
for (let p = start; p <= end; p++) excludeChars.add(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const cache = await Caching.load(argv.iCache, argv.menu.version, argv.cacheFreshAgeKey);
|
|
||||||
const finalGs = finalizeFont(cache, para, gs.glyphStore, excludeChars, otl);
|
|
||||||
if (cache.isUpdated()) await Caching.save(argv.oCache, argv.menu.version, cache, true);
|
|
||||||
|
|
||||||
// Convert to TTF
|
|
||||||
const font = await convertOtd(baseFont, otl, finalGs);
|
|
||||||
const ttfaControls = await generateTtfaControls(finalGs, font.glyphs);
|
|
||||||
return { font, glyphStore: finalGs, cacheUpdated: cache.isUpdated(), ttfaControls };
|
|
||||||
}
|
|
|
@ -3,12 +3,11 @@ import { Ot } from "ot-builder";
|
||||||
import { buildTTF } from "../font-io/index.mjs";
|
import { buildTTF } from "../font-io/index.mjs";
|
||||||
|
|
||||||
export async function buildCompatLigatures(para, font) {
|
export async function buildCompatLigatures(para, font) {
|
||||||
// We need to fix the glyph order before building the TTF
|
// MappedGlyphStore is append-only, so we do not need to worry about the order of glyphs
|
||||||
const glyphList = font.glyphs.decideOrder();
|
const glyphList = font.glyphs.decideOrder();
|
||||||
const gsFixed = Ot.ListGlyphStoreFactory.createStoreFromList(Array.from(glyphList));
|
|
||||||
font.glyphs = gsFixed;
|
|
||||||
|
|
||||||
const completedCodePoints = new Set();
|
const completedCodePoints = new Set();
|
||||||
|
const jobs = [];
|
||||||
|
|
||||||
// Build a provisional in-memory TTF for shaping
|
// Build a provisional in-memory TTF for shaping
|
||||||
const provisionalTtf = buildTTF(font);
|
const provisionalTtf = buildTTF(font);
|
||||||
|
@ -55,12 +54,17 @@ export async function buildCompatLigatures(para, font) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the ligature glyph
|
// Save the ligature glyph
|
||||||
font.glyphs.items.push(ligature);
|
jobs.push({ name: ligature.name, unicode: entry.unicode, glyph: ligature });
|
||||||
font.cmap.unicode.set(entry.unicode, ligature);
|
|
||||||
if (font.gdef) font.gdef.glyphClassDef.set(ligature, Ot.Gdef.GlyphClass.Ligature);
|
|
||||||
completedCodePoints.add(entry.unicode);
|
completedCodePoints.add(entry.unicode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Commit jobs
|
||||||
|
for (const job of jobs) {
|
||||||
|
font.glyphs.addOtGlyph(job.name, job.glyph);
|
||||||
|
font.cmap.unicode.set(job.unicode, job.glyph);
|
||||||
|
if (font.gdef) font.gdef.glyphClassDef.set(job.glyph, Ot.Gdef.GlyphClass.Ligature);
|
||||||
|
}
|
||||||
|
|
||||||
hbFont.destroy();
|
hbFont.destroy();
|
||||||
hbFace.destroy();
|
hbFace.destroy();
|
||||||
hbBlob.destroy();
|
hbBlob.destroy();
|
||||||
|
|
|
@ -3,6 +3,7 @@ import path from "path";
|
||||||
import zlib from "zlib";
|
import zlib from "zlib";
|
||||||
|
|
||||||
import * as Toml from "@iarna/toml";
|
import * as Toml from "@iarna/toml";
|
||||||
|
import * as Caching from "@iosevka/geometry-cache";
|
||||||
import { createGrDisplaySheet } from "@iosevka/glyph/relation";
|
import { createGrDisplaySheet } from "@iosevka/glyph/relation";
|
||||||
import * as Parameters from "@iosevka/param";
|
import * as Parameters from "@iosevka/param";
|
||||||
import { applyLigationData } from "@iosevka/param/ligation";
|
import { applyLigationData } from "@iosevka/param/ligation";
|
||||||
|
@ -10,33 +11,41 @@ import { applyMetricOverride } from "@iosevka/param/metric-override";
|
||||||
import * as VariantData from "@iosevka/param/variant";
|
import * as VariantData from "@iosevka/param/variant";
|
||||||
import { encode } from "@msgpack/msgpack";
|
import { encode } from "@msgpack/msgpack";
|
||||||
|
|
||||||
|
import { buildFont } from "./build-font/index.mjs";
|
||||||
import { saveTTF } from "./font-io/index.mjs";
|
import { saveTTF } from "./font-io/index.mjs";
|
||||||
import { buildFont } from "./font.mjs";
|
|
||||||
import { buildCompatLigatures } from "./hb-compat-ligature/index.mjs";
|
|
||||||
import { createNamingDictFromArgv } from "./naming/index.mjs";
|
import { createNamingDictFromArgv } from "./naming/index.mjs";
|
||||||
|
|
||||||
export default main;
|
export default main;
|
||||||
async function main(argv) {
|
async function main(argv) {
|
||||||
const paraT = await getParameters(argv);
|
// Set up parameters
|
||||||
|
const paraT = await getParametersT(argv);
|
||||||
const para = paraT(argv);
|
const para = paraT(argv);
|
||||||
const { font, glyphStore, cacheUpdated, ttfaControls } = await buildFont(argv, para);
|
|
||||||
if (argv.oCharMap) {
|
// Set up cache
|
||||||
await saveCharMap(argv, glyphStore);
|
const cache = argv.cache
|
||||||
}
|
? await Caching.load(argv.cache.input, argv.menu.version, argv.cache.freshAgeKey)
|
||||||
if (argv.oTtfaControls) {
|
: null;
|
||||||
await fs.promises.writeFile(argv.oTtfaControls, ttfaControls.join("\n") + "\n");
|
// Build font
|
||||||
}
|
const { font, glyphStore, cacheUpdated, ttfaControls } = await buildFont(para, cache);
|
||||||
if (argv.o) {
|
|
||||||
if (para.compatibilityLigatures) await buildCompatLigatures(para, font);
|
// Save charmap
|
||||||
await saveTTF(argv.o, font);
|
if (argv.oCharMap) await saveCharMap(argv, glyphStore);
|
||||||
|
// Save ttfaControls
|
||||||
|
if (argv.oTtfaControls) await fs.promises.writeFile(argv.oTtfaControls, ttfaControls);
|
||||||
|
// Save TTF
|
||||||
|
if (argv.o) await saveTTF(argv.o, font);
|
||||||
|
// Save cache
|
||||||
|
if (argv.cache && cache && cache.isUpdated()) {
|
||||||
|
await Caching.save(argv.cache.output, argv.menu.version, cache, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { cacheUpdated };
|
return { cacheUpdated };
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// Parameter preparation
|
// Parameter preparation
|
||||||
async function getParameters(argv) {
|
async function getParametersT(argv) {
|
||||||
const PARAMETERS_TOML = path.resolve(argv.paramsDir, "./parameters.toml");
|
const PARAMETERS_TOML = path.resolve(argv.paramsDir, "./parameters.toml");
|
||||||
const WEIGHTS_TOML = path.resolve(argv.paramsDir, "./shape-weight.toml");
|
const WEIGHTS_TOML = path.resolve(argv.paramsDir, "./shape-weight.toml");
|
||||||
const WIDTHS_TOML = path.resolve(argv.paramsDir, "./shape-width.toml");
|
const WIDTHS_TOML = path.resolve(argv.paramsDir, "./shape-width.toml");
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import * as Geom from "@iosevka/geometry";
|
import * as Geom from "@iosevka/geometry";
|
||||||
import { Point } from "@iosevka/geometry/point";
|
import { Point } from "@iosevka/geometry/point";
|
||||||
|
import { Glyph } from "@iosevka/glyph";
|
||||||
import * as Gr from "@iosevka/glyph/relation";
|
import * as Gr from "@iosevka/glyph/relation";
|
||||||
import { Ot } from "ot-builder";
|
import { Ot } from "ot-builder";
|
||||||
|
|
||||||
|
@ -57,6 +58,13 @@ class MappedGlyphStore {
|
||||||
if (!name) return undefined;
|
if (!name) return undefined;
|
||||||
return this.m_nameMapping.get(name);
|
return this.m_nameMapping.get(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add directly from Ot.Glyphs
|
||||||
|
addOtGlyph(name, g) {
|
||||||
|
this.m_nameMapping.set(name, g);
|
||||||
|
this.m_mapping.set(new Glyph(name), g);
|
||||||
|
}
|
||||||
|
|
||||||
decideOrder() {
|
decideOrder() {
|
||||||
const gs = Ot.ListGlyphStoreFactory.createStoreFromList([...this.m_mapping.values()]);
|
const gs = Ot.ListGlyphStoreFactory.createStoreFromList([...this.m_mapping.values()]);
|
||||||
return gs.decideOrder();
|
return gs.decideOrder();
|
||||||
|
|
|
@ -30,7 +30,7 @@ export async function generateTtfaControls(gsOrig, gsTtf) {
|
||||||
alignment.write(ttfaControls, gsTtf);
|
alignment.write(ttfaControls, gsTtf);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ttfaControls;
|
return ttfaControls.join("\n") + "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
class Alignment {
|
class Alignment {
|
||||||
|
|
17
packages/font/src/validate/metrics.mjs
Normal file
17
packages/font/src/validate/metrics.mjs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
// In FontConfig, a font is considered "monospace" if and only if all encoded non-combining
|
||||||
|
// characters (AW > 0) have the same width. We use this method to validate whether our
|
||||||
|
// "Fixed" subfamilies are properly built.
|
||||||
|
export function validateFontConfigMono(font) {
|
||||||
|
let awSet = new Set();
|
||||||
|
for (const [ch, g] of [...font.cmap.unicode.entries()]) {
|
||||||
|
const aw = g.horizontal.end - g.horizontal.start;
|
||||||
|
if (aw > 0) awSet.add(aw);
|
||||||
|
}
|
||||||
|
for (const [ch, vs, g] of [...font.cmap.vs.entries()]) {
|
||||||
|
const aw = g.horizontal.end - g.horizontal.start;
|
||||||
|
if (aw > 0) awSet.add(aw);
|
||||||
|
}
|
||||||
|
if (awSet.size > 1) {
|
||||||
|
console.error("Fixed variant has wide characters");
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ export function init(data, argv) {
|
||||||
if (argv.featureControl.noCvSs) para.enableCvSs = false;
|
if (argv.featureControl.noCvSs) para.enableCvSs = false;
|
||||||
if (argv.featureControl.noLigation) para.enableLigation = false;
|
if (argv.featureControl.noLigation) para.enableLigation = false;
|
||||||
if (argv.featureControl.buildTextureFeature) para.buildTextureFeature = true;
|
if (argv.featureControl.buildTextureFeature) para.buildTextureFeature = true;
|
||||||
|
if (argv.featureControl.exportGlyphNames) para.exportGlyphNames = true;
|
||||||
return para;
|
return para;
|
||||||
}
|
}
|
||||||
function applyBlendingParam(argv, para, data, key, keyArgv) {
|
function applyBlendingParam(argv, para, data, key, keyArgv) {
|
||||||
|
|
|
@ -254,6 +254,8 @@ const FontInfoOf = computed.group("metadata:font-info-of", async (target, fileNa
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const [compositesFromBuildPlan] = await target.need(CompositesFromBuildPlan);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: fileName,
|
name: fileName,
|
||||||
variants: bp.variants || null,
|
variants: bp.variants || null,
|
||||||
|
@ -304,6 +306,9 @@ const FontInfoOf = computed.group("metadata:font-info-of", async (target, fileNa
|
||||||
|
|
||||||
// Spacing derivation -- creating faster build for spacing variants
|
// Spacing derivation -- creating faster build for spacing variants
|
||||||
spacingDerive,
|
spacingDerive,
|
||||||
|
|
||||||
|
// Composite variants from build plan -- used for variant resolution when building fonts
|
||||||
|
compositesFromBuildPlan,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -428,24 +433,27 @@ const DistUnhintedTTF = file.make(
|
||||||
const cachePath = `${SHARED_CACHE}/${cacheFileName}.mpz`;
|
const cachePath = `${SHARED_CACHE}/${cacheFileName}.mpz`;
|
||||||
const cacheDiffPath = `${charMapPath.dir}/${fn}.cache.mpz`;
|
const cacheDiffPath = `${charMapPath.dir}/${fn}.cache.mpz`;
|
||||||
|
|
||||||
const [comps] = await target.need(
|
await target.need(de(charMapPath.dir), de(ttfaControlsPath.dir), de(SHARED_CACHE));
|
||||||
CompositesFromBuildPlan,
|
|
||||||
de(charMapPath.dir),
|
|
||||||
de(ttfaControlsPath.dir),
|
|
||||||
de(SHARED_CACHE),
|
|
||||||
);
|
|
||||||
|
|
||||||
echo.action(echo.hl.command(`Create TTF`), out.full);
|
echo.action(echo.hl.command(`Create TTF`), out.full);
|
||||||
const { cacheUpdated } = await silently.node("packages/font/src/index.mjs", {
|
const { cacheUpdated } = await silently.node("packages/font/src/index.mjs", {
|
||||||
o: out.full,
|
// INPUT: font info
|
||||||
...(fi.buildCharMap ? { oCharMap: charMapPath.full } : {}),
|
|
||||||
paramsDir: Path.resolve("params"),
|
|
||||||
oTtfaControls: ttfaControlsPath.full,
|
|
||||||
cacheFreshAgeKey: ageKey,
|
|
||||||
iCache: cachePath,
|
|
||||||
oCache: cacheDiffPath,
|
|
||||||
compositesFromBuildPlan: comps,
|
|
||||||
...fi,
|
...fi,
|
||||||
|
// INPUT: path to parameters
|
||||||
|
paramsDir: Path.resolve("params"),
|
||||||
|
// TTF output. Optional.
|
||||||
|
o: out.full,
|
||||||
|
// Charmap output. Optional.
|
||||||
|
...(fi.buildCharMap ? { oCharMap: charMapPath.full } : {}),
|
||||||
|
// TTFAutohint controls output. Optional.
|
||||||
|
oTtfaControls: ttfaControlsPath.full,
|
||||||
|
|
||||||
|
// Geometry cache parameters. Optional.
|
||||||
|
cache: {
|
||||||
|
input: cachePath,
|
||||||
|
output: cacheDiffPath,
|
||||||
|
freshAgeKey: ageKey,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (cacheUpdated) {
|
if (cacheUpdated) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue