Iosevka/font-src/support/glyph-block.mjs

156 lines
4.1 KiB
JavaScript

export class GlyphBuildExecutor {
constructor(recursiveBuildFilter) {
this.recursiveBuildFilter = recursiveBuildFilter;
this.currentBlockId = null;
this.dependencyManager = new DependencyManager();
this.pendingGlyphBlocks = [];
this.glyphBlockStore = {};
}
setGlyphToBlockDependency(glyph) {
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);
}
}
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, 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);
if (!s) {
s = new Set();
this.glyphToGlyph.set(dependent, s);
}
s.add(dependency);
}
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) {
if (s !== PENDING) continue;
const deps = this.glyphToGlyph.get(glyph);
if (deps) {
for (const g of deps) state.set(g, PENDING);
found = true;
}
state.set(glyph, CHECKED);
}
if (!found) break;
}
return state;
}
traverseDependencies(glyphs) {
const gGlyphGraph = this.traverseGlyphDependenciesImpl(glyphs, false);
const gBlockGraph = this.traverseGlyphDependenciesImpl(glyphs, true);
let glyphIdFilter = new Set();
let blockIdFilter = new Set();
for (const g of gGlyphGraph.keys()) {
if (g.identifier) glyphIdFilter.add(g.identifier);
}
for (const g of gBlockGraph.keys()) {
let b = this.glyphToBlock.get(g);
if (b) blockIdFilter.add(b);
}
return new RecursiveBuildFilter(glyphIdFilter, blockIdFilter);
}
}
export class GlyphBlock {
constructor(capture, execState, blockName, body) {
this.capture = capture;
this.execState = execState;
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.id}`);
this.resolved = 1;
const prevBlockName = this.execState.currentBlockId;
this.execState.currentBlockId = this.id;
const pendingApplications = [];
const ExportCapture = fnObj => {
pendingApplications.push(() => {
for (const [k, v] of Object.entries(fnObj())) {
this.exports[k] = v;
}
});
};
this.body(this.capture, ExportCapture);
for (const f of pendingApplications) f();
this.execState.currentBlockId = prevBlockName;
this.resolved = 2;
return this.exports;
}
}