Optimize the garbage collector to produce smaller files
This commit is contained in:
parent
a3836f8144
commit
80700d9dc8
8 changed files with 322 additions and 70 deletions
|
@ -3,7 +3,7 @@ import zlib from "zlib";
|
||||||
|
|
||||||
import { encode, decode } from "@msgpack/msgpack";
|
import { encode, decode } from "@msgpack/msgpack";
|
||||||
|
|
||||||
const Edition = 28;
|
const Edition = 29;
|
||||||
const MAX_AGE = 16;
|
const MAX_AGE = 16;
|
||||||
class GfEntry {
|
class GfEntry {
|
||||||
constructor(age, value) {
|
constructor(age, value) {
|
||||||
|
|
|
@ -1,22 +1,28 @@
|
||||||
|
import * as Geometry from "../../support/geometry/index.mjs";
|
||||||
|
import { Transform } from "../../support/geometry/transform.mjs";
|
||||||
import { Radical, VS01 } from "../../support/gr.mjs";
|
import { Radical, VS01 } from "../../support/gr.mjs";
|
||||||
|
|
||||||
export function gcFont(glyphStore, excludedChars, otl, cfg) {
|
export function gcFont(glyphStore, excludedChars, otl) {
|
||||||
markSweepOtlLookups(otl.GSUB);
|
const daGsub = markSweepOtlLookups(otl.GSUB);
|
||||||
markSweepOtlLookups(otl.GPOS);
|
markSweepOtlLookups(otl.GPOS);
|
||||||
const sink = markGlyphs(glyphStore, excludedChars, otl, cfg);
|
const sink = markGlyphs(glyphStore, excludedChars, otl, daGsub);
|
||||||
return sweepGlyphs(glyphStore, sink);
|
return sweepGlyphs(glyphStore, sink);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
function markSweepOtlLookups(table) {
|
function markSweepOtlLookups(table) {
|
||||||
if (!table || !table.features || !table.lookups) return;
|
if (!table || !table.features || !table.lookups) return;
|
||||||
const accessibleLookupsIds = new Set();
|
const accessibleLookupsIds = new Set();
|
||||||
markLookups(table, accessibleLookupsIds);
|
const directAccessibleLookupsIds = new Set();
|
||||||
|
markLookups(table, accessibleLookupsIds, directAccessibleLookupsIds);
|
||||||
sweepLookups(table, accessibleLookupsIds);
|
sweepLookups(table, accessibleLookupsIds);
|
||||||
sweepFeatures(table, accessibleLookupsIds);
|
sweepFeatures(table, accessibleLookupsIds);
|
||||||
|
return directAccessibleLookupsIds;
|
||||||
}
|
}
|
||||||
function markLookups(table, sink) {
|
function markLookups(table, sink, sinkDirect) {
|
||||||
if (!table || !table.features) return;
|
if (!table || !table.features) return;
|
||||||
markLookupsStart(table, sink);
|
markLookupsStart(table, sink, sinkDirect);
|
||||||
let loop = 0,
|
let loop = 0,
|
||||||
lookupSetChanged = false;
|
lookupSetChanged = false;
|
||||||
do {
|
do {
|
||||||
|
@ -36,11 +42,14 @@ function markLookups(table, sink) {
|
||||||
lookupSetChanged = sizeBefore !== sink.size;
|
lookupSetChanged = sizeBefore !== sink.size;
|
||||||
} while (loop < 0xff && lookupSetChanged);
|
} while (loop < 0xff && lookupSetChanged);
|
||||||
}
|
}
|
||||||
function markLookupsStart(table, sink) {
|
function markLookupsStart(table, sink, sinkDirect) {
|
||||||
for (let f in table.features) {
|
for (let f in table.features) {
|
||||||
const feature = table.features[f];
|
const feature = table.features[f];
|
||||||
if (!feature) continue;
|
if (!feature) continue;
|
||||||
for (const l of feature) sink.add(l);
|
for (const l of feature) {
|
||||||
|
sink.add(l);
|
||||||
|
sinkDirect.add(l);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function sweepLookups(table, accessibleLookupsIds) {
|
function sweepLookups(table, accessibleLookupsIds) {
|
||||||
|
@ -64,89 +73,128 @@ function sweepFeatures(table, accessibleLookupsIds) {
|
||||||
table.features = features1;
|
table.features = features1;
|
||||||
}
|
}
|
||||||
|
|
||||||
function markGlyphs(glyphStore, excludedChars, otl, cfg) {
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
const sink = markGlyphsInitial(glyphStore, excludedChars);
|
|
||||||
while (markGlyphsStep(glyphStore, sink, otl, cfg));
|
function markGlyphs(glyphStore, excludedChars, otl, daGsub) {
|
||||||
return sink;
|
const markedGlyphs = markGlyphsInitial(glyphStore, excludedChars);
|
||||||
|
while (markGlyphsGr(glyphStore, markedGlyphs, otl));
|
||||||
|
if (otl.GSUB) markGlyphsByGsub(otl.GSUB, markedGlyphs, daGsub);
|
||||||
|
while (markGlyphsGr(glyphStore, markedGlyphs, otl));
|
||||||
|
analyzeReferenceGraph(glyphStore, markedGlyphs);
|
||||||
|
return markedGlyphs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function markSingleGlyph(markedGlyphs, gName, d) {
|
||||||
|
let existing = markedGlyphs.get(gName);
|
||||||
|
if (!existing || d < existing) markedGlyphs.set(gName, d);
|
||||||
|
}
|
||||||
|
|
||||||
function markGlyphsInitial(glyphStore, excludedChars) {
|
function markGlyphsInitial(glyphStore, excludedChars) {
|
||||||
let sink = new Set();
|
let markedGlyphs = new Map();
|
||||||
for (const [gName, g] of glyphStore.namedEntries()) {
|
for (const [gName, g] of glyphStore.namedEntries()) {
|
||||||
if (!g) continue;
|
if (!g) continue;
|
||||||
if (g.glyphRank > 0) sink.add(gName);
|
if (g.glyphRank > 0) markSingleGlyph(markedGlyphs, gName, 1);
|
||||||
if (Radical.get(g)) sink.add(gName);
|
if (Radical.get(g)) markSingleGlyph(markedGlyphs, gName, 1);
|
||||||
const unicodeSet = glyphStore.queryUnicodeOf(g);
|
const unicodeSet = glyphStore.queryUnicodeOf(g);
|
||||||
if (unicodeSet) {
|
if (unicodeSet) {
|
||||||
for (const u of unicodeSet) {
|
for (const u of unicodeSet) {
|
||||||
if (!excludedChars.has(u)) sink.add(gName);
|
if (excludedChars.has(u)) continue;
|
||||||
|
let d = Math.max(1, Math.min(u, 0xffff) >> 4);
|
||||||
|
markSingleGlyph(markedGlyphs, gName, d);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sink;
|
|
||||||
|
return markedGlyphs;
|
||||||
}
|
}
|
||||||
function markGlyphsStep(glyphStore, sink, otl, cfg) {
|
|
||||||
const glyphCount = sink.size;
|
function markGlyphsGr(glyphStore, markedGlyphs, otl) {
|
||||||
|
const glyphCount = markedGlyphs.size;
|
||||||
for (const g of glyphStore.glyphs()) {
|
for (const g of glyphStore.glyphs()) {
|
||||||
markLinkedGlyph(sink, g, VS01);
|
markLinkedGlyph(markedGlyphs, g, VS01);
|
||||||
}
|
}
|
||||||
if (otl.GSUB) {
|
const glyphCount1 = markedGlyphs.size;
|
||||||
for (const l in otl.GSUB.lookups) {
|
|
||||||
const lookup = otl.GSUB.lookups[l];
|
|
||||||
if (!lookup) continue;
|
|
||||||
markGlyphsLookupImpl(sink, lookup, cfg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const glyphCount1 = sink.size;
|
|
||||||
return glyphCount1 > glyphCount;
|
return glyphCount1 > glyphCount;
|
||||||
}
|
}
|
||||||
function markLinkedGlyph(sink, g, gr) {
|
function markLinkedGlyph(markedGlyphs, g, gr) {
|
||||||
const linked = gr.get(g);
|
const linked = gr.get(g);
|
||||||
if (linked) sink.add(linked);
|
const d = markedGlyphs.get(g);
|
||||||
|
if (d && linked) markSingleGlyph(markedGlyphs, linked, d + 0x1000);
|
||||||
}
|
}
|
||||||
function markGlyphsLookupImpl(sink, lookup, cfg) {
|
|
||||||
|
function markGlyphsByGsub(gsub, markedGlyphs, daGsub) {
|
||||||
|
for (const lid of gsub.lookupOrder) {
|
||||||
|
if (!daGsub.has(lid)) continue;
|
||||||
|
markGlyphsByLookup(gsub, lid, markedGlyphs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function markGlyphsByLookup(gsub, lid, markedGlyphs) {
|
||||||
|
const lookup = gsub.lookups[lid];
|
||||||
|
if (!lookup) return;
|
||||||
switch (lookup.type) {
|
switch (lookup.type) {
|
||||||
case "gsub_single":
|
case "gsub_single":
|
||||||
return markGlyphsGsubSingle(sink, lookup, cfg);
|
return markGlyphsGsubSingle(markedGlyphs, lookup);
|
||||||
case "gsub_multiple":
|
case "gsub_multiple":
|
||||||
return markGlyphsGsubMultiple(sink, lookup, cfg);
|
return markGlyphsGsubMultiple(markedGlyphs, lookup);
|
||||||
case "gsub_alternate":
|
case "gsub_alternate":
|
||||||
return markGlyphsGsubAlternate(sink, lookup, cfg);
|
return markGlyphsGsubAlternate(markedGlyphs, lookup);
|
||||||
case "gsub_ligature":
|
case "gsub_ligature":
|
||||||
return markGlyphsGsubLigature(sink, lookup, cfg);
|
return markGlyphsGsubLigature(markedGlyphs, lookup);
|
||||||
case "gsub_chaining":
|
case "gsub_chaining": {
|
||||||
|
rules: for (const rule of lookup.rules) {
|
||||||
|
// Check whether all match coverages has at least one glyph in the sink
|
||||||
|
for (const m of rule.match) {
|
||||||
|
let atLeastOneMatch = false;
|
||||||
|
for (const matchGlyph of m)
|
||||||
|
if (markedGlyphs.has(matchGlyph)) atLeastOneMatch = true;
|
||||||
|
if (!atLeastOneMatch) continue rules;
|
||||||
|
}
|
||||||
|
// If so traverse through the lookup applications
|
||||||
|
for (const app of rule.apply) markGlyphsByLookup(gsub, app.lookup, markedGlyphs);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case "gsub_reverse":
|
case "gsub_reverse":
|
||||||
return markGlyphsGsubReverse(sink, lookup, cfg);
|
return markGlyphsGsubReverse(markedGlyphs, lookup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function markGlyphsGsubSingle(sink, lookup, cfg) {
|
|
||||||
|
function markGlyphsGsubSingle(markedGlyphs, lookup) {
|
||||||
const st = lookup.substitutions;
|
const st = lookup.substitutions;
|
||||||
for (const k in st) if (sink.has(k) && st[k]) sink.add(st[k]);
|
for (const k in st) {
|
||||||
|
const d = markedGlyphs.get(k);
|
||||||
|
if (d && st[k]) markSingleGlyph(markedGlyphs, st[k], d + 0x1000);
|
||||||
}
|
}
|
||||||
function markGlyphsGsubMultiple(sink, lookup, cfg) {
|
}
|
||||||
|
function markGlyphsGsubMultiple(markedGlyphs, lookup) {
|
||||||
const st = lookup.substitutions;
|
const st = lookup.substitutions;
|
||||||
for (const k in st) if (sink.has(k) && st[k]) for (const g of st[k]) sink.add(g);
|
for (const k in st) {
|
||||||
}
|
const d = markedGlyphs.get(k);
|
||||||
function markGlyphsGsubAlternate(sink, lookup, cfg) {
|
if (d && st[k]) for (const g of st[k]) markSingleGlyph(markedGlyphs, g, d + 0x1000);
|
||||||
const st = lookup.substitutions;
|
|
||||||
if (!cfg || !cfg.ignoreAltSub) {
|
|
||||||
for (const k in st) if (sink.has(k) && st[k]) for (const g of st[k]) sink.add(g);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function markGlyphsGsubLigature(sink, lookup, cfg) {
|
function markGlyphsGsubAlternate(markedGlyphs, lookup) {
|
||||||
|
markGlyphsGsubMultiple(markedGlyphs, lookup);
|
||||||
|
}
|
||||||
|
function markGlyphsGsubLigature(markedGlyphs, lookup) {
|
||||||
const st = lookup.substitutions;
|
const st = lookup.substitutions;
|
||||||
for (const sub of st) {
|
for (const sub of st) {
|
||||||
let check = true;
|
let maxD = 0;
|
||||||
for (const g of sub.from) if (!sink.has(g)) check = false;
|
for (const g of sub.from) {
|
||||||
if (check && sub.to) sink.add(sub.to);
|
const d = markedGlyphs.get(g);
|
||||||
|
if (d && d > maxD) maxD = d;
|
||||||
|
}
|
||||||
|
if (maxD && sub.to) markSingleGlyph(markedGlyphs, sub.to, maxD + 0x1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function markGlyphsGsubReverse(sink, lookup, cfg) {
|
function markGlyphsGsubReverse(markedGlyphs, lookup) {
|
||||||
for (const rule of lookup.rules) {
|
for (const rule of lookup.rules) {
|
||||||
if (rule.match && rule.to) {
|
if (rule.match && rule.to) {
|
||||||
const matchCoverage = rule.match[rule.inputIndex];
|
const matchCoverage = rule.match[rule.inputIndex];
|
||||||
for (let j = 0; j < matchCoverage.length; j++) {
|
for (let j = 0; j < matchCoverage.length; j++) {
|
||||||
if (sink.has(matchCoverage[j]) && rule.to[j]) sink.add(rule.to[j]);
|
const d = markedGlyphs.get(matchCoverage[j]);
|
||||||
|
if (d && rule.to[j]) markSingleGlyph(markedGlyphs, rule.to[j], d + 0x1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -155,3 +203,181 @@ function markGlyphsGsubReverse(sink, lookup, cfg) {
|
||||||
function sweepGlyphs(glyphStore, gnSet) {
|
function sweepGlyphs(glyphStore, gnSet) {
|
||||||
return glyphStore.filterByName(gnSet);
|
return glyphStore.filterByName(gnSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
function analyzeReferenceGraph(glyphStore, markedGlyphs) {
|
||||||
|
let depthMap = new Map();
|
||||||
|
let aliasMap = new Map();
|
||||||
|
|
||||||
|
for (const [gn, g] of glyphStore.namedEntries()) {
|
||||||
|
const d = markedGlyphs.get(gn);
|
||||||
|
if (d) traverseReferenceTree(depthMap, aliasMap, g, d);
|
||||||
|
}
|
||||||
|
aliasMap = optimizeAliasMap(aliasMap, depthMap);
|
||||||
|
|
||||||
|
let memo = new Set();
|
||||||
|
for (const [gn, g] of glyphStore.namedEntries()) {
|
||||||
|
const d = markedGlyphs.get(gn);
|
||||||
|
if (d) rectifyGlyphAndMarkComponents(glyphStore, aliasMap, markedGlyphs, memo, g, d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traverse the glyphs' reference tree and mark the depth of each glyph.
|
||||||
|
// For aliases (a glyphs which only contains a single reference), mark the aliasing relationship.
|
||||||
|
function traverseReferenceTree(depthMap, aliasMap, g, d) {
|
||||||
|
depthMapSet(depthMap, g, d);
|
||||||
|
|
||||||
|
let refs = g.geometry.asReferences();
|
||||||
|
if (!refs) return;
|
||||||
|
|
||||||
|
for (const sr of refs) {
|
||||||
|
traverseReferenceTree(depthMap, aliasMap, sr.glyph, d + 0x10000);
|
||||||
|
}
|
||||||
|
if (refs.length === 1) {
|
||||||
|
const sr = refs[0];
|
||||||
|
aliasMap.set(g, sr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function depthMapSet(depthMap, g, d) {
|
||||||
|
let existing = depthMap.get(g);
|
||||||
|
if (null == existing || d < existing) {
|
||||||
|
depthMap.set(g, d);
|
||||||
|
return d;
|
||||||
|
} else {
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optimize the alias map by altering the geometry of glyphs to reference the "representative glyph",
|
||||||
|
// which is the glyph with the smallest depth in the cluster of glyphs that aliased to each other.
|
||||||
|
function optimizeAliasMap(aliasMap, depthMap) {
|
||||||
|
let collection = collectAliasMap(aliasMap);
|
||||||
|
resolveCollectionRepresentative(collection, depthMap);
|
||||||
|
return alterGeometryAndOptimize(collection);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect all glyphs into clusters, grouped by the terminal glyph of alias chains.
|
||||||
|
// Each cluster will contain all the the glyphs that are aliases of the terminal glyph.
|
||||||
|
function collectAliasMap(aliasMap) {
|
||||||
|
let aliasResolution = new Map();
|
||||||
|
for (const g of aliasMap.keys()) {
|
||||||
|
const terminal = getAliasTerminal(aliasMap, g);
|
||||||
|
let m = aliasResolution.get(terminal.glyph);
|
||||||
|
if (!m) {
|
||||||
|
m = {
|
||||||
|
representative: null,
|
||||||
|
aliases: new Map()
|
||||||
|
};
|
||||||
|
aliasResolution.set(terminal.glyph, m);
|
||||||
|
}
|
||||||
|
m.aliases.set(g, { x: terminal.x, y: terminal.y });
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [gT, cluster] of aliasResolution) cluster.aliases.set(gT, { x: 0, y: 0 });
|
||||||
|
return aliasResolution;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve the representative glyph of each cluster, using the glyph with the smallest depth.
|
||||||
|
function resolveCollectionRepresentative(collection, depthMap) {
|
||||||
|
for (const [gT, cluster] of collection) {
|
||||||
|
let d = null;
|
||||||
|
for (const [g, tf] of cluster.aliases) {
|
||||||
|
const dt = depthMap.get(g);
|
||||||
|
if ((d == null && dt != null) || (d != null && dt != null && dt < d)) {
|
||||||
|
d = dt;
|
||||||
|
cluster.representative = { glyph: g, x: tf.x, y: tf.y };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the collected alias map to alter the geometry of glyphs and produce the optimized alias map.
|
||||||
|
// The geometry of each glyph will be altered to reference the representative glyph of its cluster,
|
||||||
|
// while the representative itself's geometry will be the terminal glyph's geometry with translation.
|
||||||
|
function alterGeometryAndOptimize(collection) {
|
||||||
|
let optimized = new Map();
|
||||||
|
for (const [gT, cluster] of collection) {
|
||||||
|
if (!cluster.representative) {
|
||||||
|
throw new Error("Unreachable: each cluster should have at least one representative");
|
||||||
|
}
|
||||||
|
|
||||||
|
cluster.representative.glyph.geometry = new Geometry.TransformedGeometry(
|
||||||
|
gT.geometry,
|
||||||
|
Transform.Translate(cluster.representative.x, cluster.representative.y)
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const [g, tf] of cluster.aliases) {
|
||||||
|
if (g != cluster.representative.glyph) {
|
||||||
|
g.geometry = new Geometry.ReferenceGeometry(
|
||||||
|
cluster.representative.glyph,
|
||||||
|
tf.x - cluster.representative.x,
|
||||||
|
tf.y - cluster.representative.y
|
||||||
|
);
|
||||||
|
optimized.set(g, {
|
||||||
|
glyph: cluster.representative.glyph,
|
||||||
|
x: tf.x - cluster.representative.x,
|
||||||
|
y: tf.y - cluster.representative.y
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return optimized;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAliasTerminal(aliasMap, g) {
|
||||||
|
let x = 0,
|
||||||
|
y = 0;
|
||||||
|
for (;;) {
|
||||||
|
const alias = aliasMap.get(g);
|
||||||
|
if (!alias) {
|
||||||
|
return { glyph: g, x, y };
|
||||||
|
} else {
|
||||||
|
x += alias.x;
|
||||||
|
y += alias.y;
|
||||||
|
g = alias.glyph;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function rectifyGlyphAndMarkComponents(glyphStore, aliasMap, markedGlyphs, memo, g, d) {
|
||||||
|
if (memo.has(g)) return;
|
||||||
|
memo.add(g);
|
||||||
|
|
||||||
|
let refs = g.geometry.asReferences();
|
||||||
|
if (refs) {
|
||||||
|
let parts = [];
|
||||||
|
for (let sr of refs) {
|
||||||
|
// Resolve alias
|
||||||
|
const alias = aliasMap.get(sr.glyph);
|
||||||
|
if (alias) {
|
||||||
|
sr.glyph = alias.glyph;
|
||||||
|
sr.x += alias.x;
|
||||||
|
sr.y += alias.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
const gn = glyphStore.queryNameOf(sr.glyph);
|
||||||
|
if (!gn) {
|
||||||
|
// Reference is invalid. The root glyph will be radicalized.
|
||||||
|
g.geometry = new Geometry.RadicalGeometry(g.geometry.unlinkReferences());
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// Reference is valid. Process the referenced glyph.
|
||||||
|
if (!markedGlyphs.has(gn)) markedGlyphs.set(gn, d + 0x10000);
|
||||||
|
rectifyGlyphAndMarkComponents(
|
||||||
|
glyphStore,
|
||||||
|
aliasMap,
|
||||||
|
markedGlyphs,
|
||||||
|
memo,
|
||||||
|
sr.glyph,
|
||||||
|
d + 0x10000
|
||||||
|
);
|
||||||
|
parts.push(new Geometry.ReferenceGeometry(sr.glyph, sr.x, sr.y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g.geometry = new Geometry.CombineGeometry(parts);
|
||||||
|
} else {
|
||||||
|
g.geometry = new Geometry.RadicalGeometry(g.geometry.unlinkReferences());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -41,17 +41,6 @@ function regulateCompositeGlyph(glyphStore, memo, g) {
|
||||||
if (!gn) return memoSet(memo, g, false);
|
if (!gn) return memoSet(memo, g, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// De-doppelganger
|
|
||||||
while (refs.length === 1 && regulateCompositeGlyph(glyphStore, memo, refs[0].glyph)) {
|
|
||||||
const sr = refs[0];
|
|
||||||
const targetRefs = sr.glyph.geometry.asReferences();
|
|
||||||
g.clearGeometry();
|
|
||||||
for (const tr of targetRefs) {
|
|
||||||
g.includeGeometry(new Geom.ReferenceGeometry(tr.glyph, tr.x + sr.x, tr.y + sr.y));
|
|
||||||
}
|
|
||||||
refs = g.geometry.asReferences();
|
|
||||||
}
|
|
||||||
|
|
||||||
return memoSet(memo, g, true);
|
return memoSet(memo, g, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ function validateMonospace(para, glyphStore) {
|
||||||
export function finalizeFont(cache, para, glyphStore, excludedCodePoints, restFont) {
|
export function finalizeFont(cache, para, glyphStore, excludedCodePoints, restFont) {
|
||||||
assignGrAndCodeRank(glyphStore, Nwid, Wwid);
|
assignGrAndCodeRank(glyphStore, Nwid, Wwid);
|
||||||
assignSubRank(glyphStore);
|
assignSubRank(glyphStore);
|
||||||
glyphStore = gcFont(glyphStore, excludedCodePoints, restFont, {});
|
glyphStore = gcFont(glyphStore, excludedCodePoints, restFont);
|
||||||
glyphStore = finalizeGlyphs(cache, para, glyphStore);
|
glyphStore = finalizeGlyphs(cache, para, glyphStore);
|
||||||
validateMonospace(para, glyphStore);
|
validateMonospace(para, glyphStore);
|
||||||
return glyphStore;
|
return glyphStore;
|
||||||
|
|
|
@ -47,8 +47,7 @@ function ConvertGsubGposImpl(handlers, T, table, glyphs) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const l in table.lookups) {
|
for (const l in table.lookups) {
|
||||||
if (!table.lookups[l]) throw new Error(`Cannot find lookup '${l}'`);
|
if (!ls.query(l)) throw new Error("Unreachable: lookupOrder must contain everything");
|
||||||
ls.declare(l, table.lookups[l]);
|
|
||||||
}
|
}
|
||||||
for (const l in table.lookups) ls.fill(l, table.lookups[l]);
|
for (const l in table.lookups) ls.fill(l, table.lookups[l]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
$$include '../../../meta/macros.ptl'
|
$$include '../../../meta/macros.ptl'
|
||||||
|
|
||||||
import [mix linreg clamp fallback] from"../../../support/utils.mjs"
|
import [mix linreg clamp fallback] from"../../../support/utils.mjs"
|
||||||
import [DependentSelector CvDecompose MathSansSerif] from"../../../support/gr.mjs"
|
import [DependentSelector MathSansSerif] from"../../../support/gr.mjs"
|
||||||
|
|
||||||
glyph-module
|
glyph-module
|
||||||
|
|
||||||
|
|
|
@ -319,6 +319,38 @@ export class TransformedGeometry extends GeometryBase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class RadicalGeometry extends GeometryBase {
|
||||||
|
constructor(g) {
|
||||||
|
super();
|
||||||
|
this.m_geom = g;
|
||||||
|
}
|
||||||
|
asContours() {
|
||||||
|
return this.m_geom.asContours();
|
||||||
|
}
|
||||||
|
asReferences() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
filterTag(fn) {
|
||||||
|
const e = this.m_geom.filterTag(fn);
|
||||||
|
if (!e) return null;
|
||||||
|
return new RadicalGeometry(e);
|
||||||
|
}
|
||||||
|
isEmpty() {
|
||||||
|
return this.m_geom.isEmpty();
|
||||||
|
}
|
||||||
|
measureComplexity() {
|
||||||
|
return this.m_geom.measureComplexity();
|
||||||
|
}
|
||||||
|
unlinkReferences() {
|
||||||
|
return this.m_geom.unlinkReferences();
|
||||||
|
}
|
||||||
|
toShapeStringOrNull() {
|
||||||
|
const sTarget = this.m_geom.toShapeStringOrNull();
|
||||||
|
if (!sTarget) return null;
|
||||||
|
return Format.struct("RadicalGeometry", sTarget);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class CombineGeometry extends GeometryBase {
|
export class CombineGeometry extends GeometryBase {
|
||||||
constructor(parts) {
|
constructor(parts) {
|
||||||
super();
|
super();
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import * as util from "util";
|
||||||
|
|
||||||
import { Anchor } from "../geometry/anchor.mjs";
|
import { Anchor } from "../geometry/anchor.mjs";
|
||||||
import * as Geom from "../geometry/index.mjs";
|
import * as Geom from "../geometry/index.mjs";
|
||||||
import { Point, Vec2 } from "../geometry/point.mjs";
|
import { Point, Vec2 } from "../geometry/point.mjs";
|
||||||
|
@ -23,6 +25,10 @@ export class Glyph {
|
||||||
this.ctxTag = null;
|
this.ctxTag = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[util.inspect.custom](depth, options) {
|
||||||
|
return options.stylize(this.toString(), "special");
|
||||||
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
return `<Glyph ${this._m_identifier}>`;
|
return `<Glyph ${this._m_identifier}>`;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue