Optimize build speed

This commit is contained in:
be5invis 2023-10-27 18:20:43 -07:00
parent 2ec275667e
commit 9ebfa830f6
15 changed files with 167 additions and 67 deletions

View file

@ -13,9 +13,11 @@ 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
@ -25,12 +27,13 @@ export async function buildFont(argv, para) {
for (let p = start; p <= end; p++) excludeChars.add(p);
}
}
// Finalize (like geometry conversion)
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);
}
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 };

View file

@ -253,7 +253,7 @@ glyph-block AutoBuild-Enclosure : begin
define ItalicSpacing : object
gniPrefix 'i'
getPara : function[pp digits rows width] : begin
define pp1 : pp.reinit : function [a] : begin
define pp1 : pp.createFork : function [a] : begin
set a.shape.slope 'italic'
set a.shape.slopeAngle : mix (para.slopeAngle || 0) 15 (95 / 150)
return : StandardSpacing.getPara pp1 digits rows width
@ -261,7 +261,7 @@ glyph-block AutoBuild-Enclosure : begin
define SansSerifSpacing : object
gniPrefix 'ss'
getPara : function [pp digits rows width] : begin
define pp1 : pp.reinit : function [a] : begin
define pp1 : pp.createFork : function [a] : begin
set a.shape.serifs 'sans'
return : StandardSpacing.getPara pp1 digits rows width

View file

@ -674,7 +674,7 @@ glyph-block Autobuild-Transformed : begin
: where : [createReversed _records] : begin
local { records relSets targetNameMap } : extendRelatedGlyphs 'reversed' _records
local pendingGlyphs : records.map : [record] => record.1
local forkedPara : para.reinit : function p : begin
local forkedPara : para.createFork : function p : begin
set p.shape.slopeAngle (-p.shape.slopeAngle)
local miniatureFont : Fork pendingGlyphs forkedPara
@ -702,7 +702,7 @@ glyph-block Autobuild-Transformed-Mathematical : begin
define [createMathDerivedSeriesImpl groupName tfm _records] : begin
local { records relSets targetNameMap } : extendRelatedGlyphs groupName _records
local pendingGlyphs : records.map : [record] => record.1
local forkedPara : para.reinit tfm
local forkedPara : para.createFork tfm
local forked : Fork pendingGlyphs forkedPara
foreach {unicode glyphid} [items-of records] : if [not : query-glyph targetNameMap.(glyphid)]
create-glyph targetNameMap.(glyphid) unicode : glyph-proc

View file

@ -15,7 +15,7 @@ $$include '../meta/macros.ptl'
export : define [buildGlyphs para recursive] : begin
# Execution and dependency management
local $Exec$ : new GlyphBuildExecutor
local $Exec$ : new GlyphBuildExecutor recursive
define [glyph-is-needed name] : [not recursive] || [recursive.glyphIsNeeded name]
# Initialize glyph store
@ -138,7 +138,7 @@ export : define [buildGlyphs para recursive] : begin
run-glyph-module "./auto-build/transformed.mjs"
run-glyph-module "./auto-build/composite.mjs"
foreach [gb : items-of $Exec$.pendingGlyphBlocks] : gb.resolve
$Exec$.executePendingBlocks
Gr.linkSuffixPairGr glyphStore 'NWID' 'WWID' Gr.Nwid Gr.Wwid
Gr.linkSuffixPairGr glyphStore 'lnum' 'onum' Gr.Lnum Gr.Onum

View file

@ -2,7 +2,7 @@ $$include '../../../meta/macros.ptl'
glyph-module
glyph-block Letter-Cyrillic-Orthography : begin
glyph-block Letter-Latin-Orthography : begin
glyph-block-import Common-Derivatives
# orthographic-italic 'f_i' 0xFB01

View file

@ -6,7 +6,7 @@ import [bitOr] from"../../../support/util/mask-bit.mjs"
glyph-module
glyph-block Letter-Latin-Upper-AE-OE : begin
glyph-block Letter-Latin-Upper-AA-AO : begin
glyph-block-import CommonShapes
glyph-block-import Common-Derivatives
glyph-block-import Letter-Latin-Upper-F : EFVJutLength

View file

@ -5,7 +5,7 @@ import [Joining] from"../../support/gr.mjs"
glyph-module
glyph-block Symbol-Mosaic-NotDef : begin
glyph-block Spaces : begin
glyph-block-import CommonShapes
glyph-block-import Common-Derivatives

View file

@ -5,7 +5,7 @@ import [DesignParameters] from"../../../meta/aesthetics.mjs"
glyph-module
glyph-block Symbol-Geometric-Dice : for-width-kinds WideWidth1
glyph-block Symbol-Geometric-Ballot-Box : for-width-kinds WideWidth1
glyph-block-import CommonShapes
glyph-block-import Common-Derivatives
glyph-block-import Symbol-Geometric-Shared : GeometricDim UnicodeWeightGrade GeometricSizes

View file

@ -5,7 +5,7 @@ import [mix linreg clamp fallback] from"../../../support/utils.mjs"
glyph-module
glyph-block Symbol-Punctuation-ParagraphAndSection : begin
glyph-block Symbol-Punctuation-Pilcrow : begin
glyph-block-import CommonShapes
glyph-block-import Common-Derivatives

View file

@ -5,7 +5,7 @@ import [mix linreg clamp fallback] from"../../../support/utils.mjs"
glyph-module
glyph-block Symbol-Punctuation-ParagraphAndSection : begin
glyph-block Symbol-Punctuation-Section : begin
glyph-block-import CommonShapes
glyph-block-import Common-Derivatives

View file

@ -70,16 +70,16 @@ async function getParameters() {
};
return para;
}
function reinit(argv) {
function paraT(argv) {
const para = createParaImpl(argv);
para.reinit = function (tf) {
para.createFork = function (tf) {
const argv1 = deepClone(argv);
tf(argv1, argv);
return reinit(argv1);
return paraT(argv1);
};
return para;
}
return reinit;
return paraT;
}
async function tryParseToml(str) {
try {

View file

@ -92,11 +92,11 @@ export : define [buildCVSS gsub para glyphStore] : begin
if (variant.tag && variant.rank) : cvGrs.push variant
cvGrs.sort AnyCv.compare
foreach gr [items-of cvGrs] : begin
local cvAlt : [cvs.get gr.tag].createAlternateSubst
foreach {gn glyph} [glyphStore.namedEntries] : if [not : CvDecompose.get glyph] : begin
foreach gr [items-of cvGrs] : begin
local subst : gr.get glyph
if (subst && subst != gn) : begin
local cvAlt : [cvs.get gr.tag].createAlternateSubst
if [not cvAlt.substitutions.(gn)] : set cvAlt.substitutions.(gn) { }
set cvAlt.substitutions.(gn).(gr.rank - 1) : glyphStore.ensureExists subst
@ -114,9 +114,12 @@ export : define [buildCVSS gsub para glyphStore] : begin
foreach gr [items-of ssGrs] : begin
local cvSingle : [cvs.get gr.tag].createSingleSubstFor gr.rank
feature.addLookup cvSingle
foreach {gn glyph} [glyphStore.namedEntries] : if [not : CvDecompose.get glyph] : begin
foreach gr [items-of ssGrs] : begin
local subst : gr.get glyph
if (subst && subst != gn) : begin
local cvSingle : [cvs.get gr.tag].createSingleSubstFor gr.rank
set cvSingle.substitutions.(gn) : glyphStore.ensureExists subst
do "Cleanup and link dependency"

View file

@ -1,35 +1,57 @@
export class GlyphBuildExecutor {
constructor() {
this.currentBlockName = null;
constructor(recursiveBuildFilter) {
this.recursiveBuildFilter = recursiveBuildFilter;
this.currentBlockId = null;
this.dependencyManager = new DependencyManager();
this.pendingGlyphBlocks = [];
this.glyphBlockStore = {};
}
setGlyphToBlockDependency(glyph) {
if (this.currentBlockName) {
this.dependencyManager.glyphToBlock.set(glyph, this.currentBlockName);
if (this.currentBlockId) {
this.dependencyManager.glyphToBlock.set(glyph, this.currentBlockId);
let s = this.dependencyManager.blockToGlyph.get(this.currentBlockId);
if (!s) {
s = new Set();
this.dependencyManager.blockToGlyph.set(this.currentBlockId, s);
}
s.add(glyph);
}
}
defineGlyphBlock(capture, blockName, body) {
const block = new GlyphBlock(capture, this, blockName, body);
this.glyphBlockStore[blockName] = block;
executePendingBlocks() {
// if (!this.recursiveBuildFilter) {
for (const block of this.pendingGlyphBlocks) block.resolve();
// } else {
// for (const block of this.pendingGlyphBlocks)
// if (this.recursiveBuildFilter.blockIsNeeded(block.id)) block.resolve();
// }
}
defineGlyphBlock(capture, id, body) {
const block = new GlyphBlock(capture, this, id, body);
if (this.glyphBlockStore[id]) throw new Error(`Duplicate glyph block: ${id}`);
this.glyphBlockStore[id] = block;
this.pendingGlyphBlocks.push(block);
}
}
export class RecursiveBuildFilter {
constructor(glyphIdFilter) {
constructor(glyphIdFilter, blockIdFilter) {
this.glyphIdFilter = glyphIdFilter;
this.blockIdFilter = blockIdFilter;
}
glyphIsNeeded(id) {
return this.glyphIdFilter.has(id);
}
blockIsNeeded(id) {
return this.blockIdFilter.has(id);
}
}
export class DependencyManager {
constructor() {
this.glyphToGlyph = new WeakMap();
this.glyphToBlock = new WeakMap();
this.blockToGlyph = new Map();
}
addDependency(dependent, dependency) {
let s = this.glyphToGlyph.get(dependent);
@ -39,12 +61,31 @@ export class DependencyManager {
}
s.add(dependency);
}
traverseDependencies(glyphs) {
traverseGlyphDependenciesImpl(glyphs, fBlockwiseExpand) {
let state = new Map();
const PENDING = 1,
CHECKED = 2;
for (const glyph of glyphs) state.set(glyph, PENDING);
// When fBlockwiseExpand is true, we need to expand the initial glyph set
// to include all glyphs in the same block.
if (fBlockwiseExpand) {
let blocks = new Set();
for (const glyph of glyphs) {
let b = this.glyphToBlock.get(glyph);
if (b) blocks.add(b);
}
for (const b of blocks) {
const glyphs = this.blockToGlyph.get(b);
if (glyphs) {
for (const g of glyphs) state.set(g, PENDING);
}
}
}
// Traverse the dependency graph
for (;;) {
let found = false;
for (const [glyph, s] of state) {
@ -59,11 +100,24 @@ export class DependencyManager {
if (!found) break;
}
return state;
}
traverseDependencies(glyphs) {
const gGlyphGraph = this.traverseGlyphDependenciesImpl(glyphs, false);
const gBlockGraph = this.traverseGlyphDependenciesImpl(glyphs, true);
let glyphIdFilter = new Set();
for (const g of state.keys()) {
let blockIdFilter = new Set();
for (const g of gGlyphGraph.keys()) {
if (g.identifier) glyphIdFilter.add(g.identifier);
}
return new RecursiveBuildFilter(glyphIdFilter);
for (const g of gBlockGraph.keys()) {
let b = this.glyphToBlock.get(g);
if (b) blockIdFilter.add(b);
}
return new RecursiveBuildFilter(glyphIdFilter, blockIdFilter);
}
}
@ -71,18 +125,18 @@ export class GlyphBlock {
constructor(capture, execState, blockName, body) {
this.capture = capture;
this.execState = execState;
this.blockName = blockName;
this.id = blockName;
this.body = body;
this.resolved = 0;
this.exports = {};
}
resolve() {
if (this.resolved == 2) return this.exports;
if (this.resolved == 1) throw new Error(`Circular dependency detected: ${this.blockName}`);
if (this.resolved == 1) throw new Error(`Circular dependency detected: ${this.id}`);
this.resolved = 1;
const prevBlockName = this.execState.currentBlockName;
this.execState.currentBlockName = this.blockName;
const prevBlockName = this.execState.currentBlockId;
this.execState.currentBlockId = this.id;
const pendingApplications = [];
const ExportCapture = fnObj => {
@ -95,7 +149,7 @@ export class GlyphBlock {
this.body(this.capture, ExportCapture);
for (const f of pendingApplications) f();
this.execState.currentBlockName = prevBlockName;
this.execState.currentBlockId = prevBlockName;
this.resolved = 2;
return this.exports;
}

View file

@ -112,6 +112,7 @@ async function processSsStyles() {
if (!gr.rank) continue;
md.log(` - \`${gr.tag}\`: Set character variant to “${gr.description}”.`);
}
md.log(` - Other build plans configuration, using \`inherits = "buildPlans.<Plan name>"\`.`);
return md;
}

View file

@ -31,7 +31,7 @@ const DIST_SUPER_TTC = "dist/.super-ttc";
const ARCHIVE_DIR = "release-archives";
const PATEL_C = ["node", "node_modules/patel/bin/patel-c"];
const TTCIZE = ["node", "node_modules/otb-ttc-bundle/bin/otb-ttc-bundle"];
const MAKE_TTC = ["node", "node_modules/otb-ttc-bundle/bin/otb-ttc-bundle"];
const SEVEN_ZIP = process.env.SEVEN_ZIP_PATH || "7z";
const TTFAUTOHINT = process.env.TTFAUTOHINT_PATH || "ttfautohint";
@ -130,13 +130,25 @@ const BuildPlans = computed("metadata:build-plans", async target => {
const [rp] = await target.need(RawPlans);
const rawBuildPlans = rp.buildPlans;
// Initialize build plans
const returnBuildPlans = {};
const fileNameToBpMap = {};
for (const prefix in rawBuildPlans) {
const bp = { ...rawBuildPlans[prefix] };
validateAndShimBuildPlans(prefix, bp, rp.weights, rp.slopes, rp.widths);
if (!bp.family) fail(`Build plan for ${prefix} does not have a family name. Exit.`);
bp.webfontFormats = bp["webfont-formats"] || defaultWebFontFormats;
bp.targets = [];
returnBuildPlans[prefix] = bp;
}
// Resolve WWS, including inheritance and default config
for (const prefix in rawBuildPlans) {
resolveWws(prefix, returnBuildPlans, rp);
}
// Create file name to BP mapping
const fileNameToBpMap = {};
for (const prefix in rawBuildPlans) {
const bp = returnBuildPlans[prefix];
const weights = bp.weights,
slopes = bp.slopes,
widths = bp.widths;
@ -157,31 +169,31 @@ const BuildPlans = computed("metadata:build-plans", async target => {
function linkSpacingDerivableBuildPlans(bps) {
for (const pfxTo in bps) {
const planTo = bps[pfxTo];
const planToVal = rectifyPlanForSpacingDerivation(planTo);
if (blockSpacingDerivation(planTo)) continue;
if (!isLinkDeriveToSpacing(planTo.spacing)) continue;
const bpTo = bps[pfxTo];
if (blockSpacingDerivation(bpTo)) continue;
if (!isDeriveToSpacing(bpTo.spacing)) continue;
for (const pfxFrom in bps) {
const planFrom = bps[pfxFrom];
if (!isLinkDeriveFromSpacing(planFrom.spacing)) continue;
const planFromVal = rectifyPlanForSpacingDerivation(planFrom);
if (!deepEqual(planToVal, planFromVal)) continue;
planTo.spacingDeriveFrom = pfxFrom;
const bpFrom = bps[pfxFrom];
if (!isDeriveFromSpacing(bpFrom.spacing)) continue;
if (!spacingDeriveCompatible(pfxTo, bpTo, pfxFrom, bpFrom)) continue;
bpTo.spacingDeriveFrom = pfxFrom;
}
}
}
function blockSpacingDerivation(bp) {
return !!bp["compatibility-ligatures"];
}
function isLinkDeriveToSpacing(spacing) {
function isDeriveToSpacing(spacing) {
return spacing === "term" || spacing === "fontconfig-mono" || spacing === "fixed";
}
function isLinkDeriveFromSpacing(spacing) {
function isDeriveFromSpacing(spacing) {
return !spacing || spacing === "normal";
}
function rectifyPlanForSpacingDerivation(p) {
function spacingDeriveCompatible(pfxTo, bpTo, pfxFrom, bpFrom) {
// If the two build plans are the same, then they are compatible.
return deepEqual(rectifyPlanForSpacingDerive(bpTo), rectifyPlanForSpacingDerive(bpFrom));
}
function rectifyPlanForSpacingDerive(p) {
return {
...p,
family: "#Validation",
@ -749,7 +761,7 @@ const GlyfTtc = file.make(
async function buildCompositeTtc(out, inputs) {
const inputPaths = inputs.map(f => f.full);
echo.action(echo.hl.command(`Create TTC`), out.full, echo.hl.operator("<-"), inputPaths);
await absolutelySilently.run(TTCIZE, ["-o", out.full], inputPaths);
await absolutelySilently.run(MAKE_TTC, ["-o", out.full], inputPaths);
}
async function buildGlyphSharingTtc(target, parts, out) {
@ -757,7 +769,7 @@ async function buildGlyphSharingTtc(target, parts, out) {
const [ttfInputs] = await target.need(parts.map(part => BuildNoGcTtf(part.dir, part.file)));
const ttfInputPaths = ttfInputs.map(p => p.full);
echo.action(echo.hl.command(`Create TTC`), out.full, echo.hl.operator("<-"), ttfInputPaths);
await silently.run(TTCIZE, "-u", ["-o", out.full], ttfInputPaths);
await silently.run(MAKE_TTC, "-u", ["-o", out.full], ttfInputPaths);
}
///////////////////////////////////////////////////////////
@ -1179,21 +1191,48 @@ const Parameters = task(`meta:parameters`, async target => {
///////////////////////////////////////////////////////////
// Build plan validation
function validateAndShimBuildPlans(prefix, bp, dWeights, dSlopes, dWidths) {
if (!bp.family) {
fail(`Build plan for ${prefix} does not have a family name. Exit.`);
}
function resolveWws(bpName, buildPlans, defaultConfig) {
const bp = buildPlans[bpName];
if (!bp) fail(`Build plan ${bpName} not found.`);
if (!bp.slopes && bp.slants) {
echo.warn(
`Build plan for ${prefix} uses legacy "slants" to define slopes. ` +
fail(
`Build plan for ${bpName} uses legacy "slants" to define slopes. ` +
`Use "slopes" instead.`
);
}
bp.weights = shimBpAspect("weights", bp.weights, dWeights);
bp.slopes = shimBpAspect("slopes", bp.slopes || bp.slants, dSlopes);
bp.widths = shimBpAspect("widths", bp.widths, dWidths);
bp.weights = resolveWwsAspect("weights", bpName, buildPlans, defaultConfig, []);
bp.widths = resolveWwsAspect("widths", bpName, buildPlans, defaultConfig, []);
bp.slopes = resolveWwsAspect("slopes", bpName, buildPlans, defaultConfig, []);
}
function resolveWwsAspect(aspectName, bpName, buildPlans, defaultConfig, deps) {
const bp = buildPlans[bpName];
if (!bp) fail(`Build plan ${bpName} not found.`);
if (bp[aspectName]) {
return shimBpAspect(aspectName, bp[aspectName], defaultConfig[aspectName]);
} else if (bp[`${aspectName}-inherits`]) {
const inheritedPlanName = bp[`${aspectName}-inherits`];
const inheritedPlan = buildPlans[inheritedPlanName];
if (deps.includes(inheritedPlan))
fail(`Circular dependency detected when resolving ${aspectName} of ${bp.family}.`);
const updatedDes = [...deps, bpName];
return resolveWwsAspect(
aspectName,
inheritedPlanName,
buildPlans,
defaultConfig,
updatedDes
);
} else {
return defaultConfig[aspectName];
}
}
function shimBpAspect(aspectName, aspect, defaultAspect) {
if (!aspect) return defaultAspect;
const result = {};