mirror of
https://github.com/kiliman/operator-mono-lig.git
synced 2026-08-12 02:10:52 +02:00
🔥 Delete gsub.js and replace with calt feature
This commit is contained in:
@@ -1,313 +0,0 @@
|
||||
const xpath = require('xpath');
|
||||
const NodeType = {};
|
||||
NodeType.TEXT_NODE = 3;
|
||||
|
||||
let dom;
|
||||
let gsubDom;
|
||||
let scriptListDom;
|
||||
let featureListDom;
|
||||
let lookupListDom;
|
||||
let featureDom;
|
||||
let lookupIndex = 0;
|
||||
let chainIndex = 0;
|
||||
const substLookupMap = {};
|
||||
|
||||
const initGsubTables = (_dom, options) => {
|
||||
dom = _dom;
|
||||
|
||||
gsubDom = xpath.select('ttFont/GSUB', dom, true);
|
||||
|
||||
// look for 'calt' feature
|
||||
featureListDom = xpath.select('FeatureList', gsubDom, true);
|
||||
lookupListDom = xpath.select('LookupList', gsubDom, true);
|
||||
|
||||
if (options.italicsHack) {
|
||||
// remove all features from FeatureList
|
||||
Array.from(featureListDom.childNodes).forEach((c) => {
|
||||
featureListDom.removeChild(c);
|
||||
});
|
||||
|
||||
Array.from(xpath.select('ttFont/GSUB//FeatureIndex', dom)).forEach((c) => {
|
||||
c.parentNode.removeChild(c);
|
||||
});
|
||||
|
||||
// remove all lookups from LookupList
|
||||
Array.from(lookupListDom.childNodes).forEach((c) =>
|
||||
lookupListDom.removeChild(c)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const buildGsubTables = (_dom, ligature) => {
|
||||
dom = _dom;
|
||||
|
||||
if (/_/.test(ligature.glyph) === false) {
|
||||
// not a ligature, so see if glyph exists in font (as substitute)
|
||||
const glyphNode = xpath.select(
|
||||
`ttFont/GlyphOrder/GlyphID[@name="${ligature.name}"]`,
|
||||
dom,
|
||||
true
|
||||
);
|
||||
if (!glyphNode) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const glyphs = ligature.name.replace(/\.liga$/, '').split('_');
|
||||
const isSingleGlyph = glyphs.length == 1;
|
||||
|
||||
gsubDom = xpath.select('ttFont/GSUB', dom, true);
|
||||
|
||||
// look for 'calt' feature
|
||||
featureListDom = xpath.select('FeatureList', gsubDom, true);
|
||||
|
||||
let featureRecord;
|
||||
const featureTag = xpath.select(
|
||||
'FeatureRecord/FeatureTag[@value="calt"]',
|
||||
featureListDom,
|
||||
true
|
||||
);
|
||||
if (!featureTag) {
|
||||
const featureListCount = xpath.select(
|
||||
'count(FeatureRecord)',
|
||||
featureListDom,
|
||||
true
|
||||
);
|
||||
featureRecord = createElementWithAttributes('FeatureRecord', {
|
||||
index: featureListCount,
|
||||
});
|
||||
featureRecord.appendChild(
|
||||
createElementWithAttributes('FeatureTag', { value: 'calt' })
|
||||
);
|
||||
featureRecord.appendChild(dom.createElement('Feature'));
|
||||
featureListDom.appendChild(featureRecord);
|
||||
} else {
|
||||
featureRecord = featureTag.parentNode;
|
||||
}
|
||||
featureDom = xpath.select('Feature', featureRecord, true);
|
||||
|
||||
const featureIndex = featureRecord.getAttribute('index');
|
||||
|
||||
// add feature to scriptlist for DFLT and latn if not present
|
||||
addFeatureToScriptList('DFLT', featureIndex);
|
||||
addFeatureToScriptList('latn', featureIndex);
|
||||
|
||||
lookupIndex = xpath.select('count(Lookup)', lookupListDom, true);
|
||||
|
||||
const lookupDom = createElementWithAttributes('Lookup', {
|
||||
index: lookupIndex++,
|
||||
});
|
||||
appendChildren(
|
||||
lookupDom,
|
||||
createElementWithAttributes('LookupType', { value: isSingleGlyph ? 1 : 6 }),
|
||||
createElementWithAttributes('LookupFlag', { value: 0 })
|
||||
);
|
||||
|
||||
// add contextual lookup to feature
|
||||
const featureLookupCount = xpath.select(
|
||||
'count(LookupListIndex)',
|
||||
featureDom,
|
||||
true
|
||||
);
|
||||
featureDom.appendChild(
|
||||
createElementWithAttributes('LookupListIndex', {
|
||||
index: featureLookupCount,
|
||||
value: lookupDom.getAttribute('index'),
|
||||
})
|
||||
);
|
||||
|
||||
if (isSingleGlyph) {
|
||||
lookupDom.appendChild(
|
||||
getSubstLookup(ligature.name, ligature.glyph, lookupDom)
|
||||
);
|
||||
} else {
|
||||
lookupDom.appendChild(buildBacktrackFirst(glyphs));
|
||||
lookupDom.appendChild(buildLookAheadLast(glyphs));
|
||||
lookupDom.appendChild(buildLigatureSubst(glyphs, ligature.glyph));
|
||||
// remaining context
|
||||
for (let n = glyphs.length - 2; n >= 0; n--) {
|
||||
lookupDom.appendChild(
|
||||
buildChainContext(getLIGs(n), [glyphs[n]], glyphs.slice(n + 1), 'LIG')
|
||||
);
|
||||
}
|
||||
}
|
||||
lookupListDom.appendChild(lookupDom);
|
||||
};
|
||||
|
||||
const finalizeGsubTables = () => {
|
||||
// get current LookupList count
|
||||
let newIndex = xpath.select('count(Lookup)', lookupListDom, true);
|
||||
|
||||
// append added lookups
|
||||
Object.entries(substLookupMap).forEach(([, lookup]) => {
|
||||
// fixup existing lookups with new index
|
||||
const oldIndex = lookup.getAttribute('index');
|
||||
const substLookups = xpath.select(
|
||||
`//ChainContextSubst/SubstLookupRecord/LookupListIndex[@value="${oldIndex}"]`,
|
||||
lookupListDom,
|
||||
false
|
||||
);
|
||||
substLookups.forEach((substLookup) => {
|
||||
substLookup.setAttribute('value', newIndex);
|
||||
});
|
||||
lookup.setAttribute('index', newIndex++);
|
||||
lookupListDom.appendChild(lookup);
|
||||
});
|
||||
};
|
||||
|
||||
//const dump = dom => console.log(format(serialize(dom)));
|
||||
|
||||
const addFeatureToScriptList = (tag, featureIndex) => {
|
||||
scriptListDom = xpath.select('ScriptList', gsubDom, true);
|
||||
const defaultLangSys = xpath.select(
|
||||
`ScriptRecord/ScriptTag[@value="${tag}"]/../Script/DefaultLangSys`,
|
||||
scriptListDom,
|
||||
true
|
||||
);
|
||||
const featureIndexNode = xpath.select(
|
||||
`FeatureIndex[@value=${featureIndex}]`,
|
||||
defaultLangSys,
|
||||
true
|
||||
);
|
||||
if (!featureIndexNode) {
|
||||
const count = xpath.select('count(FeatureIndex)', defaultLangSys, true);
|
||||
defaultLangSys.appendChild(
|
||||
createElementWithAttributes('FeatureIndex', {
|
||||
index: count,
|
||||
value: featureIndex,
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const buildBacktrackFirst = (glyphs) => {
|
||||
// backtrack = first glyph
|
||||
// input = first glphy
|
||||
// lookAhead = [1..n]
|
||||
return buildChainContext([glyphs[0]], [glyphs[0]], glyphs.slice(1));
|
||||
};
|
||||
|
||||
const buildLookAheadLast = (glyphs) => {
|
||||
// backtrack = none
|
||||
// input = first glyph
|
||||
// lookAhead = [1..n] + [n]
|
||||
return buildChainContext(
|
||||
[],
|
||||
[glyphs[0]],
|
||||
[...glyphs.slice(1), glyphs.slice(-1)]
|
||||
);
|
||||
};
|
||||
|
||||
const buildLigatureSubst = (glyphs, lookup) => {
|
||||
// backtrack = LIG * n-1
|
||||
// input = last glyph
|
||||
// lookAhead = none
|
||||
return buildChainContext(
|
||||
getLIGs(glyphs.length - 1),
|
||||
glyphs.slice(-1),
|
||||
[],
|
||||
lookup
|
||||
);
|
||||
};
|
||||
|
||||
const getLIGs = (length) => new Array(length).fill('LIG', 0, length);
|
||||
|
||||
const buildChainContext = (backtrack, input, lookAhead, substitute) => {
|
||||
const chainDom = createElementWithAttributes('ChainContextSubst', {
|
||||
index: chainIndex++,
|
||||
Format: 3,
|
||||
});
|
||||
|
||||
buildCoverage(chainDom, 'BacktrackCoverage', backtrack);
|
||||
buildCoverage(chainDom, 'InputCoverage', input);
|
||||
buildCoverage(chainDom, 'LookAheadCoverage', lookAhead);
|
||||
|
||||
if (substitute) {
|
||||
const lookup = getSubstLookup(input[0], substitute);
|
||||
const listIndex = lookup.getAttribute('index');
|
||||
const substLookupRecordDom = chainDom.appendChild(
|
||||
createElementWithAttributes('SubstLookupRecord', { index: 0 })
|
||||
);
|
||||
appendChildren(
|
||||
substLookupRecordDom,
|
||||
createElementWithAttributes('SequenceIndex', { value: 0 }),
|
||||
createElementWithAttributes('LookupListIndex', { value: listIndex })
|
||||
);
|
||||
}
|
||||
return chainDom;
|
||||
};
|
||||
|
||||
const buildCoverage = (chainDom, tagName, coverage) => {
|
||||
if (coverage) {
|
||||
coverage.forEach((glyph, i) => {
|
||||
const coverageDom = createElementWithAttributes(tagName, {
|
||||
index: i++,
|
||||
Format: 1,
|
||||
});
|
||||
coverageDom.appendChild(
|
||||
createElementWithAttributes('Glyph', { value: glyph })
|
||||
);
|
||||
chainDom.appendChild(coverageDom);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getSubstLookup = (input, output, lookupDom) => {
|
||||
if (!lookupDom) {
|
||||
lookupDom = substLookupMap[output];
|
||||
}
|
||||
|
||||
if (!lookupDom) {
|
||||
lookupDom = createElementWithAttributes('Lookup', {
|
||||
index: Object.keys(substLookupMap).length + 1,
|
||||
});
|
||||
lookupDom.appendChild(
|
||||
createElementWithAttributes('LookupType', { value: 1 })
|
||||
);
|
||||
lookupDom.appendChild(
|
||||
createElementWithAttributes('LookupFlag', { value: 0 })
|
||||
);
|
||||
substLookupMap[output] = lookupDom;
|
||||
}
|
||||
|
||||
let singleSubstDom = xpath.select('SingleSubst', lookupDom, true);
|
||||
|
||||
if (!singleSubstDom) {
|
||||
singleSubstDom = createElementWithAttributes('SingleSubst', {
|
||||
index: 0,
|
||||
Format: 2,
|
||||
});
|
||||
lookupDom.appendChild(singleSubstDom);
|
||||
}
|
||||
|
||||
// look for input subst
|
||||
const substitionDom = xpath.select(
|
||||
`Substitution[@in="${input}"]`,
|
||||
singleSubstDom,
|
||||
true
|
||||
);
|
||||
if (!substitionDom) {
|
||||
singleSubstDom.appendChild(
|
||||
createElementWithAttributes('Substitution', { in: input, out: output })
|
||||
);
|
||||
}
|
||||
return lookupDom;
|
||||
};
|
||||
|
||||
const createElementWithAttributes = (tagName, attributes) => {
|
||||
const element = dom.createElement(tagName);
|
||||
Object.entries(attributes).forEach((attribute) => {
|
||||
element.setAttribute(attribute[0], attribute[1]);
|
||||
});
|
||||
return element;
|
||||
};
|
||||
|
||||
const appendChildren = (node, ...children) => {
|
||||
children.forEach((child) => node.appendChild(child));
|
||||
};
|
||||
|
||||
//const serialize = dom => new XMLSerializer().serializeToString(dom);
|
||||
|
||||
exports.initGsubTables = initGsubTables;
|
||||
exports.buildGsubTables = buildGsubTables;
|
||||
exports.finalizeGsubTables = finalizeGsubTables;
|
||||
@@ -1,10 +1,11 @@
|
||||
// @ts-nocheck
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
if (!os.EOL) {
|
||||
os.EOL = process.platform === 'win32' ? '\r\n' : '\n';
|
||||
}
|
||||
|
||||
const gsub = require('./gsub');
|
||||
const calt = require('./calt');
|
||||
|
||||
const xpath = require('xpath');
|
||||
const { DOMParser, XMLSerializer } = require('xmldom');
|
||||
@@ -60,11 +61,13 @@ const buildFont = (profile, options) => {
|
||||
//[...ligatures, { name: 'LIG', glyph: 'LIG' }];
|
||||
|
||||
processPatch('names', patchNames, dom, ligatures, profile);
|
||||
processPatch('gsub', patchGsub, dom, ligatures, options);
|
||||
processPatch('charstrings', patchCharStrings, dom, ligaturesWithLIG);
|
||||
processPatch('glyphs', patchGlyphs, dom, ligaturesWithLIG);
|
||||
processPatch('hmtx', patchHmtx, dom);
|
||||
|
||||
const feature = calt.gencalt(ligatures);
|
||||
fs.writeFileSync('./features/calt.fea', feature);
|
||||
|
||||
console.log(`Writing ligature font file ${dstFileName}`);
|
||||
fs.writeFileSync(dstFileName, format(serialize(dom)));
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user